/* TrackView.c */
/*****************************************************************************/
/*                                                                           */
/*    Out Of Phase:  Digital Music Synthesis on General Purpose Computers    */
/*    Copyright (C) 1994  Thomas R. Lawrence                                 */
/*                                                                           */
/*    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., 675 Mass Ave, Cambridge, MA 02139, USA.              */
/*                                                                           */
/*    Thomas R. Lawrence can be reached at tomlaw@world.std.com.             */
/*                                                                           */
/*****************************************************************************/

#include "MiscInfo.h"
#include "Audit.h"
#include "Debug.h"
#include "Definitions.h"

#include "TrackView.h"
#include "TrackObject.h"
#include "Memory.h"
#include "FrameObject.h"
#include "Frequency.h"
#include "EditImages.h"
#include "StaffCalibration.h"
#include "TrackDisplayScheduling.h"
#include "Array.h"
#include "EditCommandParameters.h"
#include "Numbers.h"
#include "TieTrackPixel.h"
#include "Scrap.h"
#include "Files.h"
#include "BufferedFileInput.h"
#include "BufferedFileOutput.h"
#include "DataMunging.h"
#include "MainWindowStuff.h"
#include "LoadSaveNoteVectors.h"
#include "Alert.h"


/* so that tips of notes can be seen above last staff line */
#define EDGESPACER (48)

/* so that you have space to add new notes */
#define HORIZONTALEXTENTION (200)

/* width of the insertion point */
#define INSERTIONPOINTWIDTH (2)

/* tie endpoint positioning correction values */
#define TIESTARTXCORRECTION (11)
#define TIESTARTYCORRECTION (9)
#define TIEENDXCORRECTION (4)
#define TIEENDYCORRECTION (9)

/* tie curve drawing parameters */
#define NUMTIELINEINTERVALS (10)
#define MAXTIEDEPTH (15)

/* the width of the box drawn for range selection */
#define RANGESELECTTHICKNESS (3)


#define MAGICSCRAPSTRING ("\xff\x00\x1f\xfe NoteRangeScrap")


typedef enum
	{
		eTrackViewNoSelection EXECUTE(= -99),
		eTrackViewSingleNoteSelection,
		eTrackViewSingleCommandSelection,
		eTrackViewRangeSelection
	} SelectionModes;

typedef enum
	{
		eTrackViewNoUndo EXECUTE(= -5134),
		eTrackViewUndoNoteInsertion,
		eTrackViewUndoRangeInsertion,
		eTrackViewUndoFromFile
	} UndoChoices;

struct TrackViewRec
	{
		WinType*							Window;
		OrdType								XLoc;
		OrdType								YLoc;
		OrdType								Width;
		OrdType								Height;
		TrackObjectRec*				TrackObj;
		MyBoolean							CursorBarIsVisible;
		OrdType								CursorBarLoc;
		long									PixelIndent;
		long									VerticalOffset;

		SelectionModes				SelectionMode;
		long									InsertionPointIndex; /* valid only for eTrackViewNoSelection */
		NoteObjectRec*				SelectedNote; /* valid only for single selection */
		long									SelectedNoteFrame; /* valid only for single selection */
		long									RangeSelectStart; /* valid only for eTrackViewRangeSelection */
		long									RangeSelectEnd; /* valid only for eTrackViewRangeSelection */

		UndoChoices						UndoState;
		long									UndoNoteInsertionFrameIndex; /* valid only for eTrackViewUndoNoteInsertion */
		long									UndoNoteInsertionNoteIndex; /* valid only for eTrackViewUndoNoteInsertion */
		long									UndoRangeStartIndex; /* valid only for eTrackViewUndoRangeInsertion */
		long									UndoRangeElemCount; /* valid only for eTrackViewUndoRangeInsertion */
		FileSpec*							UndoTempFileLoc; /* valid only for eTrackViewUndoFromFile */
		FileType*							UndoTempFileDesc; /* valid only for eTrackViewUndoFromFile */

		TrackDispScheduleRec*	Schedule;
	};


static long								StaffRefCount = 0;
static short*							MinorStaffList = NIL;


/* create a new track view object */
TrackViewRec*						NewTrackView(struct TrackObjectRec* TrackObj, WinType* Window,
													OrdType X, OrdType Y, OrdType Width, OrdType Height)
	{
		TrackViewRec*					View;
		ArrayRec*							BackgroundList;
		long									Limit;
		long									Scan;

		View = (TrackViewRec*)AllocPtrCanFail(sizeof(TrackViewRec),"TrackViewRec");
		if (View == NIL)
			{
			 FailurePoint1:
				return NIL;
			}
		View->Schedule = NewTrackDisplaySchedule(TrackObj);
		if (View->Schedule == NIL)
			{
			 FailurePoint3:
				ReleasePtr((char*)View);
				goto FailurePoint1;
			}
		BackgroundList = TrackObjectGetBackgroundList(TrackObj);
		Limit = ArrayGetLength(BackgroundList);
		for (Scan = 0; Scan < Limit; Scan += 1)
			{
				if (!AddTrackToDisplaySchedule(View->Schedule,
					(TrackObjectRec*)ArrayGetElement(BackgroundList,Scan)))
					{
					 FailurePoint4:
						goto FailurePoint3;
					}
			}
		if (!TrackObjectAddDependentView(TrackObj,View))
			{
			 FailurePoint5:
				goto FailurePoint4;
			}
		for (Scan = 0; Scan < Limit; Scan += 1)
			{
				if (!TrackObjectAddDependentView((TrackObjectRec*)ArrayGetElement(
					BackgroundList,Scan),View))
					{
					 FailurePoint6:
						/* WARNING:  Scan must be valid from this loop */
						while (Scan > 0)
							{
								Scan -= 1;
								TrackObjectRemoveDependentView((TrackObjectRec*)ArrayGetElement(
									BackgroundList,Scan),View);
							}
						TrackObjectRemoveDependentView(TrackObj,View);
						goto FailurePoint5;
					}
			}
		if (StaffRefCount == 0)
			{
				MinorStaffList = GetMinorStaffList();
				if (MinorStaffList == NIL)
					{
						goto FailurePoint6;
					}
			}
		StaffRefCount += 1;
		View->Window = Window;
		View->XLoc = X;
		View->YLoc = Y;
		View->Width = Width;
		View->Height = Height;
		View->TrackObj = TrackObj;
		View->PixelIndent = 0;
		View->CursorBarIsVisible = False;
		View->SelectionMode = eTrackViewNoSelection;
		View->InsertionPointIndex = 0;
		View->VerticalOffset = (MaxVerticalSize() - Height) / 2;
		if (View->VerticalOffset < -EDGESPACER)
			{
				View->VerticalOffset = -EDGESPACER;
			}
		View->VerticalOffset = (View->VerticalOffset / 4) * 4;
		View->UndoState = eTrackViewNoUndo;
		return View;
	}


/* dispose of the track view object */
void										DisposeTrackView(TrackViewRec* View)
	{
		ArrayRec*							BackgroundList;
		long									Limit;
		long									Scan;

		CheckPtrExistence(View);
		TrackViewFlushUndoInfo(View);
		BackgroundList = TrackObjectGetBackgroundList(View->TrackObj);
		Limit = ArrayGetLength(BackgroundList);
		for (Scan = 0; Scan < Limit; Scan += 1)
			{
				TrackObjectRemoveDependentView((TrackObjectRec*)ArrayGetElement(
					BackgroundList,Scan),View);
			}
		TrackObjectRemoveDependentView(View->TrackObj,View);
		DisposeTrackDisplaySchedule(View->Schedule);
		StaffRefCount -= 1;
		if (StaffRefCount == 0)
			{
				ReleasePtr((char*)MinorStaffList);
			}
		ReleasePtr((char*)View);
	}


/* get the left edge of the track view area */
OrdType									GetTrackViewXLoc(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return View->XLoc;
	}


/* get the top edge of the track view area */
OrdType									GetTrackViewYLoc(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return View->YLoc;
	}


/* get the width of the track view area */
OrdType									GetTrackViewWidth(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return View->Width;
	}


/* get the height of the track view area */
OrdType									GetTrackViewHeight(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return View->Height;
	}


/* change the location of the track view area */
void										SetTrackViewPosition(TrackViewRec* View, OrdType X, OrdType Y,
													OrdType Width, OrdType Height)
	{
		CheckPtrExistence(View);
		View->VerticalOffset += (View->Height - Height) / 2;
		if (View->VerticalOffset < -EDGESPACER)
			{
				View->VerticalOffset = -EDGESPACER;
			}
		View->VerticalOffset = (View->VerticalOffset / 4) * 4;
		View->XLoc = X;
		View->YLoc = Y;
		View->Width = Width;
		View->Height = Height;
		TrackViewRedrawAll(View);
	}


/* redraw the track view staff bars.  it assumes clipping rectangle has been */
/* properly set up */
void										TrackViewRedrawStaff(TrackViewRec* View, OrdType XStart,
													OrdType Width)
	{
		long									Scan;
		short*								MajorStaffList;

		CheckPtrExistence(View);
		/* draw staff bars around C */
		MajorStaffList = GetMajorStaffList();
		for (Scan = GetMajorStaffListLength(); Scan >= 0; Scan -= 1)
			{
				DrawLine(View->Window,eBlack,XStart,ConvertPitchToPixel(MajorStaffList[Scan],0)
					- View->VerticalOffset + View->YLoc,Width,0);
			}
		/* draw other staff bars */
		for (Scan = PtrSize((char*)MinorStaffList) / sizeof(short) - 1; Scan >= 0; Scan -= 1)
			{
				DrawLine(View->Window,eMediumGrey,XStart,ConvertPitchToPixel(
					MinorStaffList[Scan],0) - View->VerticalOffset + View->YLoc,Width,0);
			}
		/* draw all C lines */
		for (Scan = 0; Scan <= (NUMNOTES - 1) / 2; Scan += 12)
			{
				DrawLine(View->Window,eLightGrey,XStart,ConvertPitchToPixel(CENTERNOTE - Scan,0)
					- View->VerticalOffset + View->YLoc,Width,0);
				DrawLine(View->Window,eLightGrey,XStart,ConvertPitchToPixel(CENTERNOTE + Scan,0)
					- View->VerticalOffset + View->YLoc,Width,0);
			}
	}


/* get the number of vertical degrees of freedom, for setting the vertical scroll bar */
long										TrackViewGetVerticalDegreesOfFreedom(TrackViewRec* View)
	{
		long									ReturnValue;

		CheckPtrExistence(View);
		ReturnValue = MaxVerticalSize() - View->Height + (2 * EDGESPACER);
		if (ReturnValue < 0)
			{
				ReturnValue = 0;
			}
		return ReturnValue;
	}


/* get the vertical offset for setting vertical scroll bar.  0 is top of score area. */
long										TrackViewGetVerticalOffset(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		/* they want all positive numbers, so we add in EDGESPACER */
		return View->VerticalOffset + EDGESPACER;
	}


/* hit test to see if the coordinates are within the track view area */
MyBoolean								TrackViewHitTest(TrackViewRec* View, OrdType X, OrdType Y)
	{
		CheckPtrExistence(View);
		return (X >= View->XLoc) && (Y >= View->YLoc) && (X < View->XLoc + View->Width)
			&& (Y < View->YLoc + View->Height);
	}


/* change the vertical offset and redraw the track view area */
void										TrackViewSetNewVerticalOffset(TrackViewRec* View, long Offset)
	{
		long									Delta;

		CheckPtrExistence(View);
		TrackViewUndrawCursorBar(View);
		/* do some bounds checking.  we don't use EDGESPACER since they are dealing */
		/* in positive numbers, and so does TrackViewGetVerticalDegreesOfFreedom. */
		if (Offset < 0)
			{
				Offset = 0;
			}
		if (Offset > TrackViewGetVerticalDegreesOfFreedom(View) - 1)
			{
				Offset = TrackViewGetVerticalDegreesOfFreedom(View) - 1;
			}
		/* they use positive numbers, but we need one with proper origin */
		Offset -= EDGESPACER;
		/* since we use patterns aligned to 2 and 4 pixels, keep things even so */
		/* the patterns don't look weird */
		Offset = (Offset / 4) * 4;
		/* figure out how much to scroll by */
		Delta = View->VerticalOffset - Offset;
		if (Delta < ORDTYPEMIN)
			{
				Delta = ORDTYPEMIN;
			}
		else if (Delta > ORDTYPEMAX)
			{
				Delta = ORDTYPEMAX;
			}
		SetClipRect(View->Window,View->XLoc,View->YLoc,View->Width,View->Height);
		ScrollArea(View->Window,View->XLoc,View->YLoc,View->Width,View->Height,0,Delta);
		if (Delta < 0)
			{
				/* current offset < new offset, scroll up & open hole at bottom */
				AddClipRect(View->Window,View->XLoc,View->YLoc + View->Height + Delta
					- RANGESELECTTHICKNESS,View->Width,-Delta + RANGESELECTTHICKNESS);
			}
		 else
			{
				/* current offset > new offset, scroll down & open hole at top */
				AddClipRect(View->Window,View->XLoc,View->YLoc,View->Width,Delta
					+ RANGESELECTTHICKNESS);
			}
		View->VerticalOffset = Offset;
		TrackViewRedrawDontSetClip(View,View->XLoc,View->Width);
	}


/* redraw the entire track view, including properly setting the clipping region. */
void										TrackViewRedrawAll(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		SetClipRect(View->Window,View->XLoc,View->YLoc,View->Width,View->Height);
		TrackViewRedrawDontSetClip(View,View->XLoc,View->Width);
	}


/* update the cursor and optionally draw the vertical (pitch) positioning bar */
void										TrackViewUpdateMouseCursor(TrackViewRec* View,
													OrdType X, OrdType Y, MyBoolean DrawFunnyBarThing)
	{
		MyBoolean							OnAFrameFlag;
		long									Dummy;
		OrdType								NewCursorBar;

		CheckPtrExistence(View);
		X -= View->XLoc;
		Y -= View->YLoc;
		if (!TrackDisplayPixelToIndex(View->Schedule,View->TrackObj,X + View->PixelIndent,
			&OnAFrameFlag,&Dummy))
			{
				return;
			}
		if (DrawFunnyBarThing)
			{
				NewCursorBar = ConvertPitchToPixel(ConvertPixelToPitch(Y + View->VerticalOffset),0);
				if ((NewCursorBar != View->CursorBarLoc) || !View->CursorBarIsVisible)
					{
						TrackViewUndrawCursorBar(View);
						View->CursorBarLoc = NewCursorBar;
						TrackViewDrawCursorBar(View);
					}
			}
		if (OnAFrameFlag)
			{
				SetScoreOverlayCursor();
			}
		 else
			{
				SetScoreIntersticeCursor();
			}
	}


/* the track data has been modified, so set some flags */
void										TrackViewTrackObjectModified(TrackViewRec* View,
													TrackObjectRec* TrackObj, long Index)
	{
		CheckPtrExistence(View);
		CheckPtrExistence(TrackObj);
		TrackDisplayScheduleMarkChanged(View->Schedule,View->TrackObj,Index);
	}


/* another track has been deleted, so remove it from the background list */
void										TrackViewObjectTrackDying(TrackViewRec* View,
													TrackObjectRec* TrackObj)
	{
		CheckPtrExistence(View);
		CheckPtrExistence(TrackObj);
		DeleteTrackFromDisplaySchedule(View->Schedule,TrackObj);
	}


/* insert a note at the specified mouse coordinate with the attributes */
void										TrackViewAddNote(TrackViewRec* View,
													OrdType X, OrdType Y, unsigned long NoteAttributes)
	{
		MyBoolean							AddToFrame;
		long									Index;
		NoteObjectRec*				Note;
		unsigned long					NoteSharpFlatTemp;
		short									NotePitchTemp;

		CheckPtrExistence(View);
		ERROR((NoteAttributes & eCommandFlag) != 0,PRERR(ForceAbort,
			"TrackViewAddNote being used to insert command into score"));
		TrackViewFlushUndoInfo(View);
		X -= View->XLoc;
		Y -= View->YLoc;
		/* figure out where we are supposed to put this note. */
		if (!TrackDisplayPixelToIndex(View->Schedule,View->TrackObj,X + View->PixelIndent,
			&AddToFrame,&Index))
			{
				return;
			}
		/* construct the note to be added */
		Note = NewNote();
		if (Note == NIL)
			{
				return;
			}
		SetUpNoteInfo(&NotePitchTemp,&NoteSharpFlatTemp,(NoteAttributes & eSharpModifier)
			!= 0,(NoteAttributes & eFlatModifier) != 0,Y + View->VerticalOffset);
		PutNotePitch(Note,NotePitchTemp);
		PutNoteDuration(Note,NoteAttributes & eDurationMask);
		PutNoteDurationDivision(Note,NoteAttributes & eDivisionMask);
		PutNoteDotStatus(Note,(NoteAttributes & eDotModifier) != 0);
		PutNoteFlatOrSharpStatus(Note,NoteSharpFlatTemp);
		PutNoteIsItARest(Note,(NoteAttributes & eRestModifier) != 0);
		ERROR((NoteAttributes & ~(eSharpModifier | eFlatModifier | eDurationMask
			| eDivisionMask | eDotModifier | eRestModifier)) != 0,PRERR(AllowResume,
			"TrackViewAddNote:  some unknown bits in the note attributes word are set"));
		/* add note to the appropriate place */
		if (AddToFrame)
			{
				FrameObjectRec*				Frame;

				/* add to existing frame */
				Frame = TrackObjectGetFrame(View->TrackObj,Index);
				if (IsThisACommandFrame(Frame))
					{
						/* if it's a command frame, then insert note after it */
						Index += 1;
						goto InsertionPoint;
					}
				if (!AppendNoteToFrame(Frame,Note))
					{
					 AppendFailurePoint1:
						DisposeNote(Note);
						return;
					}
				TrackObjectAltered(View->TrackObj,Index);
				/* remember the undo information */
				ERROR(View->UndoState != eTrackViewNoUndo,PRERR(ForceAbort,
					"TrackViewAddNote:  undo info should have been purged"));
				View->UndoState = eTrackViewUndoNoteInsertion;
				View->UndoNoteInsertionFrameIndex = Index;
				View->UndoNoteInsertionNoteIndex = NumNotesInFrame(Frame) - 1;
			}
		 else
			{
				FrameObjectRec*				Frame;

				/* create a new frame */
			 InsertionPoint:
				/* we hafta create a new frame */
				Frame = NewFrame();
				if (Frame == NIL)
					{
					 InsertionFailurePoint1:
						goto AppendFailurePoint1;
					}
				if (!AppendNoteToFrame(Frame,Note))
					{
					 InsertionFailurePoint2:
						DisposeFrameAndContents(Frame);
						goto InsertionFailurePoint1;
					}
				if (!TrackObjectInsertFrame(View->TrackObj,Index,Frame))
					{
					 InsertionFailurePoint3:
						goto InsertionFailurePoint2;
					}
				/* remember the undo information */
				ERROR(View->UndoState != eTrackViewNoUndo,PRERR(ForceAbort,
					"TrackViewAddNote:  undo info should have been purged"));
				View->UndoState = eTrackViewUndoNoteInsertion;
				View->UndoNoteInsertionFrameIndex = Index;
				View->UndoNoteInsertionNoteIndex = 0;
				/* we don't have to notify track of change because it knows already. */
			}
		/* adjust the insertion point */
		View->SelectionMode = eTrackViewSingleNoteSelection;
		View->SelectedNote = Note;
		View->SelectedNoteFrame = Index;
		/* redraw what needs to be redrawn.  this definitely needs to be fixed */
		/* up since it's unnecessary to redraw the whole staff just to add a note. */
		TrackViewRedrawAll(View);
		View->CursorBarIsVisible = False; /* temporary */
	}


/* add a command at the specified mouse coordinates. */
void										TrackViewAddCommand(TrackViewRec* View,
													OrdType X, OrdType Y, NoteCommands CommandOpcode)
	{
		MyBoolean							AddToFrame;
		long									Index;
		NoteObjectRec*				Command;
		FrameObjectRec*				Frame;

		CheckPtrExistence(View);
		TrackViewFlushUndoInfo(View);
		X -= View->XLoc;
		Y -= View->YLoc;
		/* figure out where we are supposed to put this note. */
		if (!TrackDisplayPixelToIndex(View->Schedule,View->TrackObj,X + View->PixelIndent,
			&AddToFrame,&Index))
			{
				return;
			}
		/* AddToFrame is ignored for adding commands */
		/* construct the command to be added */
		Command = NewCommand();
		if (Command == NIL)
			{
				return;
			}
		PutCommandOpcode(Command,CommandOpcode);
		/* add command to the appropriate place */
		/* create a new frame */
		Frame = NewFrame();
		if (Frame == NIL)
			{
			 InsertionFailurePoint1:
				DisposeNote(Command);
				return;
			}
		if (!AppendNoteToFrame(Frame,Command))
			{
			 InsertionFailurePoint2:
				DisposeFrameAndContents(Frame);
				goto InsertionFailurePoint1;
			}
		if (!TrackObjectInsertFrame(View->TrackObj,Index,Frame))
			{
			 InsertionFailurePoint3:
				goto InsertionFailurePoint2;
			}
		/* remember the undo information */
		ERROR(View->UndoState != eTrackViewNoUndo,PRERR(ForceAbort,
			"TrackViewAddCommand:  undo info should have been purged"));
		View->UndoState = eTrackViewUndoNoteInsertion;
		View->UndoNoteInsertionFrameIndex = Index;
		View->UndoNoteInsertionNoteIndex = 0;
		/* we don't have to notify track of change because it knows already. */
		/* adjust the insertion point */
		View->SelectionMode = eTrackViewSingleCommandSelection;
		View->SelectedNote = Command;
		View->SelectedNoteFrame = Index;
		/* redraw what needs to be redrawn.  this definitely needs to be fixed */
		/* up since it's unnecessary to redraw the whole staff just to add a command. */
		TrackViewRedrawAll(View);
		View->CursorBarIsVisible = False; /* temporary */
	}


/* erase the vertical (pitch) positioning bar */
void										TrackViewUndrawCursorBar(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		if (View->CursorBarIsVisible)
			{
				SetClipRect(View->Window,View->XLoc,View->YLoc,View->Width,View->Height);
				AddClipRect(View->Window,View->XLoc,View->YLoc - View->VerticalOffset
					+ View->CursorBarLoc - 1,View->Width,3);
				DrawBoxErase(View->Window,View->XLoc,View->YLoc - View->VerticalOffset
					+ View->CursorBarLoc - 1,View->Width,3);
				TrackViewRedrawDontSetClip(View,View->XLoc,View->Width);
			}
		View->CursorBarIsVisible = False;
	}


/* draw the vertical (pitch) positioning bar.  this assumes that it has been */
/* previously undrawn (i.e. it doesn't erase any existing bars) */
void										TrackViewDrawCursorBar(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		View->CursorBarIsVisible = True;
		SetClipRect(View->Window,View->XLoc,View->YLoc,View->Width,View->Height);
		DrawBoxPaint(View->Window,eMediumGrey,View->XLoc,View->YLoc - View->VerticalOffset
			+ View->CursorBarLoc - 1,View->Width,3);
	}


/* draw a cute little paraboloid thing */
static void							DrawTieLine(WinType* ScreenID, Patterns Pattern,
													OrdType StartX, OrdType StartY, OrdType Width, OrdType Height)
	{
		long									Index;
		OrdType								PixelXLoc[NUMTIELINEINTERVALS];
		OrdType								PixelYLoc[NUMTIELINEINTERVALS];

		/* generate coordinates */
		for (Index = 0; Index < NUMTIELINEINTERVALS; Index += 1)
			{
				double								FuncX;

				FuncX = 2 * ((double)Index / (NUMTIELINEINTERVALS - 1)) - 1;
				PixelXLoc[Index] = Width * ((double)Index / (NUMTIELINEINTERVALS - 1));
				PixelYLoc[Index] = Height * ((double)Index / (NUMTIELINEINTERVALS - 1))
					+ ((1 - FuncX * FuncX) * MAXTIEDEPTH);
			}
		/* draw the lines */
		for (Index = 0; Index < NUMTIELINEINTERVALS - 1; Index += 1)
			{
				OrdType								PosX = StartX + PixelXLoc[Index];
				OrdType								PosY = StartY + PixelYLoc[Index];

				DrawLine(ScreenID,Pattern,PosX,PosY,(StartX + PixelXLoc[Index + 1]) - PosX,
					(StartY + PixelYLoc[Index + 1]) - PosY);
			}
	}


/* redraw the whole track view, but don't mess with the clipping region.  XStart is */
/* is in window coordinates, not local coordinates */
void										TrackViewRedrawDontSetClip(TrackViewRec* View, OrdType XStart,
													OrdType Width)
	{
		long									NumTracks;
		long									TrackScan;
		long									CenterNotePixel;
		FontType							Font;
		OrdType								FontHeight;

		CheckPtrExistence(View);
		/* erase the drawing area */
		DrawBoxErase(View->Window,XStart,View->YLoc,Width,View->Height);
		/* draw the staff */
		TrackViewRedrawStaff(View,XStart,Width);
		/* now we have to draw the notes */
		CenterNotePixel = View->YLoc - View->VerticalOffset + GetCenterNotePixel();
		Font = GetScreenFont();
		FontHeight = GetFontHeight(Font,9);

		/* draw ties first, so they are under the notes */
		{
			TieIntersectListRec*	TieList;

			TieList = TrackDisplayGetTieIntervalList(View->Schedule,View->PixelIndent,Width);
			if (TieList != NIL)
				{
					long									TieLimit;
					long									TieScan;

					TieLimit = GetTieTrackIntersectListLength(TieList);
					for (TieScan = 0; TieScan < TieLimit; TieScan += 1)
						{
							long									StartX;
							long									StartY;
							long									EndX;
							long									EndY;

							GetTieTrackIntersectElement(TieList,TieScan,&StartX,&StartY,&EndX,&EndY);
							DrawTieLine(View->Window,eBlack,(StartX + TIESTARTXCORRECTION)
								- View->PixelIndent + View->XLoc,(StartY + TIESTARTYCORRECTION)
								+ View->YLoc - View->VerticalOffset,(EndX + TIEENDXCORRECTION)
								- (StartX + TIESTARTXCORRECTION),(EndY + TIEENDYCORRECTION)
								- (StartY + TIESTARTYCORRECTION));
						}
					DisposeTieTrackIntersectList(TieList);
				}
		}

		/* draw the notes */
		NumTracks = TrackDisplayGetNumTracks(View->Schedule);
		/* note the less than or EQUAL to NumTracks in the loop... */
		/* see note inside loop about why */
		for (TrackScan = 0; TrackScan <= NumTracks; TrackScan += 1)
			{
				TrackObjectRec*				TrackObj;
				long									ActualTrackIndex;

				/* we want to draw our track, but we want to draw it last.  to do this, */
				/* we won't draw it when it comes around normally, but we'll loop one extra */
				/* (fake) time, during which we won't do the normal get track but instead, */
				/* we'll just use our own track.  not especially elegant, but it works */
				if (TrackScan < NumTracks)
					{
						TrackObj = TrackDisplayGetParticularTrack(View->Schedule,TrackScan);
					}
				 else
					{
						TrackObj = View->TrackObj;
					}
				ActualTrackIndex = TrackDisplayGetTrackIndex(View->Schedule,TrackObj);
				if ((TrackObj != View->TrackObj) || (TrackScan == NumTracks))
					{
						MyBoolean							Idiot;
						long									Position;

						if (TrackDisplayPixelToIndex(View->Schedule,TrackObj,XStart
							+ View->PixelIndent,&Idiot,&Position))
							{
								long									Limit;
								long									Scan;

								/* find out how much to draw.  this definitely needs to be */
								/* fixed since there is no reason to draw frames until the */
								/* end of the entire track */
								Limit = TrackObjectGetNumFrames(TrackObj);

								if (Position > 0)
									{
										/* this draws the note just off the left edge of the */
										/* screen to fix some update region alignment problems. */
										Position -= 1;
									}

								/* draw the insertion point, if there is one */
								if ((TrackObj == View->TrackObj) && (View->SelectionMode
									== eTrackViewNoSelection))
									{
										long						TotalNumFrames;

										TotalNumFrames = TrackObjectGetNumFrames(TrackObj);
										ERROR((View->InsertionPointIndex < 0)
											|| (View->InsertionPointIndex > TotalNumFrames),PRERR(ForceAbort,
											"TrackViewRedrawDontSetClip:  insertion point index out of range"));
										if (View->InsertionPointIndex < TotalNumFrames)
											{
												long						InsertionPixel;

												/* draw the selection before it's corresponding frame */
												if (TrackDisplayIndexToPixel(View->Schedule,0/*first track*/,
													View->InsertionPointIndex,&InsertionPixel))
													{
														InsertionPixel = View->XLoc - INSERTIONPOINTWIDTH / 2
															+ InsertionPixel - View->PixelIndent;
														if (InsertionPixel < ORDTYPEMIN / 2)
															{
																InsertionPixel = ORDTYPEMIN / 2;
															}
														if (InsertionPixel > ORDTYPEMAX / 2)
															{
																InsertionPixel = ORDTYPEMAX / 2;
															}
														DrawBoxPaint(View->Window,eBlack,InsertionPixel,
															View->YLoc,INSERTIONPOINTWIDTH,View->Height);
													}
											}
										else if (TotalNumFrames > 0)
											{
												long						InsertionPixel;

												/* oops, no frame, so draw it after the last frame */
												if (TrackDisplayIndexToPixel(View->Schedule,0/*first track*/,
													TrackObjectGetNumFrames(TrackObj) - 1,&InsertionPixel))
													{
														InsertionPixel += WidthOfFrameAndDraw(View->Window,
															0,0,0,TrackObjectGetFrame(TrackObj,TotalNumFrames - 1),
															False/*don't draw*/,False);
														InsertionPixel = View->XLoc - INSERTIONPOINTWIDTH / 2
															+ InsertionPixel + EXTERNALSEPARATION - View->PixelIndent;
														if (InsertionPixel < ORDTYPEMIN / 2)
															{
																InsertionPixel = ORDTYPEMIN / 2;
															}
														if (InsertionPixel > ORDTYPEMAX / 2)
															{
																InsertionPixel = ORDTYPEMAX / 2;
															}
														DrawBoxPaint(View->Window,eBlack,InsertionPixel,View->YLoc,
															INSERTIONPOINTWIDTH,View->Height);
													}
											}
										else
											{
												/* oops, no frames at all, so just draw it at the beginning */
												/* oh, well, it doesn't get drawn... should fix. (not that */
												/* there's any doubt where an insertion would occur...) */
											}
									}

								/* draw all of the frames */
								for (Scan = Position; Scan < Limit; Scan += 1)
									{
										long									PixelIndex;
										OrdType								TheFrameWidth EXECUTE(= -8181);

										/* see if we can draw.  if TrackDisplayIndexToPixel returns false, */
										/* then it just won't be drawn.  if TrackDisplayShouldWeDrawIt */
										/* returns False, then don't draw because it isn't scheduled. */
										if (TrackDisplayIndexToPixel(View->Schedule,ActualTrackIndex,Scan,
											&PixelIndex) && TrackDisplayShouldWeDrawIt(View->Schedule,
											ActualTrackIndex,Scan))
											{
												FrameObjectRec*				Frame;

												/* escape if we have gone past the end of the window */
												if (PixelIndex - (View->PixelIndent - View->XLoc)
													- LEFTNOTEEDGEINSET >= XStart + Width)
													{
														goto DonePoint;
													}

												/* get the frame to draw */
												Frame = TrackObjectGetFrame(TrackObj,Scan);

												/* this section is for stuff that only works in */
												/* the current track */
												if (TrackObj == View->TrackObj)
													{
														long							MeasureBarIndex;

														/* if we should draw a measure bar, then do so.  this */
														/* is done before drawing the notes so that the notes */
														/* will appear on top of the measure bar numbers if */
														/* there is an overwrite */

														/* draw the measure bar */
														MeasureBarIndex = TrackDisplayMeasureBarIndex(
															View->Schedule,Scan);
														if (MeasureBarIndex != -1)
															{
																char*							IndexText;
																Patterns					HowToDraw;

																if (TrackDisplayShouldMeasureBarBeGreyed(
																	View->Schedule,Scan))
																	{
																		HowToDraw = eMediumGrey;
																	}
																 else
																	{
																		HowToDraw = eBlack;
																	}
																/* actually something to be drawn */
																DrawLine(View->Window,HowToDraw,PixelIndex - 4/*!*/
																	- View->PixelIndent + View->XLoc,View->YLoc
																	- View->VerticalOffset,0,MaxVerticalSize());
																IndexText = IntegerToString(MeasureBarIndex);
																if (IndexText != NIL)
																	{
																		DrawTextLine(View->Window,GetScreenFont(),9,
																			IndexText,PtrSize(IndexText),PixelIndex - 4/*!*/
																			- View->PixelIndent + View->XLoc - (LengthOfText(
																			GetScreenFont(),9,IndexText,PtrSize(IndexText),
																			ePlain) / 2),CenterNotePixel - (GetFontHeight(
																			GetScreenFont(),9) / 2),ePlain);
																		ReleasePtr(IndexText);
																	}
															}
													}

												/* draw the notes */
												TheFrameWidth = WidthOfFrameAndDraw(View->Window,PixelIndex
													- View->PixelIndent + View->XLoc,CenterNotePixel,
													FontHeight,Frame,True/*draw*/,
													(TrackObj != View->TrackObj)/*greyed*/);

												/* draw any hilighting for selected items */
												switch (View->SelectionMode)
													{
														default:
															EXECUTE(PRERR(ForceAbort,
																"TrackViewRedrawDontSetClip:  bad selection mode"));
															break;
														case eTrackViewNoSelection:
															break;
														case eTrackViewSingleNoteSelection:
															{
																long							FrameEnd;
																long							Scan;

																FrameEnd = NumNotesInFrame(Frame);
																ERROR((View->SelectedNoteFrame < 0)
																	|| (View->SelectedNoteFrame
																	>= TrackObjectGetNumFrames(View->TrackObj)),
																	PRERR(ForceAbort,
																	"TrackViewRedrawDontSetClip:  bad selected note frame"));
																Scan = 0;
																while (Scan < FrameEnd)
																	{
																		if (GetNoteFromFrame(Frame,Scan)
																			== View->SelectedNote)
																			{
																				DrawBoxFrame(View->Window,eMediumGrey,
																					PixelIndex - View->PixelIndent + View->XLoc
																					+ (Scan * INTERNALSEPARATION),View->YLoc
																					- View->VerticalOffset,ICONWIDTH,
																					MaxVerticalSize());
																				DrawBoxFrame(View->Window,eMediumGrey,
																					PixelIndex - View->PixelIndent + View->XLoc
																					+ (Scan * INTERNALSEPARATION) + 1,View->YLoc
																					- View->VerticalOffset + 1,ICONWIDTH - 2,
																					MaxVerticalSize() - 2);
																				goto SingleNoteSelectDonePoint;
																			}
																		Scan += 1;
																	}
															}
														 SingleNoteSelectDonePoint:
															break;
														case eTrackViewSingleCommandSelection:
															ERROR((View->SelectedNoteFrame < 0)
																|| (View->SelectedNoteFrame
																>= TrackObjectGetNumFrames(View->TrackObj)),
																PRERR(ForceAbort,
																"TrackViewRedrawDontSetClip:  bad selected note frame"));
															if (IsThisACommandFrame(Frame))
																{
																	if (View->SelectedNote == GetNoteFromFrame(Frame,0))
																		{
																			DrawBoxFrame(View->Window,eMediumGrey,PixelIndex
																				- View->PixelIndent + View->XLoc,View->YLoc
																				- View->VerticalOffset,TheFrameWidth,
																				MaxVerticalSize());
																			DrawBoxFrame(View->Window,eMediumGrey,PixelIndex
																				- View->PixelIndent + View->XLoc + 1,View->YLoc
																				- View->VerticalOffset + 1,TheFrameWidth - 2,
																				MaxVerticalSize() - 2);
																		}
																}
															break;
														case eTrackViewRangeSelection:
															break;
													}
											}
									} /* end of frame scan */
								/* jump here when end of visible note series is reached */
							 DonePoint:
								;
							}
					}
			} /* end of track scan */

		/* draw selection box */
		if (View->SelectionMode == eTrackViewRangeSelection)
			{
				long							StartPixelIndex;
				long							EndPixelIndex;
				long							OurTrackIndex;

				OurTrackIndex = TrackDisplayGetTrackIndex(View->Schedule,View->TrackObj);
				ERROR((View->RangeSelectStart < 0)
					|| (View->RangeSelectStart > TrackObjectGetNumFrames(View->TrackObj)),
					PRERR(ForceAbort,"TrackViewRedrawDontSetClip:  bad range start"));
				ERROR((View->RangeSelectEnd < 0)
					|| (View->RangeSelectEnd > TrackObjectGetNumFrames(View->TrackObj)),
					PRERR(ForceAbort,"TrackViewRedrawDontSetClip:  bad range end"));
				ERROR(View->RangeSelectStart >= View->RangeSelectEnd,
					PRERR(ForceAbort,"TrackViewRedrawDontSetClip:  "
					"range selection boundaries are invalid"));

				if (!TrackDisplayIndexToPixel(View->Schedule,OurTrackIndex,
					View->RangeSelectStart,&StartPixelIndex))
					{
						return;
					}
				StartPixelIndex += (View->XLoc - View->PixelIndent); /* normalize to screen */
				if (StartPixelIndex < ORDTYPEMIN / 2)
					{
						StartPixelIndex = ORDTYPEMIN / 2;
					}
				if (StartPixelIndex > ORDTYPEMAX / 2)
					{
						StartPixelIndex = ORDTYPEMAX / 2;
					}

				if (!TrackDisplayIndexToPixel(View->Schedule,OurTrackIndex,
					View->RangeSelectEnd - 1,&EndPixelIndex))
					{
						return;
					}
				EndPixelIndex += WidthOfFrameAndDraw(View->Window,0,0,0,
					TrackObjectGetFrame(View->TrackObj,View->RangeSelectEnd - 1),
					False/*nodraw*/,False);
				EndPixelIndex += (View->XLoc - View->PixelIndent); /* normalize to screen */
				if (EndPixelIndex < ORDTYPEMIN / 2)
					{
						EndPixelIndex = ORDTYPEMIN / 2;
					}
				if (EndPixelIndex > ORDTYPEMAX / 2)
					{
						EndPixelIndex = ORDTYPEMAX / 2;
					}

				/* draw upper and lower edges of bounding box */
				DrawBoxPaint(View->Window,eBlack,StartPixelIndex,View->YLoc,EndPixelIndex
					- StartPixelIndex + EXTERNALSEPARATION,RANGESELECTTHICKNESS);
				DrawBoxPaint(View->Window,eBlack,StartPixelIndex,View->YLoc + View->Height
					- RANGESELECTTHICKNESS,EndPixelIndex - StartPixelIndex + EXTERNALSEPARATION,
					RANGESELECTTHICKNESS);

				/* draw left edge of bounding box */
				DrawBoxPaint(View->Window,eBlack,StartPixelIndex,View->YLoc,
					RANGESELECTTHICKNESS,View->Height);

				/* draw right edge of bounding box */
				DrawBoxPaint(View->Window,eBlack,EndPixelIndex + EXTERNALSEPARATION,
					View->YLoc,RANGESELECTTHICKNESS,View->Height);
			}
	}


/* get the horizontal offset from the start of the track, in pixels */
long										TrackViewGetHorizontalOffset(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return View->PixelIndent;
	}


/* get the horizontal extent of the track (in pixels) for setting the scroll bar */
long										TrackViewGetHorizontalDegreesOfFreedom(TrackViewRec* View)
	{
		long									Temp;

		CheckPtrExistence(View);
		if (!TrackDisplayGetTotalLength(View->Schedule,&Temp))
			{
				return 0;
			}
		Temp = Temp - View->Width + HORIZONTALEXTENTION;
		if (Temp < 0)
			{
				Temp = 0;
			}
		return Temp;
	}


/* change the horizontal position of the track display and redraw */
void										TrackViewSetNewHorizontalOffset(TrackViewRec* View, long Offset)
	{
		long									Delta;

		CheckPtrExistence(View);
		TrackViewUndrawCursorBar(View);
		/* do some bounds checking. */
		if (Offset < 0)
			{
				Offset = 0;
			}
		if (Offset > TrackViewGetHorizontalDegreesOfFreedom(View) - 1)
			{
				Offset = TrackViewGetHorizontalDegreesOfFreedom(View) - 1;
			}
		/* since we use patterns aligned to 2 and 4 pixels, keep things even so */
		/* the patterns don't look weird */
		Offset = (Offset / 4) * 4;
		/* figure out how much to shift by */
		Delta = View->PixelIndent - Offset;
		if (Delta < ORDTYPEMIN)
			{
				Delta = ORDTYPEMIN;
			}
		else if (Delta > ORDTYPEMAX)
			{
				Delta = ORDTYPEMAX;
			}
		SetClipRect(View->Window,View->XLoc,View->YLoc,View->Width,View->Height);
		ScrollArea(View->Window,View->XLoc,View->YLoc,View->Width,View->Height,Delta,0);
		if (Delta < 0)
			{
				/* current offset < new offset, scroll left & open hole at right */
				AddClipRect(View->Window,View->XLoc + View->Width + Delta,View->YLoc,
					-Delta,View->Height);
			}
		 else
			{
				/* current offset > new offset, scroll right & open hole at left */
				AddClipRect(View->Window,View->XLoc,View->YLoc,Delta,View->Height);
			}
		View->PixelIndent = Offset;
		TrackViewRedrawDontSetClip(View,View->XLoc,View->Width);
	}


/* try to select a single note, if there is one, at the specified mouse location */
void										TrackViewTrySingleNoteSelection(TrackViewRec* View,
													OrdType X, OrdType Y)
	{
		NoteObjectRec*				Note;
		MyBoolean							CommandFlag;
		long									FrameIndex;

		CheckPtrExistence(View);
		X -= View->XLoc;
		Y -= View->YLoc;
		Note = TrackDisplayGetUnderlyingNote(View->Schedule,TrackDisplayGetTrackIndex(
			View->Schedule,View->TrackObj),&CommandFlag,X + View->PixelIndent,&FrameIndex);
		if (Note != NIL)
			{
				if (CommandFlag)
					{
						View->SelectionMode = eTrackViewSingleCommandSelection;
					}
				 else
					{
						View->SelectionMode = eTrackViewSingleNoteSelection;
					}
				View->SelectedNote = Note;
				View->SelectedNoteFrame = FrameIndex;
			}
		 else
			{
				View->SelectionMode = eTrackViewNoSelection;
				View->InsertionPointIndex = FrameIndex;
				ERROR(View->InsertionPointIndex > TrackObjectGetNumFrames(View->TrackObj),
					PRERR(ForceAbort,"TrackViewTrySingleNoteSelection:  insertion point beyond end of track"));
			}
		TrackViewUndrawCursorBar(View);
		TrackViewRedrawAll(View);
	}


/* is the current selection a single note? */
MyBoolean								TrackViewIsASingleNoteSelected(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return (View->SelectionMode == eTrackViewSingleNoteSelection);
	}


/* is the current selection a single command? */
MyBoolean								TrackViewIsASingleCommandSelected(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return (View->SelectionMode == eTrackViewSingleCommandSelection);
	}


/* is a range of frames selected? */
MyBoolean								TrackViewIsARangeSelected(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return (View->SelectionMode == eTrackViewRangeSelection);
	}


/* is nothing selected, and thus there is an insertion point? */
MyBoolean								TrackViewIsThereInsertionPoint(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return (View->SelectionMode == eTrackViewNoSelection);
	}


/* get the frame index of the insertion point.  it is an error if there is no */
/* valid insertion point */
long										TrackViewGetInsertionPointIndex(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		ERROR(View->SelectionMode != eTrackViewNoSelection,PRERR(ForceAbort,
			"TrackViewGetInsertionPointIndex:  there is no valid insertion point"));
		ERROR(View->InsertionPointIndex > TrackObjectGetNumFrames(View->TrackObj),
			PRERR(ForceAbort,"TrackViewGetInsertionPointIndex:  insertion point beyond end of track"));
		return View->InsertionPointIndex;
	}


/* get the beginning of a selection range.  it is an error if a range is not selected */
long										TrackViewGetRangeStart(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		ERROR(View->SelectionMode != eTrackViewRangeSelection,PRERR(ForceAbort,
			"TrackViewGetRangeStart:  there is no valid range selection"));
		return View->RangeSelectStart;
	}


/* get the frame after the end of a selection range.  it is an error if there is */
/* no selected range. */
long										TrackViewGetRangeEndPlusOne(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		ERROR(View->SelectionMode != eTrackViewRangeSelection,PRERR(ForceAbort,
			"TrackViewGetRangeEndPlusOne:  there is no valid range selection"));
		return View->RangeSelectEnd;
	}


/* get the frame number of a single note or command selection */
long										TrackViewGetSingleNoteSelectionFrameNumber(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		ERROR(!TrackViewIsASingleNoteSelected(View)
			&& !TrackViewIsASingleCommandSelected(View),PRERR(ForceAbort,
			"TrackViewGetSingleNoteSelectionFrameNumber:  no single note/command selection"));
		return View->SelectedNoteFrame;
	}


/* delete the selected single note or command.  it is an error if no single note */
/* or command is selected */
void										TrackViewDeleteSingleNoteOrCommand(TrackViewRec* View)
	{
		FrameObjectRec*				Frame;
		long									Limit;
		long									Scan;

		CheckPtrExistence(View);
		TrackViewFlushUndoInfo(View);
		ERROR((View->SelectionMode != eTrackViewSingleNoteSelection)
			&& (View->SelectionMode != eTrackViewSingleCommandSelection),PRERR(ForceAbort,
			"TrackViewDeleteSingleNote:  no single note is selected"));
		if (!TrackViewBuildFullUndoBackingStore(View))
			{
				switch (AskYesNoCancel("There is not enough disk space available to preserve "
					"undo information.  Continue?",NIL,"Continue","Cancel",NIL))
					{
						default:
							EXECUTE(PRERR(ForceAbort,
								"TrackViewDeleteSingleNoteOrCommand:  bad value from AskYesNoCancel"));
							break;
						case eYes:
							break;
						case eNo:
							return; /* abort now */
					}
			}
		Frame = TrackObjectGetFrame(View->TrackObj,View->SelectedNoteFrame);
		CheckPtrExistence(Frame);
		Limit = NumNotesInFrame(Frame);
		for (Scan = 0; Scan < Limit; Scan += 1)
			{
				if (View->SelectedNote == GetNoteFromFrame(Frame,Scan))
					{
						/* we found the note to delete */
						/* zap ties to this note */
						TrackObjectNullifyTies(View->TrackObj,View->SelectedNote);
						/* indicate that stuff needs to be redrawn */
						TrackObjectAltered(View->TrackObj,View->SelectedNoteFrame);
						/* now do the actual deletion */
						if (NumNotesInFrame(Frame) == 1)
							{
								/* since this is the only note in the frame, we'll just delete the */
								/* whole frame. */
								TrackObjectDeleteFrameRun(View->TrackObj,View->SelectedNoteFrame,1);
							}
						 else
							{
								/* there are other notes in the frame, so just delete this one note */
								if (!DeleteNoteFromFrame(Frame,Scan))
									{
										/* failed */
										return;
									}
								DisposeNote(View->SelectedNote);
							}
						View->SelectionMode = eTrackViewNoSelection;
						View->InsertionPointIndex = View->SelectedNoteFrame;
						ERROR(View->InsertionPointIndex > TrackObjectGetNumFrames(View->TrackObj),
							PRERR(ForceAbort,"TrackViewDeleteSingleNote:  insertion point beyond end of track"));
						EXECUTE(View->SelectedNote = (NoteObjectRec*)0x81818181;)
						EXECUTE(View->SelectedNoteFrame = -1;)
						/* redrawing everything is overkill and should be fixed */
						TrackViewRedrawAll(View);
						return;
					}
			}
		EXECUTE(PRERR(AllowResume,"TrackViewDeleteSingleNote:  couldn't find the note"));
	}


/* add a track to the greyed-out background display */
MyBoolean								TrackViewAddBackgroundTrack(TrackViewRec* View,
													struct TrackObjectRec* TrackObj)
	{
		MyBoolean							Result;
		long									FrameIndex;
		MyBoolean							OnAFrameFlag;

		CheckPtrExistence(View);
		CheckPtrExistence(TrackObj);
		(void)TrackDisplayPixelToIndex(View->Schedule,View->TrackObj,View->PixelIndent,
			&OnAFrameFlag,&FrameIndex);
		Result = AddTrackToDisplaySchedule(View->Schedule,TrackObj);
		if (FrameIndex < TrackObjectGetNumFrames(View->TrackObj))
			{
				(void)TrackDisplayIndexToPixel(View->Schedule,0,FrameIndex,&(View->PixelIndent));
			}
		 else
			{
				View->PixelIndent = 0;
			}
		TrackViewRedrawAll(View);
		return Result;
	}


/* remove a track from the greyed-out background display */
void										TrackViewRemoveBackgroundTrack(TrackViewRec* View,
													struct TrackObjectRec* TrackObj)
	{
		long									FrameIndex;
		MyBoolean							OnAFrameFlag;

		CheckPtrExistence(View);
		CheckPtrExistence(TrackObj);
		(void)TrackDisplayPixelToIndex(View->Schedule,View->TrackObj,View->PixelIndent,
			&OnAFrameFlag,&FrameIndex);
		DeleteTrackFromDisplaySchedule(View->Schedule,TrackObj);
		if (FrameIndex < TrackObjectGetNumFrames(View->TrackObj))
			{
				(void)TrackDisplayIndexToPixel(View->Schedule,0,FrameIndex,&(View->PixelIndent));
			}
		 else
			{
				View->PixelIndent = 0;
			}
		TrackViewRedrawAll(View);
	}


/* present a dialog box for editing the selected object's attributes.  it is an */
/* error if there is no single note or command selected. */
void										TrackViewEditSingleSelectionAttributes(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		TrackViewFlushUndoInfo(View);
		ERROR(!TrackViewIsASingleNoteSelected(View)
			&& !TrackViewIsASingleCommandSelected(View),PRERR(ForceAbort,
			"TrackViewEditSingleSelectionAttributes:  no singly select object"));
		EditNoteOrCommandAttributes(View->SelectedNote,View->TrackObj);
		ForceFrameLengthChange(TrackObjectGetFrame(View->TrackObj,View->SelectedNoteFrame));
	}


/* set a tie from the currently selected note to the note at the specified position. */
/* it is an error if there is no single note selected. */
void										TrackViewSetTieOnNote(TrackViewRec* View, OrdType X, OrdType Y)
	{
		MyBoolean							ActuallyOnAFrame;
		long									Index;
		long									DestinationFrameIndex;
		NoteObjectRec*				DestinationNote;
		MyBoolean							CommandFlag;

		CheckPtrExistence(View);
		TrackViewFlushUndoInfo(View);
		ERROR(View->SelectionMode != eTrackViewSingleNoteSelection,PRERR(ForceAbort,
			"TrackViewSetTieOnNote:  single note selection is false."));
		X -= View->XLoc;
		Y -= View->YLoc;
		/* figure out where we are supposed to put this note. */
		if (!TrackDisplayPixelToIndex(View->Schedule,View->TrackObj,X + View->PixelIndent,
			&ActuallyOnAFrame,&Index))
			{
				return;
			}

		/* if they didn't click on anything, then cancel the tie */
		if (!ActuallyOnAFrame)
			{
				goto DeleteTheTiePoint;
			}

		/* get the destination note */
		DestinationNote = TrackDisplayGetUnderlyingNote(View->Schedule,
			TrackDisplayGetTrackIndex(View->Schedule,View->TrackObj),&CommandFlag,
			X + View->PixelIndent,&DestinationFrameIndex);

		/* change the data */
		if ((DestinationFrameIndex <= View->SelectedNoteFrame) || (DestinationNote == NIL))
			{
				/* delete the tie */
			 DeleteTheTiePoint:
				PutNoteTieTarget(View->SelectedNote,NIL);
				TrackObjectAltered(View->TrackObj,View->SelectedNoteFrame);
			}
		 else
			{
				/* create the tie */
				/* patch the tie flag thing */
				PutNoteTieTarget(View->SelectedNote,DestinationNote);
				TrackObjectAltered(View->TrackObj,View->SelectedNoteFrame);
				/* change selection */
				View->SelectedNote = DestinationNote;
				View->SelectedNoteFrame = DestinationFrameIndex;
			}

		/* redraw */
		TrackViewRedrawAll(View);
		View->CursorBarIsVisible = False; /* temporary */
	}


/* select a range of frames. */
void										TrackViewSetRangeSelection(TrackViewRec* View, long StartFrame,
													long EndFramePlusOne)
	{
		CheckPtrExistence(View);
		ERROR(StartFrame >= EndFramePlusOne,PRERR(ForceAbort,
			"TrackViewSetRangeSelection:  frame boundaries are invalid"));
		ERROR(StartFrame < 0,PRERR(ForceAbort,
			"TrackViewSetRangeSelection:  start frame is before beginning of track"));
		ERROR(EndFramePlusOne > TrackObjectGetNumFrames(View->TrackObj),PRERR(ForceAbort,
			"TrackViewSetRangeSelection:  end frame is after end of track"));
		View->SelectionMode = eTrackViewRangeSelection;
		View->RangeSelectStart = StartFrame;
		View->RangeSelectEnd = EndFramePlusOne;
		/* redraw with the selection changes */
		TrackViewRedrawAll(View);
		View->CursorBarIsVisible = False; /* temporary */
	}


/* set an insertion point at the specified frame index */
void										TrackViewSetInsertionPoint(TrackViewRec* View, long FrameIndex)
	{
		CheckPtrExistence(View);
		ERROR((FrameIndex < 0) || (FrameIndex > TrackObjectGetNumFrames(View->TrackObj)),
			PRERR(ForceAbort,"TrackViewSetInsertionPoint:  index is out of range"));
		View->SelectionMode = eTrackViewNoSelection;
		View->InsertionPointIndex = FrameIndex;
		/* redraw with the selection changes */
		TrackViewRedrawAll(View);
		View->CursorBarIsVisible = False; /* temporary */
	}


/* do a mouse down, which may select things.  ExtendSelection is true for shift-key */
/* type selection extending */
void										TrackViewDoMouseDown(TrackViewRec* View, OrdType XLoc,
													OrdType YLoc, MyBoolean ExtendSelection,
													void (*ScrollCallback)(void* Refcon), void* Refcon)
	{
		MyBoolean							LandedOnAFrame;
		long									ClickFrameIndex;
		long									BaseSelectStart;
		long									BaseSelectEnd;
		long									PreviousSelectStart;
		long									PreviousSelectEnd;

		CheckPtrExistence(View);

		/* if we are NOT extending the selection, then we need to set the insertion */
		/* point to the specified location */
		if (!ExtendSelection || ((View->SelectionMode != eTrackViewNoSelection)
			&& (View->SelectionMode != eTrackViewRangeSelection)))
			{
				if (!TrackDisplayPixelToIndex(View->Schedule,View->TrackObj,
					XLoc - View->XLoc + View->PixelIndent,&LandedOnAFrame,&ClickFrameIndex))
					{
						return;
					}
				ERROR((ClickFrameIndex < 0) || (ClickFrameIndex
					> TrackObjectGetNumFrames(View->TrackObj)),PRERR(ForceAbort,
					"TrackViewDoMouseDown:  click frame index is beyond the bounds of the track"));
				View->SelectionMode = eTrackViewNoSelection;
				View->InsertionPointIndex = ClickFrameIndex;
				ERROR(View->InsertionPointIndex > TrackObjectGetNumFrames(View->TrackObj),
					PRERR(ForceAbort,"TrackViewDoMouseDown:  insertion point beyond end of track"));
			}
		ERROR((View->SelectionMode != eTrackViewNoSelection) && (View->SelectionMode
			!= eTrackViewRangeSelection),PRERR(ForceAbort,
			"TrackViewDoMouseDown:  bad selection mode"));

		/* now figure out where the existing selection boundaries are */
		if (View->SelectionMode == eTrackViewRangeSelection)
			{
				BaseSelectStart = View->RangeSelectStart;
				BaseSelectEnd = View->RangeSelectEnd;
			}
		 else
			{
				BaseSelectStart = View->InsertionPointIndex;
				BaseSelectEnd = View->InsertionPointIndex;
			}
		PreviousSelectStart = BaseSelectStart;
		PreviousSelectEnd = BaseSelectEnd;
		if (BaseSelectStart < BaseSelectEnd)
			{
				TrackViewSetRangeSelection(View,BaseSelectStart,BaseSelectEnd);
			}
		 else
			{
				TrackViewSetInsertionPoint(View,BaseSelectStart);
			}

		/* enter the selection tracking loop */
		do
			{
				long									NewSelectStart;
				long									NewSelectEnd;

				/* scroll if necessary */
				if (XLoc - View->XLoc < 0)
					{
						/* scroll left */
						TrackViewSetNewHorizontalOffset(View,TrackViewGetHorizontalOffset(
							View) - (1 * GetTrackViewWidth(View)) / 8);
						ScrollCallback(Refcon);
					}
				if (XLoc - View->XLoc >= View->Width)
					{
						/* scroll right */
						TrackViewSetNewHorizontalOffset(View,TrackViewGetHorizontalOffset(
							View) + (1 * GetTrackViewWidth(View)) / 8);
						ScrollCallback(Refcon);
					}
				if (YLoc - View->YLoc < 0)
					{
						/* scroll up */
						TrackViewSetNewVerticalOffset(View,TrackViewGetVerticalOffset(
							View) - (1 * GetTrackViewHeight(View)) / 8);
						ScrollCallback(Refcon);
					}
				if (YLoc - View->YLoc >= View->Height)
					{
						/* scroll down */
						TrackViewSetNewVerticalOffset(View,TrackViewGetVerticalOffset(
							View) + (1 * GetTrackViewHeight(View)) / 8);
						ScrollCallback(Refcon);
					}

				/* figure out where the thang goes now */
				if (!TrackDisplayPixelToIndex(View->Schedule,View->TrackObj,
					XLoc - View->XLoc + View->PixelIndent,&LandedOnAFrame,&ClickFrameIndex))
					{
						return;
					}
				ERROR((ClickFrameIndex < 0) || (ClickFrameIndex
					> TrackObjectGetNumFrames(View->TrackObj)),PRERR(ForceAbort,
					"TrackViewDoMouseDown:  click frame index is beyond the bounds of the track"));
				NewSelectStart = BaseSelectStart;
				NewSelectEnd = BaseSelectEnd;
				if (ClickFrameIndex < NewSelectStart)
					{
						NewSelectStart = ClickFrameIndex;
					}
				if (ClickFrameIndex > NewSelectEnd)
					{
						NewSelectEnd = ClickFrameIndex;
					}
				if ((NewSelectStart != PreviousSelectStart)
					|| (NewSelectEnd != PreviousSelectEnd))
					{
						PreviousSelectStart = NewSelectStart;
						PreviousSelectEnd = NewSelectEnd;
						if (NewSelectStart < NewSelectEnd)
							{
								TrackViewSetRangeSelection(View,NewSelectStart,NewSelectEnd);
							}
						 else
							{
								TrackViewSetInsertionPoint(View,NewSelectStart);
							}
					}
			} while (eMouseUp != GetAnEvent(&XLoc,&YLoc,NIL,NIL,NIL,NIL));
	}


/* set the range selection to the entire track */
void										TrackViewSelectAll(TrackViewRec* View)
	{
		long									NumFrames;

		CheckPtrExistence(View);
		NumFrames = TrackObjectGetNumFrames(View->TrackObj);
		if (NumFrames > 0)
			{
				View->SelectionMode = eTrackViewRangeSelection;
				View->RangeSelectStart = 0;
				View->RangeSelectEnd = NumFrames;
			}
		 else
			{
				View->SelectionMode = eTrackViewNoSelection;
				View->InsertionPointIndex = 0;
			}
		/* redraw with the selection changes */
		TrackViewRedrawAll(View);
		View->CursorBarIsVisible = False; /* temporary */
	}


/* delete the selected range.  it is an error if there is no selected range. it */
/* returns False if it fails.  undo information is automatically maintained */
MyBoolean								TrackViewDeleteRangeSelection(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		ERROR(View->SelectionMode != eTrackViewRangeSelection,PRERR(ForceAbort,
			"TrackViewDeleteRangeSelection:  called with no range selected"));
		TrackViewFlushUndoInfo(View);
		if (!TrackViewBuildFullUndoBackingStore(View))
			{
				switch (AskYesNoCancel("There is not enough disk space available to preserve "
					"undo information.  Continue?",NIL,"Continue","Cancel",NIL))
					{
						default:
							EXECUTE(PRERR(ForceAbort,
								"TrackViewDeleteSingleNoteOrCommand:  bad value from AskYesNoCancel"));
							break;
						case eYes:
							break;
						case eNo:
							return False; /* abort now */
					}
			}

		/* delete the stuff */
		TrackObjectDeleteFrameRun(View->TrackObj,View->RangeSelectStart,
			View->RangeSelectEnd - View->RangeSelectStart);
		View->SelectionMode = eTrackViewNoSelection;
		View->InsertionPointIndex = View->RangeSelectStart;
		ERROR(View->InsertionPointIndex > TrackObjectGetNumFrames(View->TrackObj),
			PRERR(ForceAbort,"TrackViewDeleteRangeSelection:  insertion point beyond end of track"));

		/* redraw with changes */
		TrackViewRedrawAll(View);
		View->CursorBarIsVisible = False; /* temporary */
		TrackViewShowSelection(View);

		return True;
	}


/* attempt to paste notes into the track.  returns False if it fails.  undo */
/* information is automatically maintained. */
MyBoolean								TrackViewAttemptPaste(TrackViewRec* View)
	{
		char*									Scrap;
		MyBoolean							TotalSuccessFlag = False;

		CheckPtrExistence(View);
		TrackViewFlushUndoInfo(View);
		if (View->SelectionMode == eTrackViewRangeSelection)
			{
				if (!TrackViewDeleteRangeSelection(View))
					{
						return False;
					}
			}
		ERROR(View->SelectionMode != eTrackViewNoSelection,PRERR(ForceAbort,
			"TrackViewAttemptPaste:  no insertion point"));
		Scrap = GetCopyOfScrap();
		if (Scrap != NIL)
			{
				if ((PtrSize(Scrap) >= sizeof(MAGICSCRAPSTRING))
					&& MemEqu(MAGICSCRAPSTRING,Scrap,sizeof(MAGICSCRAPSTRING)))
					{
						FileSpec*							TempFileLoc;

						TempFileLoc = NewTempFileSpec(CODE4BYTES('\?','\?','\?','\?'),
							CODE4BYTES('\?','\?','\?','\?'));
						if (TempFileLoc != NIL)
							{
								FileType*							FileDesc;

								if (OpenFile(TempFileLoc,&FileDesc,eReadAndWrite))
									{
										if (0 == WriteToFile(FileDesc,Scrap + sizeof(MAGICSCRAPSTRING),
											PtrSize(Scrap) - sizeof(MAGICSCRAPSTRING)))
											{
												if (SetFilePosition(FileDesc,0))
													{
														BufferedInputRec*		BuffFile;

														BuffFile = NewBufferedInput(FileDesc);
														if (BuffFile != NIL)
															{
																ArrayRec*						FrameArray EXECUTE(= (ArrayRec*)0x81818181);

																if (eFileLoadNoError == ReadNoteVector(&FrameArray,BuffFile))
																	{
																		long								Scan;

																		CheckPtrExistence(FrameArray);
																		Scan = 0;
																		while (ArrayGetLength(FrameArray) > 0)
																			{
																				FrameObjectRec*			Thing;

																				Thing = (FrameObjectRec*)ArrayGetElement(
																					FrameArray,0);
																				if (!TrackObjectInsertFrame(View->TrackObj,
																					Scan + View->InsertionPointIndex,Thing))
																					{
																						long								Limit;
																						long								Scan2;

																						/* delete all the ones we added */
																						TrackObjectDeleteFrameRun(View->TrackObj,
																							View->InsertionPointIndex,Scan);
																						/* delete the ones we didn't add */
																						Limit = ArrayGetLength(FrameArray);
																						for (Scan2 = 0; Scan2 < Limit; Scan2 += 1)
																							{
																								DisposeFrameAndContents(
																									(FrameObjectRec*)ArrayGetElement(
																									FrameArray,Scan2));
																							}
																						goto ExitPoint;
																					}
																				ArrayDeleteElement(FrameArray,0);
																				Scan += 1;
																			}
																		/* remember undo information, if the delete */
																		/* range thing above didn't already set it up */
																		if (View->UndoState == eTrackViewNoUndo)
																			{
																				View->UndoState = eTrackViewUndoRangeInsertion;
																				View->UndoRangeStartIndex = View->InsertionPointIndex;
																				View->UndoRangeElemCount = Scan;
																			}
																		/* update display parameters */
																		View->InsertionPointIndex += Scan;
																		ERROR(View->InsertionPointIndex
																			> TrackObjectGetNumFrames(View->TrackObj),
																			PRERR(ForceAbort,"TrackViewAttemptPaste:  "
																			"insertion point beyond end of track"));
																		TotalSuccessFlag = True;
																		TrackViewRedrawAll(View);
																		View->CursorBarIsVisible = False; /* temporary */
																		TrackViewShowSelection(View);
																		/* jump here when something bad happens */
																	 ExitPoint:
																		DisposeArray(FrameArray);
																	}
																EndBufferedInput(BuffFile);
															}
													}
											}
										CloseFile(FileDesc);
									}
								DeleteFile(TempFileLoc);
								DisposeFileSpec(TempFileLoc);
							}
					}
				ReleasePtr(Scrap);
			}
		return TotalSuccessFlag;
	}


/* copy the selected range to the clipboard.  it is an error if there is no selected */
/* range.  it returns True if successful. */
MyBoolean								TrackViewCopyRangeSelection(TrackViewRec* View)
	{
		MyBoolean							TotalSuccessFlag;
		ArrayRec*							CopyOfSelection;

		CheckPtrExistence(View);
		ERROR(View->SelectionMode != eTrackViewRangeSelection,PRERR(ForceAbort,
			"TrackViewCopyRangeSelection:  called with no range selected"));
		TrackViewFlushUndoInfo(View);
		CopyOfSelection = TrackObjectCopyFrameRun(View->TrackObj,View->RangeSelectStart,
			View->RangeSelectEnd - View->RangeSelectStart);
		if (CopyOfSelection != NIL)
			{
				long									Limit;
				long									Scan;
				FileSpec*							TempFileLoc;

				TempFileLoc = NewTempFileSpec(CODE4BYTES('\?','\?','\?','\?'),
					CODE4BYTES('\?','\?','\?','\?'));
				if (TempFileLoc != NIL)
					{
						FileType*							FileDesc;

						if (OpenFile(TempFileLoc,&FileDesc,eReadAndWrite))
							{
								BufferedOutputRec*		BuffOut;

								BuffOut = NewBufferedOutput(FileDesc);
								if (BuffOut != NIL)
									{
										MyBoolean							WriteSucceeded = False;

										if (WriteBufferedOutput(BuffOut,sizeof(MAGICSCRAPSTRING),
											MAGICSCRAPSTRING))
											{
												if (eFileLoadNoError == WriteNoteVector(CopyOfSelection,BuffOut))
													{
														WriteSucceeded = True;
													}
											}
										if (EndBufferedOutput(BuffOut) && WriteSucceeded)
											{
												if (SetFilePosition(FileDesc,0))
													{
														char*									Scrap;

														Scrap = AllocPtrCanFail(GetFileLength(FileDesc),
															"TrackViewCopyRangeSelection:  scrap temp");
														if (Scrap != NIL)
															{
																if (0 == ReadFromFile(FileDesc,Scrap,PtrSize(Scrap)))
																	{
																		if (SetScrapToThis(Scrap))
																			{
																				TotalSuccessFlag = True;
																			}
																	}
																ReleasePtr(Scrap);
															}
													}
											}
									}
								CloseFile(FileDesc);
							}
						DeleteFile(TempFileLoc);
						DisposeFileSpec(TempFileLoc);
					}
				Limit = ArrayGetLength(CopyOfSelection);
				for (Scan = 0; Scan < Limit; Scan += 1)
					{
						DisposeFrameAndContents((FrameObjectRec*)ArrayGetElement(
							CopyOfSelection,Scan));
					}
				DisposeArray(CopyOfSelection);
			}
		return TotalSuccessFlag;
	}


/* cut the range selection (copy to clipboard, then delete).  returns True if */
/* successful.  it is an error if there is no range selected.  automatically */
/* updates undo information. */
MyBoolean								TrackViewCutRangeSelection(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		TrackViewFlushUndoInfo(View);
		if (TrackViewCopyRangeSelection(View))
			{
				if (TrackViewDeleteRangeSelection(View))
					{
						return True;
					}
			}
		return False;
	}


/* show the selection or insertion point by scrolling the view area */
void										TrackViewShowSelection(TrackViewRec* View)
	{
		long									CenteringIndex;
		long									PixelIndex;

		CheckPtrExistence(View);
		switch (View->SelectionMode)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"TrackViewShowSelection:  illegal selection mode"));
					break;
				case eTrackViewNoSelection:
					CenteringIndex = View->InsertionPointIndex;
					break;
				case eTrackViewSingleNoteSelection:
				case eTrackViewSingleCommandSelection:
					CenteringIndex = View->SelectedNoteFrame;
					break;
				case eTrackViewRangeSelection:
					CenteringIndex = View->RangeSelectStart;
					break;
			}
		if (CenteringIndex < TrackObjectGetNumFrames(View->TrackObj))
			{
				if (!TrackDisplayIndexToPixel(View->Schedule,0/*first track*/,
					CenteringIndex,&PixelIndex))
					{
						return;
					}
			}
		else if (TrackObjectGetNumFrames(View->TrackObj) > 0)
			{
				if (!TrackDisplayIndexToPixel(View->Schedule,0/*first track*/,
					TrackObjectGetNumFrames(View->TrackObj) - 1,&PixelIndex))
					{
						return;
					}
			}
		else
			{
				/* no frames at all. */
				PixelIndex = 0;
			}
		PixelIndex -= View->Width / 2;
		if (PixelIndex < 0)
			{
				PixelIndex = 0;
			}
		TrackViewSetNewHorizontalOffset(View,PixelIndex);
	}


/* find out if we can undo the last operation */
MyBoolean								TrackViewCanWeUndo(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		return (eTrackViewNoUndo != View->UndoState);
	}


/* dispose of all undo information. */
void										TrackViewFlushUndoInfo(TrackViewRec* View)
	{
		CheckPtrExistence(View);
		switch (View->UndoState)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"TrackViewFlushUndoInfo:  bad undo state"));
					break;
				case eTrackViewNoUndo:
				case eTrackViewUndoNoteInsertion:
				case eTrackViewUndoRangeInsertion:
					break;
				case eTrackViewUndoFromFile:
					CloseFile(View->UndoTempFileDesc);
					DeleteFile(View->UndoTempFileLoc);
					DisposeFileSpec(View->UndoTempFileLoc);
					break;
			}
		View->UndoState = eTrackViewNoUndo;
	}


/* save the undo information for the track.  returns False if it fails. */
MyBoolean								TrackViewBuildFullUndoBackingStore(TrackViewRec* View)
	{
		BufferedOutputRec*		Output;

		CheckPtrExistence(View);
		TrackViewFlushUndoInfo(View);
		ERROR(View->UndoState != eTrackViewNoUndo,PRERR(ForceAbort,
			"TrackViewBuildFullUndoBackingStore:  expected undo information to be purged"));
		/* make backups (see, even my programs do it!) */
		View->UndoTempFileLoc = NewTempFileSpec(CODE4BYTES('\?','\?','\?','\?'),
			CODE4BYTES('\?','\?','\?','\?'));
		if (View->UndoTempFileLoc == NIL)
			{
			 FailurePoint1:
				return False;
			}
		if (!OpenFile(View->UndoTempFileLoc,&(View->UndoTempFileDesc),eReadAndWrite))
			{
			 FailurePoint2:
				DeleteFile(View->UndoTempFileLoc);
				DisposeFileSpec(View->UndoTempFileLoc);
				goto FailurePoint1;
			}
		Output = NewBufferedOutput(View->UndoTempFileDesc);
		if (Output == NIL)
			{
			 FailurePoint3:
				CloseFile(View->UndoTempFileDesc);
				goto FailurePoint2;
			}
		if (!TrackObjectWriteNotesOutToFile(View->TrackObj,Output))
			{
			 FailurePoint3a:
				EndBufferedOutput(Output);
				goto FailurePoint3;
			}
		if (!EndBufferedOutput(Output))
			{
			 FailurePoint4:
				goto FailurePoint3;
			}
		View->UndoState = eTrackViewUndoFromFile;
		return True;
	}


/* undo the last operation.  if it fails, well... */
void										TrackViewUndo(TrackViewRec* View)
	{
		UndoChoices						LocalUndoState;
		long									LocalUndoNoteInsertionFrameIndex;
		long									LocalUndoNoteInsertionNoteIndex;
		long									LocalUndoRangeStartIndex;
		long									LocalUndoRangeElemCount;
		FileSpec*							LocalUndoTempFileLoc;
		FileType*							LocalUndoTempFileDesc;

		CheckPtrExistence(View);
		/* first, make a backup of the undo information, so we can undo the undo */
		LocalUndoState = View->UndoState;
		LocalUndoNoteInsertionFrameIndex = View->UndoNoteInsertionFrameIndex;
		LocalUndoNoteInsertionNoteIndex = View->UndoNoteInsertionNoteIndex;
		LocalUndoRangeStartIndex = View->UndoRangeStartIndex;
		LocalUndoRangeElemCount = View->UndoRangeElemCount;
		LocalUndoTempFileLoc = View->UndoTempFileLoc;
		LocalUndoTempFileDesc = View->UndoTempFileDesc;
		/* reset the undo state */
		View->UndoState = eTrackViewNoUndo;
		/* build backup for our undo */
		if (!TrackViewBuildFullUndoBackingStore(View))
			{
				switch (AskYesNoCancel("There is not enough disk space available to preserve "
					"undo information.  Continue?",NIL,"Continue","Cancel",NIL))
					{
						default:
							EXECUTE(PRERR(ForceAbort,
								"TrackViewDeleteSingleNoteOrCommand:  bad value from AskYesNoCancel"));
							break;
						case eYes:
							/* continue */
							break;
						case eNo:
							/* abort now -- restore the undo information */
							View->UndoState = LocalUndoState;
							View->UndoNoteInsertionFrameIndex = LocalUndoNoteInsertionFrameIndex;
							View->UndoNoteInsertionNoteIndex = LocalUndoNoteInsertionNoteIndex;
							View->UndoRangeStartIndex = LocalUndoRangeStartIndex;
							View->UndoRangeElemCount = LocalUndoRangeElemCount;
							View->UndoTempFileLoc = LocalUndoTempFileLoc;
							View->UndoTempFileDesc = LocalUndoTempFileDesc;
							return;
					}
			}
		/* do the undoing */
		switch (LocalUndoState)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"TrackViewUndo:  bad undo state"));
					break;
				case eTrackViewUndoNoteInsertion:
					/* delete the note */
					{
						FrameObjectRec*			Frame;

						Frame = TrackObjectGetFrame(View->TrackObj,LocalUndoNoteInsertionFrameIndex);
						if (NumNotesInFrame(Frame) == 1)
							{
								/* delete whole frame */
								TrackObjectDeleteFrameRun(View->TrackObj,
									LocalUndoNoteInsertionFrameIndex,1);
								/* reset edit point position */
								View->InsertionPointIndex = LocalUndoNoteInsertionFrameIndex;
								View->SelectionMode = eTrackViewNoSelection;
							}
						 else
							{
								NoteObjectRec*			Note;

								/* just delete a note from the frame */
								Note = GetNoteFromFrame(Frame,LocalUndoNoteInsertionNoteIndex);
								if (!IsItACommand(Note))
									{
										TrackObjectNullifyTies(View->TrackObj,Note);
									}
								DeleteNoteFromFrame(Frame,LocalUndoNoteInsertionNoteIndex);
								/* reset edit point position */
								View->InsertionPointIndex = LocalUndoNoteInsertionNoteIndex;
								View->SelectionMode = eTrackViewNoSelection;
							}
					}
					break;
				case eTrackViewUndoRangeInsertion:
					TrackObjectDeleteFrameRun(View->TrackObj,LocalUndoRangeStartIndex,
						LocalUndoRangeElemCount);
					/* reset edit point position */
					View->InsertionPointIndex = LocalUndoRangeStartIndex;
					View->SelectionMode = eTrackViewNoSelection;
					break;
				case eTrackViewUndoFromFile:
					/* recover all the notes from the file */
					if (SetFilePosition(LocalUndoTempFileDesc,0))
						{
							BufferedInputRec*			Input;

							Input = NewBufferedInput(LocalUndoTempFileDesc);
							if (Input != NIL)
								{
									if (TrackObjectRecoverNotesFromFile(View->TrackObj,Input))
										{
											/* successful */
										}
									 else
										{
											AlertWarning("Unable to completely restore previous stuff.",NIL);
										}
									EndBufferedInput(Input);
								}
						}
					/* reset edit point position */
					if (View->InsertionPointIndex > TrackObjectGetNumFrames(View->TrackObj))
						{
							View->InsertionPointIndex = TrackObjectGetNumFrames(View->TrackObj);
						}
					View->SelectionMode = eTrackViewNoSelection;
					/* delete crud */
					CloseFile(LocalUndoTempFileDesc);
					DeleteFile(LocalUndoTempFileLoc);
					DisposeFileSpec(LocalUndoTempFileLoc);
					break;
			}
		TrackViewRedrawAll(View);
		View->CursorBarIsVisible = False; /* temporary */
	}


/* transpose the selection by adding AddHalfSteps to the pitch of each selected note */
/* AddHalfSteps can be negative.  it is an error if there is no single note selection */
/* or no range selection */
void										TrackViewTransposeSelection(TrackViewRec* View, long AddHalfSteps)
	{
		CheckPtrExistence(View);
		TrackViewFlushUndoInfo(View);
		if (TrackViewIsASingleNoteSelected(View))
			{
				long									NewPitch;

				NewPitch = GetNotePitch(View->SelectedNote) + AddHalfSteps;
				if (NewPitch < 0)
					{
						NewPitch = 0;
					}
				else if (NewPitch > NUMNOTES - 1)
					{
						NewPitch = NUMNOTES - 1;
					}
				PutNotePitch(View->SelectedNote,NewPitch);
				switch (NewPitch % 12)
					{
						case 0: /* C */
						case 2: /* D */
						case 4: /* E */
						case 5: /* F */
						case 7: /* G */
						case 9: /* A */
						case 11: /* B */
							PutNoteFlatOrSharpStatus(View->SelectedNote,0);
							break;
						case 1: /* C# / Db */
						case 3: /* D# / Eb */
						case 6: /* F# / Gb */
						case 8: /* G# / Ab */
						case 10: /* A# / Bb */
							if (AddHalfSteps >= 0)
								{
									if (GetNoteFlatOrSharpStatus(View->SelectedNote) == eFlatModifier)
										{
											PutNoteFlatOrSharpStatus(View->SelectedNote,eFlatModifier);
										}
									 else
										{
											PutNoteFlatOrSharpStatus(View->SelectedNote,eSharpModifier);
										}
								}
							 else
								{
									if (GetNoteFlatOrSharpStatus(View->SelectedNote) == eSharpModifier)
										{
											PutNoteFlatOrSharpStatus(View->SelectedNote,eSharpModifier);
										}
									 else
										{
											PutNoteFlatOrSharpStatus(View->SelectedNote,eFlatModifier);
										}
								}
							break;
					}
			}
		else if (TrackViewIsARangeSelected(View))
			{
				long									FrameScan;

				for (FrameScan = View->RangeSelectStart; FrameScan < View->RangeSelectEnd;
					FrameScan += 1)
					{
						FrameObjectRec*				Frame;

						Frame = TrackObjectGetFrame(View->TrackObj,FrameScan);
						CheckPtrExistence(Frame);
						if (!IsThisACommandFrame(Frame))
							{
								long									NoteScan;
								long									NoteLimit;

								NoteLimit = NumNotesInFrame(Frame);
								for (NoteScan = 0; NoteScan < NoteLimit; NoteScan += 1)
									{
										NoteObjectRec*				Note;
										long									NewPitch;

										Note = GetNoteFromFrame(Frame,NoteScan);
										CheckPtrExistence(Note);
										NewPitch = GetNotePitch(Note) + AddHalfSteps;
										if (NewPitch < 0)
											{
												NewPitch = 0;
											}
										else if (NewPitch > NUMNOTES - 1)
											{
												NewPitch = NUMNOTES - 1;
											}
										PutNotePitch(Note,NewPitch);
										switch (NewPitch % 12)
											{
												case 0: /* C */
												case 2: /* D */
												case 4: /* E */
												case 5: /* F */
												case 7: /* G */
												case 9: /* A */
												case 11: /* B */
													PutNoteFlatOrSharpStatus(Note,0);
													break;
												case 1: /* C# / Db */
												case 3: /* D# / Eb */
												case 6: /* F# / Gb */
												case 8: /* G# / Ab */
												case 10: /* A# / Bb */
													if (AddHalfSteps >= 0)
														{
															if (GetNoteFlatOrSharpStatus(Note) == eFlatModifier)
																{
																	PutNoteFlatOrSharpStatus(Note,eFlatModifier);
																}
															 else
																{
																	PutNoteFlatOrSharpStatus(Note,eSharpModifier);
																}
														}
													 else
														{
															if (GetNoteFlatOrSharpStatus(Note) == eSharpModifier)
																{
																	PutNoteFlatOrSharpStatus(Note,eSharpModifier);
																}
															 else
																{
																	PutNoteFlatOrSharpStatus(Note,eFlatModifier);
																}
														}
													break;
											}
									}
							}
					}
			}
		else
			{
				EXECUTE(PRERR(ForceAbort,"TrackViewTransposeSelection:  improper selection"));
			}
		TrackViewRedrawAll(View);
		View->CursorBarIsVisible = False; /* temporary */
		TrackObjectAltered(View->TrackObj,0);
	}


/* scroll to the specified measure.  invalid measure numbers may be specified. */
void										TrackViewShowMeasure(TrackViewRec* View, long MeasureNumber)
	{
		long									FrameIndex;

		CheckPtrExistence(View);
		if (!TrackDisplayMeasureIndexToFrame(View->Schedule,MeasureNumber,&FrameIndex))
			{
				ErrorBeep();
			}
		 else
			{
				long									PixelIndex;

				if (FrameIndex < TrackObjectGetNumFrames(View->TrackObj))
					{
						if (!TrackDisplayIndexToPixel(View->Schedule,0/*first track*/,
							FrameIndex,&PixelIndex))
							{
								return;
							}
					}
				else if (TrackObjectGetNumFrames(View->TrackObj) > 0)
					{
						if (!TrackDisplayIndexToPixel(View->Schedule,0/*first track*/,
							TrackObjectGetNumFrames(View->TrackObj) - 1,&PixelIndex))
							{
								return;
							}
					}
				else
					{
						/* no frames at all. */
						PixelIndex = 0;
					}
				PixelIndex -= (View->Width / 3);
				if (PixelIndex < 0)
					{
						PixelIndex = 0;
					}
				TrackViewSetNewHorizontalOffset(View,PixelIndex);
			}
	}
