#include "global.h"
#include <utility>
#include <float.h>
#include "ScreenEdit.h"
#include "ActorUtil.h"
#include "AdjustSync.h"
#include "BackgroundUtil.h"
#include "CommonMetrics.h"
#include "Foreach.h"
#include "GameManager.h"
#include "GamePreferences.h"
#include "GameSoundManager.h"
#include "GameState.h"
#include "InputEventPlus.h"
#include "InputMapper.h"
#include "LocalizedString.h"
#include "NoteDataUtil.h"
#include "NoteSkinManager.h"
#include "NoteTypes.h"
#include "NotesWriterSM.h"
#include "PrefsManager.h"
#include "RageSoundManager.h"
#include "RageInput.h"
#include "RageLog.h"
#include "ScreenDimensions.h"
#include "ScreenManager.h"
#include "ScreenMiniMenu.h"
#include "ScreenPrompt.h"
#include "ScreenSaveSync.h"
#include "ScreenTextEntry.h"
#include "SongManager.h"
#include "SongUtil.h"
#include "StepsUtil.h"
#include "Style.h"
#include "ThemeManager.h"
#include "ThemeMetric.h"
#include "TimingData.h"
#include "Game.h"
#include "RageSoundReader.h"

static Preference<float> g_iDefaultRecordLength( "DefaultRecordLength", 4 );
static Preference<bool> g_bEditorShowBGChangesPlay( "EditorShowBGChangesPlay", true );

/** @brief How long must the button be held to generate a hold in record mode? */
const float RECORD_HOLD_SECONDS = 0.3f;

#define PLAYER_X		(SCREEN_CENTER_X)
#define PLAYER_Y		(SCREEN_CENTER_Y)
#define PLAYER_HEIGHT		(360)
#define PLAYER_Y_STANDARD	(PLAYER_Y-PLAYER_HEIGHT/2)

#define EDIT_X			(SCREEN_CENTER_X)
#define EDIT_Y			(PLAYER_Y)

#define RECORD_X		(SCREEN_CENTER_X)
#define RECORD_Y		(SCREEN_CENTER_Y)

#define PLAY_RECORD_HELP_TEXT	THEME->GetString(m_sName,"PlayRecordHelpText")
#define EDIT_HELP_TEXT		THEME->GetString(m_sName,"EditHelpText")

AutoScreenMessage( SM_UpdateTextInfo );
AutoScreenMessage( SM_BackFromMainMenu );
AutoScreenMessage( SM_BackFromAreaMenu );
AutoScreenMessage( SM_BackFromAlterMenu );
AutoScreenMessage( SM_BackFromStepsInformation );
AutoScreenMessage( SM_BackFromStepsData );
AutoScreenMessage( SM_BackFromOptions );
AutoScreenMessage( SM_BackFromSongInformation );
AutoScreenMessage( SM_BackFromBGChange );
AutoScreenMessage( SM_BackFromInsertTapAttack );
AutoScreenMessage( SM_BackFromInsertTapAttackPlayerOptions );
AutoScreenMessage( SM_BackFromAttackAtTime );
AutoScreenMessage( SM_BackFromInsertStepAttack );
AutoScreenMessage( SM_BackFromAddingModToExistingAttack );
AutoScreenMessage( SM_BackFromEditingModToExistingAttack );
AutoScreenMessage( SM_BackFromEditingAttackStart );
AutoScreenMessage( SM_BackFromEditingAttackLength );
AutoScreenMessage( SM_BackFromAddingAttackToChart );
AutoScreenMessage( SM_BackFromInsertStepAttackPlayerOptions );
AutoScreenMessage( SM_BackFromInsertCourseAttack );
AutoScreenMessage( SM_BackFromInsertCourseAttackPlayerOptions );
AutoScreenMessage( SM_BackFromCourseModeMenu );
AutoScreenMessage( SM_BackFromKeysoundTrack );
AutoScreenMessage( SM_BackFromNewKeysound );
AutoScreenMessage( SM_DoRevertToLastSave );
AutoScreenMessage( SM_DoRevertFromDisk );
AutoScreenMessage( SM_BackFromTimingDataInformation );
AutoScreenMessage( SM_BackFromDifficultyMeterChange );
AutoScreenMessage( SM_BackFromBeat0Change );
AutoScreenMessage( SM_BackFromBPMChange );
AutoScreenMessage( SM_BackFromStopChange );
AutoScreenMessage( SM_BackFromDelayChange );
AutoScreenMessage( SM_BackFromTimeSignatureChange );
AutoScreenMessage( SM_BackFromTickcountChange );
AutoScreenMessage( SM_BackFromComboChange );
AutoScreenMessage( SM_BackFromLabelChange );
AutoScreenMessage( SM_BackFromWarpChange );
AutoScreenMessage( SM_BackFromSpeedPercentChange );
AutoScreenMessage( SM_BackFromSpeedWaitChange );
AutoScreenMessage( SM_BackFromSpeedModeChange );
AutoScreenMessage( SM_BackFromScrollChange );
AutoScreenMessage( SM_BackFromFakeChange );
AutoScreenMessage( SM_DoEraseStepTiming );
AutoScreenMessage( SM_DoSaveAndExit );
AutoScreenMessage( SM_DoExit );
AutoScreenMessage( SM_SaveSuccessful );
AutoScreenMessage( SM_SaveFailed );

static const char *EditStateNames[] = {
	"Edit",
	"Record",
	"RecordPaused",
	"Playing"
};
XToString( EditState );
LuaXType( EditState );

void ScreenEdit::InitEditMappings()
{
	m_EditMappingsDeviceInput.Clear();

	// Common mappings:
	switch( EDIT_MODE.GetValue() )
	{
	case EditMode_Practice:
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_PREV_MEASURE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_UP);
		m_EditMappingsMenuButton.button[EDIT_BUTTON_SCROLL_PREV_MEASURE][0] = GAME_BUTTON_UP;
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_NEXT_MEASURE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DOWN);
		m_EditMappingsMenuButton.button[EDIT_BUTTON_SCROLL_NEXT_MEASURE][0] = GAME_BUTTON_DOWN;
		break;
	default:
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_LINE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_UP);
		m_EditMappingsMenuButton.button[EDIT_BUTTON_SCROLL_UP_LINE][0] = GAME_BUTTON_UP;
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_LINE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DOWN);
		m_EditMappingsMenuButton.button[EDIT_BUTTON_SCROLL_DOWN_LINE][0] = GAME_BUTTON_DOWN;
		break;
	}

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_PAGE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PGUP);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_PAGE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_SEMICOLON);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_PAGE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PGDN);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_PAGE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_SQUOTE);
	
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_UP_TS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_UP_TS][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_TS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PGUP);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_UP_TS][1] = DeviceInput(DEVICE_KEYBOARD, KEY_SEMICOLON);
	
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_DOWN_TS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_DOWN_TS][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_TS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PGDN);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_DOWN_TS][1] = DeviceInput(DEVICE_KEYBOARD, KEY_SQUOTE);
	
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_HOME][0] = DeviceInput(DEVICE_KEYBOARD, KEY_HOME);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_END][0] = DeviceInput(DEVICE_KEYBOARD, KEY_END);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PERIOD);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_COMMA);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SEGMENT_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_PERIOD);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SEGMENT_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SEGMENT_NEXT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
	
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SEGMENT_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_COMMA);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SEGMENT_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SEGMENT_PREV][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
	
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_SELECT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_SELECT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_LAY_SELECT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_SPACE);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_PLAY_FROM_START][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cp);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_PLAY_FROM_START][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_PLAY_FROM_START][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_PLAY_FROM_CURSOR][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cp);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_PLAY_FROM_CURSOR][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_PLAY_FROM_CURSOR][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_PLAY_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cp);

	// EditMode-specific mappings
	switch( EDIT_MODE.GetValue() )
	{
	case EditMode_Practice:
		// Left = Zoom out (1x, 2x, 4x)
		// Right = Zoom in
		m_EditMappingsDeviceInput.button   [EDIT_BUTTON_SCROLL_SPEED_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LEFT);
		m_EditMappingsDeviceInput.button   [EDIT_BUTTON_SCROLL_SPEED_UP][0]   = DeviceInput(DEVICE_KEYBOARD, KEY_RIGHT);

		// F1 = Show help popup
		m_EditMappingsDeviceInput.button   [EDIT_BUTTON_OPEN_INPUT_HELP][0]   = DeviceInput(DEVICE_KEYBOARD, KEY_F1);

		// Esc = Show Edit Menu
		m_EditMappingsDeviceInput.button   [EDIT_BUTTON_OPEN_EDIT_MENU][0]    = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
		m_EditMappingsMenuButton.button   [EDIT_BUTTON_OPEN_EDIT_MENU][0]    = GAME_BUTTON_START;
		m_EditMappingsMenuButton.button   [EDIT_BUTTON_OPEN_EDIT_MENU][1]    = GAME_BUTTON_BACK;

			
		// Escape, Enter = exit play/record
		m_PlayMappingsDeviceInput.button   [EDIT_BUTTON_RETURN_TO_EDIT][0]    = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
		m_PlayMappingsDeviceInput.button   [EDIT_BUTTON_RETURN_TO_EDIT][1]    = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
		m_PlayMappingsMenuButton.button   [EDIT_BUTTON_RETURN_TO_EDIT][1]    = GAME_BUTTON_BACK;
		return;
	case EditMode_CourseMods:
		// Left/Right = Snap to Next/Prev
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SNAP_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LEFT);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SNAP_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RIGHT);

		// v = course playback menu
		// m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_COURSE_ATTACK_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cv);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_ADD_COURSE_MODS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Co);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_COURSE_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cv);

		// F1 = Show help popup
		m_EditMappingsDeviceInput.button   [EDIT_BUTTON_OPEN_INPUT_HELP][0]   = DeviceInput(DEVICE_KEYBOARD, KEY_F1);

		// Esc = Show Edit Menu
		m_EditMappingsDeviceInput.button   [EDIT_BUTTON_OPEN_EDIT_MENU][0]    = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
		m_EditMappingsMenuButton.button   [EDIT_BUTTON_OPEN_EDIT_MENU][0]    = GAME_BUTTON_START;
		m_EditMappingsMenuButton.button   [EDIT_BUTTON_OPEN_EDIT_MENU][1]    = GAME_BUTTON_BACK;

		// Escape, Enter = exit play/record
		m_PlayMappingsDeviceInput.button   [EDIT_BUTTON_RETURN_TO_EDIT][0]    = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
		m_PlayMappingsDeviceInput.button   [EDIT_BUTTON_RETURN_TO_EDIT][1]    = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
		m_PlayMappingsMenuButton.button   [EDIT_BUTTON_RETURN_TO_EDIT][0]    = GAME_BUTTON_START;
		return;

	case EditMode_Full:
		// F4 = Show timing menu
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_TIMING_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F4);
		
		/* Don't allow F5/F6 in home mode. It breaks the "delay creation until first save" logic. */
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_PREV_STEPS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F5);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_NEXT_STEPS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F6);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_BPM_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F7);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_BPM_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F8);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_STOP_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F9);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_STOP_UP][0]  = DeviceInput(DEVICE_KEYBOARD, KEY_F10);
	
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_DELAY_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F9);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELAY_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELAY_DOWN][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_DELAY_UP][0]  = DeviceInput(DEVICE_KEYBOARD, KEY_F10);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELAY_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELAY_UP][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
			
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_OFFSET_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F11);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_OFFSET_UP][0]  = DeviceInput(DEVICE_KEYBOARD, KEY_F12);

		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAMPLE_START_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RBRACKET);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAMPLE_START_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LBRACKET);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAMPLE_LENGTH_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RBRACKET);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAMPLE_LENGTH_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAMPLE_LENGTH_UP][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAMPLE_LENGTH_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LBRACKET);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAMPLE_LENGTH_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAMPLE_LENGTH_DOWN][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);

		m_EditMappingsDeviceInput.button[EDIT_BUTTON_PLAY_SAMPLE_MUSIC][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cl);

		m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cb);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cb);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
		// m_EditMappingsDeviceInput.button[EDIT_BUTTON_ADD_STEP_MODS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cc);
		// m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_STEP_ATTACK_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cv);

		m_EditMappingsDeviceInput.button[EDIT_BUTTON_INSERT_SHIFT_PAUSES][0] = DeviceInput(DEVICE_KEYBOARD, KEY_INSERT);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_INSERT_SHIFT_PAUSES][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_INSERT_SHIFT_PAUSES][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
		m_EditMappingsDeviceInput.button[EDIT_BUTTON_DELETE_SHIFT_PAUSES][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DEL);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELETE_SHIFT_PAUSES][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_DELETE_SHIFT_PAUSES][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
		break;
	default: break;
	}

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_0][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C1);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_1][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C2);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_2][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C3);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_3][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C4);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_4][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C5);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_5][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C6);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_6][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C7);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_7][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C8);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_8][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C9);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_COLUMN_9][0] = DeviceInput(DEVICE_KEYBOARD, KEY_C0);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_RIGHT_SIDE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LALT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_RIGHT_SIDE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RALT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_LAY_ROLL][0]   = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_LAY_ROLL][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_CYCLE_TAP_LEFT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cn);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_CYCLE_TAP_RIGHT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cm);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_CYCLE_SEGMENT_LEFT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cn);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_CYCLE_SEGMENT_LEFT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_CYCLE_SEGMENT_LEFT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
	
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_CYCLE_SEGMENT_RIGHT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cm);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_CYCLE_SEGMENT_RIGHT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_CYCLE_SEGMENT_RIGHT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
	
	m_EditMappingsDeviceInput.button    [EDIT_BUTTON_SCROLL_SPEED_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_UP);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_SPEED_UP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_SPEED_UP][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);

	m_EditMappingsDeviceInput.button    [EDIT_BUTTON_SCROLL_SPEED_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DOWN);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_SPEED_DOWN][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SCROLL_SPEED_DOWN][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_SELECT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SCROLL_SELECT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SNAP_NEXT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LEFT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SNAP_PREV][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RIGHT);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_EDIT_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
	m_EditMappingsMenuButton.button[EDIT_BUTTON_OPEN_EDIT_MENU][0] = GAME_BUTTON_START;
	m_EditMappingsMenuButton.button[EDIT_BUTTON_OPEN_EDIT_MENU][1] = GAME_BUTTON_BACK;
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_AREA_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_ALTER_MENU][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Ca);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_OPEN_INPUT_HELP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_F1);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cb);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LALT);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RALT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cb);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_RECORD_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cr);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_RECORD_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_EditMappingsDeviceInput.hold[EDIT_BUTTON_RECORD_SELECTION][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_INSERT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_INSERT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_INSERT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_BACKSLASH);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_DELETE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_DEL);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_ADJUST_FINE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_RALT);
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_ADJUST_FINE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_LALT);

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SAVE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_Cs);
	#if defined(MACOSX)
		/* use cmd */
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAVE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LMETA);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAVE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RMETA);
	#else
		/* use ctrl */
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAVE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
		m_EditMappingsDeviceInput.hold[EDIT_BUTTON_SAVE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
	#endif

	m_EditMappingsDeviceInput.button[EDIT_BUTTON_UNDO][1] = DeviceInput(DEVICE_KEYBOARD, KEY_Cu);

	// Switch players, if it makes sense to do so.
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SWITCH_PLAYERS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_SLASH);
	
	// Allow song and step timing to be swapped.
	m_EditMappingsDeviceInput.button[EDIT_BUTTON_SWITCH_TIMINGS][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Ct);

	m_PlayMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
	m_PlayMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
	m_PlayMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = GAME_BUTTON_BACK;
	m_PlayMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = GAME_BUTTON_START;

	m_RecordMappingsDeviceInput.button[EDIT_BUTTON_LAY_ROLL][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LSHIFT);
	m_RecordMappingsDeviceInput.button[EDIT_BUTTON_LAY_ROLL][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RSHIFT);
	m_RecordMappingsDeviceInput.button[EDIT_BUTTON_REMOVE_NOTE][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LALT);
	m_RecordMappingsDeviceInput.button[EDIT_BUTTON_REMOVE_NOTE][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RALT);
	m_RecordMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
	m_RecordMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = DeviceInput(DEVICE_KEYBOARD, KEY_ENTER);
	m_RecordMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = GAME_BUTTON_BACK;
	m_RecordMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = GAME_BUTTON_START;

	m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_PLAY_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cp);
	m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_RECORD_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cr);
	m_RecordPausedMappingsDeviceInput.hold[EDIT_BUTTON_RECORD_SELECTION][0] = DeviceInput(DEVICE_KEYBOARD, KEY_LCTRL);
	m_RecordPausedMappingsDeviceInput.hold[EDIT_BUTTON_RECORD_SELECTION][1] = DeviceInput(DEVICE_KEYBOARD, KEY_RCTRL);
	m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_RECORD_FROM_CURSOR][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cr);
	m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_RETURN_TO_EDIT][0] = DeviceInput(DEVICE_KEYBOARD, KEY_ESC);
	m_RecordPausedMappingsMenuButton.button[EDIT_BUTTON_RETURN_TO_EDIT][1] = GAME_BUTTON_BACK;
	m_RecordPausedMappingsDeviceInput.button[EDIT_BUTTON_UNDO][0] = DeviceInput(DEVICE_KEYBOARD, KEY_Cu);
}

/* Given a DeviceInput that was just depressed, return an active edit function. */
EditButton ScreenEdit::DeviceToEdit( const DeviceInput &DeviceI ) const
{
	ASSERT( DeviceI.IsValid() );

	const MapEditToDI *pCurrentMap = GetCurrentDeviceInputMap();

	/* First, search to see if a key that requires a modifier is pressed. */
	FOREACH_EditButton( e )
	{
		for( int slot = 0; slot < NUM_EDIT_TO_DEVICE_SLOTS; ++slot )
		{
			if( pCurrentMap->button[e][slot] == DeviceI && pCurrentMap->hold[e][0].IsValid() )
			{
				/* The button maps to this function, and has one or more shift modifiers attached. */
				for( int holdslot = 0; holdslot < NUM_EDIT_TO_DEVICE_SLOTS; ++holdslot )
				{
					DeviceInput hDI = pCurrentMap->hold[e][holdslot];
					if( hDI.IsValid() && INPUTFILTER->IsBeingPressed(hDI) )
						return e;
				}
			}
		}
	}

	/* No shifted keys matched.  See if any unshifted inputs are bound to this key. */  
	FOREACH_EditButton(e)
	{
		for( int slot = 0; slot < NUM_EDIT_TO_DEVICE_SLOTS; ++slot )
		{
			if( pCurrentMap->button[e][slot] == DeviceI && !pCurrentMap->hold[e][0].IsValid() )
			{
				/* The button maps to this function. */
				return e;
			}
		}
	}

	return EditButton_Invalid;
}

/* Given a DeviceInput that was just depressed, return an active edit function. */
EditButton ScreenEdit::MenuButtonToEditButton( GameButton MenuI ) const
{
	const MapEditButtonToMenuButton *pCurrentMap = GetCurrentMenuButtonMap();

	FOREACH_EditButton(e)
	{
		for( int slot = 0; slot < NUM_EDIT_TO_MENU_SLOTS; ++slot )
		{
			if( pCurrentMap->button[e][slot] == MenuI )
			{
				/* The button maps to this function. */
				return e;
			}
		}
	}

	return EditButton_Invalid;
}

/* If DeviceI was just pressed, return true if button is triggered.  (More than one
 * function may be mapped to a key.) */
bool ScreenEdit::EditPressed( EditButton button, const DeviceInput &DeviceI )
{
	ASSERT( DeviceI.IsValid() );

	const MapEditToDI *pCurrentMap = GetCurrentDeviceInputMap();

	/* First, search to see if a key that requires a modifier is pressed. */
	bool bPrimaryButtonPressed = false;
	for( int slot = 0; slot < NUM_EDIT_TO_DEVICE_SLOTS; ++slot )
	{
		if( pCurrentMap->button[button][slot] == DeviceI )
			bPrimaryButtonPressed = true;
	}

	if( !bPrimaryButtonPressed )
		return false;

	/* The button maps to this function.  Does the function has one or more shift modifiers attached? */
	if( !pCurrentMap->hold[button][0].IsValid() )
		return true;

	for( int holdslot = 0; holdslot < NUM_EDIT_TO_DEVICE_SLOTS; ++holdslot )
	{
		DeviceInput hDI = pCurrentMap->hold[button][holdslot];
		if( INPUTFILTER->IsBeingPressed(hDI) )
			return true;
	}

	/* No shifted keys matched. */  
	return false;
}

bool ScreenEdit::EditToDevice( EditButton button, int iSlotNum, DeviceInput &DeviceI ) const
{
	ASSERT( iSlotNum < NUM_EDIT_TO_DEVICE_SLOTS );
	const MapEditToDI *pCurrentMap = GetCurrentDeviceInputMap();
	if( pCurrentMap->button[button][iSlotNum].IsValid() )
		DeviceI = pCurrentMap->button[button][iSlotNum];
	else if( pCurrentMap->hold[button][iSlotNum].IsValid() )
		DeviceI = pCurrentMap->hold[button][iSlotNum];
	return DeviceI.IsValid();
}

bool ScreenEdit::EditIsBeingPressed( EditButton button ) const
{
	for( int slot = 0; slot < NUM_EDIT_TO_DEVICE_SLOTS; ++slot )
	{
		DeviceInput DeviceI;
		if( EditToDevice( button, slot, DeviceI ) && INPUTFILTER->IsBeingPressed(DeviceI) )
			return true;
	}

	return false;
}

const MapEditToDI *ScreenEdit::GetCurrentDeviceInputMap() const
{
	switch( m_EditState )
	{
	DEFAULT_FAIL( m_EditState );
	case STATE_EDITING:		return &m_EditMappingsDeviceInput;
	case STATE_PLAYING:		return &m_PlayMappingsDeviceInput;
	case STATE_RECORDING:		return &m_RecordMappingsDeviceInput;
	case STATE_RECORDING_PAUSED:	return &m_RecordPausedMappingsDeviceInput;
	}
}

const MapEditButtonToMenuButton *ScreenEdit::GetCurrentMenuButtonMap() const
{
	switch( m_EditState )
	{
	DEFAULT_FAIL( m_EditState );
	case STATE_EDITING:		return &m_EditMappingsMenuButton;
	case STATE_PLAYING:		return &m_PlayMappingsMenuButton;
	case STATE_RECORDING:		return &m_RecordMappingsMenuButton;
	case STATE_RECORDING_PAUSED:	return &m_RecordPausedMappingsMenuButton;
	}
}


static MenuDef g_EditHelp(
	"ScreenMiniMenuEditHelp"
	// fill this in dynamically
);

static MenuDef g_AttackAtTimeMenu(
	"ScreenMiniMenuAttackAtTimeMenu"
	// fill this in dynamically
);

static MenuDef g_IndividualAttack(
	"ScreenMiniMenuIndividualAttack"
	// fill this in dynamically
);

static MenuDef g_KeysoundTrack(
							   "ScreenMiniMenuKeysoundTrack"
							   ); // fill this in dynamically

static MenuDef g_MainMenu(
	"ScreenMiniMenuMainMenu",
	MenuRowDef(ScreenEdit::play_whole_song,
		"Play whole song",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::play_current_beat_to_end,
		"Play current beat to end",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::save,
		"Save",
		true, EditMode_Home, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::play_selection,
		"Play selection",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::set_selection_start,
		"Set selection start",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::set_selection_end,
		"Set selection end",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::revert_to_last_save,
		"Revert to last save",
		true, EditMode_Home, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::revert_from_disk,
		"Revert from disk",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::options,
		"Editor options",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::edit_song_info,
		"Edit song info",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::edit_steps_information,	
		"Edit steps information",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::edit_timing_data,
		"Edit Timing Data",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::view_steps_data,
		"View steps data",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::play_preview_music,
		"Play preview music",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::exit,
		"Exit Edit Mode",
		true, EditMode_Practice, true, true, 0, NULL )
);

static MenuDef g_AlterMenu(
	"ScreenMiniMenuAlterMenu",
	MenuRowDef(ScreenEdit::cut,
		"Cut",
		true, 
		EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::copy,
		"Copy",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::clear,			"Clear area",				true, 
	      EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::quantize,			"Quantize",				true, 
	      EditMode_Practice, true, true, 0, 
	      "4th","8th","12th","16th","24th","32nd","48th","64th","192nd"),
	MenuRowDef(ScreenEdit::turn,				"Turn",					true, 
	      EditMode_Practice, true, true, 0, "Left","Right","Mirror","Backwards","Shuffle","SuperShuffle" ),
	MenuRowDef(ScreenEdit::transform,			"Transform",				true, 
	      EditMode_Practice, true, true, 0, "NoHolds","NoMines","Little","Wide",
	      "Big","Quick","Skippy","Mines","Echo","Stomp","Planted","Floored",
	      "Twister","NoJumps","NoHands","NoQuads","NoStretch" ),
	MenuRowDef(ScreenEdit::alter,			"Alter",				true, 
	      EditMode_Practice, true, true, 0, "Autogen To Fill Width","Backwards","Swap Sides",
	      "Copy Left To Right","Copy Right To Left","Clear Left","Clear Right",
	      "Collapse To One","Collapse Left","Shift Left","Shift Right" ),
	MenuRowDef(ScreenEdit::tempo,			"Tempo",				true, 
	      EditMode_Full, true, true, 0, "Compress 2x","Compress 3->2",
	      "Compress 4->3","Expand 3->4","Expand 2->3","Expand 2x" ),
	MenuRowDef(ScreenEdit::play,
		"Play selection",
		true,
		EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::record,
		"Record in selection",
		true, 
		EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::preview_designation,
		"Designate as Music Preview",
		true,
		EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::convert_to_pause,
		"Convert selection to pause",
		true, 
		EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::convert_to_delay,
		"Convert selection to delay",	
		true, 
		EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::convert_to_warp,	
		"Convert selection to warp",
		true, 
		EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::convert_to_fake,
		"Convert selection to fake",
		true, 
		EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::convert_to_attack,
		"Convert selection to attack",
		true,
		EditMode_Full, true, true, 0, NULL),
	MenuRowDef(ScreenEdit::routine_invert_notes,
		"Invert notes' player",
		true,
		EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::routine_mirror_1_to_2,
		"Mirror Player 1 to 2",
		true,
		EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::routine_mirror_2_to_1,
		"Mirror Player 2 to 1",	
		true,
		EditMode_Full, true, true, 0, NULL )
);

static MenuDef g_AreaMenu(
	"ScreenMiniMenuAreaMenu",
	MenuRowDef(ScreenEdit::paste_at_current_beat,
		"Paste at current beat",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::paste_at_begin_marker,
		"Paste at begin marker",
		true, EditMode_Practice, true, true, 0, NULL ),
    MenuRowDef(ScreenEdit::insert_and_shift,
		"Insert beat and shift down",
		true, EditMode_Practice, true, true, 0, "4th","8th","12th","16th","24th","32nd","48th","64th","192nd" ),
	MenuRowDef(ScreenEdit::delete_and_shift,
		"Delete beat and shift up",	
		true, EditMode_Practice, true, true, 0, "4th","8th","12th","16th","24th","32nd","48th","64th","192nd" ),
	MenuRowDef(ScreenEdit::shift_pauses_forward,
		"Shift all timing changes down",
		true, EditMode_Full, true, true, 0, "4th","8th","12th","16th","24th","32nd","48th","64th","192nd" ),
	MenuRowDef(ScreenEdit::shift_pauses_backward,
		"Shift all timing changes up",
		true, EditMode_Full, true, true, 0, "4th","8th","12th","16th","24th","32nd","48th","64th","192nd" ),
	MenuRowDef(ScreenEdit::convert_pause_to_beat,
		"Convert pause to beats",
		true, 
		EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::convert_delay_to_beat,
		"Convert delay to beats",
		true,
		EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::last_second_at_beat,
		"Designate last second at current beat",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::undo,
		"Undo",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::clear_clipboard,
		"Clear clipboard",
		true,
		EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::modify_attacks_at_row,
		"Modify Attacks at current beat",
		true, EditMode_CourseMods, true, true, 0, NULL),
	MenuRowDef(ScreenEdit::modify_keysounds_at_row,
		"Modify Keysounds at current beat",
		true, EditMode_Full, true, true, 0, NULL)
			  
);

static MenuDef g_StepsInformation(
	"ScreenMiniMenuStepsInformation",
	MenuRowDef(ScreenEdit::difficulty,
		"Difficulty",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::meter,
		"Meter",
		true, EditMode_Practice, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::predict_meter,
		"Predicted Meter",
		false, EditMode_Full, true, true, 0, NULL ),
    MenuRowDef(ScreenEdit::chartname,
		"Chart Name",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::description,
		"Description",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::chartstyle,
		"Chart Style",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::step_credit,	
		"Step Author",
		true, EditMode_Practice, true, true, 0, NULL ),
    MenuRowDef(ScreenEdit::step_display_bpm,
		"Display BPM",
		true, EditMode_Full, true, true, 0, "Actual", "Specified", "Random" ),
    MenuRowDef(ScreenEdit::step_min_bpm,
		"Min BPM",
		true, EditMode_Full, true, true, 0, NULL ),
    MenuRowDef(ScreenEdit::step_max_bpm,
		"Max BPM",
		true, EditMode_Full, true, true, 0, NULL )
);

static MenuDef g_StepsData(
	"ScreenMiniMenuStepsData",
	MenuRowDef( ScreenEdit::tap_notes,	"Tap Steps",	false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::jumps,		"Jumps",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::hands,		"Hands",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::quads,		"Quads",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::holds,		"Holds",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::mines,		"Mines",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::rolls,		"Rolls",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::lifts,		"Lifts",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::fakes,		"Fakes",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::stream,		"Stream",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::voltage,	"Voltage",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::air,		"Air",			false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::freeze,		"Freeze",		false, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef( ScreenEdit::chaos,		"Chaos",		false, EditMode_Full, true, true, 0, NULL )
);

static MenuDef g_SongInformation(
	"ScreenMiniMenuSongInformation",
	MenuRowDef(ScreenEdit::main_title,
		"Main title",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::sub_title,
		"Sub title",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::artist,
		"Artist",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::genre,
		"Genre",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::credit,
		"Credit",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::main_title_transliteration,	
		"Main title transliteration",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::sub_title_transliteration,
		"Sub title transliteration",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::artist_transliteration,
		"Artist transliteration",
		true, EditMode_Practice, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::last_second_hint,
		"Last second hint",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::preview_start,
		"Preview Start",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::preview_length,
		"Preview Length",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::display_bpm,
		"Display BPM",
		true, EditMode_Full, true, true, 0, "Actual", "Specified", "Random" ),
	MenuRowDef(ScreenEdit::min_bpm,
		"Min BPM",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::max_bpm,
		"Max BPM",
		true, EditMode_Full, true, true, 0, NULL )
);


static MenuDef g_TimingDataInformation(
	"ScreenMiniMenuTimingDataInformation",
	MenuRowDef(ScreenEdit::beat_0_offset,
		"Beat 0 Offset",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::bpm,
		"Edit BPM change",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::stop,
		"Edit stop",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::delay,
		"Edit delay",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::time_signature,
		"Edit time signature",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::label,
		"Edit label",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::tickcount,
		"Edit tickcount",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::combo,
		"Edit combo",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::warp,
		"Edit warp",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::speed_percent,
		"Edit speed (percent)",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::speed_wait,
		"Edit speed (wait)",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::speed_mode,
		"Edit speed (mode)",
		true, EditMode_Full, true, true, 0, "Beats", "Seconds" ),
	MenuRowDef(ScreenEdit::scroll,
		"Edit scrolling factor",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::fake,
		"Edit fake",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::copy_full_timing,
		"Copy timing data",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::paste_full_timing,
		"Paste timing data",
		true, EditMode_Full, true, true, 0, NULL ),
	MenuRowDef(ScreenEdit::erase_step_timing,
		"Erase step timing",
		true, EditMode_Full, true, true, 0, NULL )
);

// XXX: What are these enums used for?
enum
{
	song_bganimation,
	song_movie,
	song_bitmap,
	global_bganimation,
	global_movie,
	global_movie_song_group,
	global_movie_song_group_and_genre,
	dynamic_random,
	baked_random,
	none
};
static bool EnabledIfSet1SongBGAnimation();
static bool EnabledIfSet1SongMovie();
static bool EnabledIfSet1SongBitmap();
static bool EnabledIfSet1GlobalBGAnimation();
static bool EnabledIfSet1GlobalMovie();
static bool EnabledIfSet1GlobalMovieSongGroup();
static bool EnabledIfSet1GlobalMovieSongGroupAndGenre();
static bool EnabledIfSet2SongBGAnimation();
static bool EnabledIfSet2SongMovie();
static bool EnabledIfSet2SongBitmap();
static bool EnabledIfSet2GlobalBGAnimation();
static bool EnabledIfSet2GlobalMovie();
static bool EnabledIfSet2GlobalMovieSongGroup();
static bool EnabledIfSet2GlobalMovieSongGroupAndGenre();
static MenuDef g_BackgroundChange(
	"ScreenMiniMenuBackgroundChange",
	MenuRowDef(ScreenEdit::layer,
		"Layer",
		false,
		EditMode_Full, true, false, 0, "" ),
	MenuRowDef(ScreenEdit::rate,
		"Rate",
		true,
		EditMode_Full, true, false, 10, "0%","10%","20%","30%","40%","50%",
		"60%","70%","80%","90%","100%","120%","140%","160%","180%","200%",
		"220%","240%","260%","280%","300%","350%","400%" ),
	MenuRowDef(ScreenEdit::transition,
		"Force Transition",
		true,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::effect,
		"Force Effect",
		true,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::color1,
		"Force Color 1",
		true,
		EditMode_Full, true, false, 0, "-","#FFFFFF","#808080","#FFFFFF80",
		"#000000","#FF0000","#00FF00","#0000FF","#FFFF00","#00FFFF","#FF00FF" ),
	MenuRowDef(ScreenEdit::color2,
		"Force Color 2",
		true,
		EditMode_Full, true, false, 0, "-","#FFFFFF","#808080","#FFFFFF80",
		"#000000","#FF0000","#00FF00","#0000FF","#FFFF00","#00FFFF","#FF00FF" ),
	MenuRowDef(ScreenEdit::file1_type,
		"File1 Type",
		true,
		EditMode_Full, true, true, 0, "Song BGAnimation", "Song Movie", 
		"Song Bitmap", "Global BGAnimation", "Global Movie", 
		"Global Movie from Song Group", "Global Movie from Song Group and Genre", 
		"Dynamic Random", "Baked Random", "None" ),
	MenuRowDef(ScreenEdit::file1_song_bganimation,
		"File1 Song BGAnimation",
		EnabledIfSet1SongBGAnimation,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file1_song_movie,
		"File1 Song Movie",
		EnabledIfSet1SongMovie,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file1_song_still,
		"File1 Song Still",
		EnabledIfSet1SongBitmap,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file1_global_bganimation,
		"File1 Global BGAnimation",
		EnabledIfSet1GlobalBGAnimation,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file1_global_movie,
		"File1 Global Movie",
		EnabledIfSet1GlobalMovie,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file1_global_movie_song_group,
		"File1 Global Movie (Group)",
		EnabledIfSet1GlobalMovieSongGroup,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file1_global_movie_song_group_and_genre,
		"File1 Global Movie (Group + Genre)",
		EnabledIfSet1GlobalMovieSongGroupAndGenre,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file2_type,
		"File2 Type",
		true,
		EditMode_Full, true, true, 0, "Song BGAnimation", "Song Movie",
		"Song Bitmap", "Global BGAnimation", "Global Movie", 
		"Global Movie from Song Group", "Global Movie from Song Group and Genre", 
		"Dynamic Random", "Baked Random", "None" ),
	MenuRowDef(ScreenEdit::file2_song_bganimation,
		"File2 Song BGAnimation",
		EnabledIfSet2SongBGAnimation,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file2_song_movie,
		"File2 Song Movie",
		EnabledIfSet2SongMovie,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file2_song_still,
		"File2 Song Still",
		EnabledIfSet2SongBitmap,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file2_global_bganimation,
		"File2 Global BGAnimation",
		EnabledIfSet2GlobalBGAnimation,	
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file2_global_movie,
		"File2 Global Movie",
		EnabledIfSet2GlobalMovie,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file2_global_movie_song_group,
		"File2 Global Movie (Group)",
		EnabledIfSet2GlobalMovieSongGroup,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::file2_global_movie_song_group_and_genre,
		"File2 Global Movie (Group + Genre)",
		EnabledIfSet2GlobalMovieSongGroupAndGenre,
		EditMode_Full, true, false, 0, NULL ),
	MenuRowDef(ScreenEdit::delete_change,
		"Remove Change",
		true,
		EditMode_Full, true, true, 0, NULL )
);
static bool EnabledIfSet1SongBGAnimation()		{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == song_bganimation			&& !g_BackgroundChange.rows[ScreenEdit::file1_song_bganimation].choices.empty(); }
static bool EnabledIfSet1SongMovie()			{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == song_movie				&& !g_BackgroundChange.rows[ScreenEdit::file1_song_movie].choices.empty(); }
static bool EnabledIfSet1SongBitmap()			{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == song_bitmap				&& !g_BackgroundChange.rows[ScreenEdit::file1_song_still].choices.empty(); }
static bool EnabledIfSet1GlobalBGAnimation()		{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == global_bganimation			&& !g_BackgroundChange.rows[ScreenEdit::file1_global_bganimation].choices.empty(); }
static bool EnabledIfSet1GlobalMovie()			{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == global_movie			&& !g_BackgroundChange.rows[ScreenEdit::file1_global_movie].choices.empty(); }
static bool EnabledIfSet1GlobalMovieSongGroup()		{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == global_movie_song_group		&& !g_BackgroundChange.rows[ScreenEdit::file1_global_movie_song_group].choices.empty(); }
static bool EnabledIfSet1GlobalMovieSongGroupAndGenre() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file1_type] == global_movie_song_group_and_genre	&& !g_BackgroundChange.rows[ScreenEdit::file1_global_movie_song_group_and_genre].choices.empty(); }
static bool EnabledIfSet2SongBGAnimation()		{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == song_bganimation			&& !g_BackgroundChange.rows[ScreenEdit::file2_song_bganimation].choices.empty(); }
static bool EnabledIfSet2SongMovie()			{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == song_movie				&& !g_BackgroundChange.rows[ScreenEdit::file2_song_movie].choices.empty(); }
static bool EnabledIfSet2SongBitmap()			{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == song_bitmap				&& !g_BackgroundChange.rows[ScreenEdit::file2_song_still].choices.empty(); }
static bool EnabledIfSet2GlobalBGAnimation()		{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == global_bganimation			&& !g_BackgroundChange.rows[ScreenEdit::file2_global_bganimation].choices.empty(); }
static bool EnabledIfSet2GlobalMovie()			{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == global_movie			&& !g_BackgroundChange.rows[ScreenEdit::file2_global_movie].choices.empty(); }
static bool EnabledIfSet2GlobalMovieSongGroup()		{ return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == global_movie_song_group		&& !g_BackgroundChange.rows[ScreenEdit::file2_global_movie_song_group].choices.empty(); }
static bool EnabledIfSet2GlobalMovieSongGroupAndGenre() { return ScreenMiniMenu::s_viLastAnswers[ScreenEdit::file2_type] == global_movie_song_group_and_genre	&& !g_BackgroundChange.rows[ScreenEdit::file2_global_movie_song_group_and_genre].choices.empty(); }

static RString GetOneBakedRandomFile( Song *pSong, bool bTryGenre = true )
{
	vector<RString> vsPaths; 
	vector<RString> vsNames;
	BackgroundUtil::GetGlobalRandomMovies(
		pSong,
		"",
		vsPaths, 
		vsNames,
		bTryGenre );

	return vsNames.empty() ? RString() : vsNames[RandomInt(vsNames.size())];
}

static MenuDef g_InsertTapAttack(
	"ScreenMiniMenuInsertTapAttack",
	MenuRowDef(-1,
		"Duration seconds",
		true, EditMode_Practice, true, false, 3, "5","10","15","20","25","30","35","40","45" ), // TODO: Replace
	MenuRowDef(-1,
		"Set modifiers",
		true, EditMode_Practice, true, true, 0, "Press Start" )
);

static MenuDef g_InsertCourseAttack(
	"ScreenMiniMenuInsertCourseAttack",
	MenuRowDef(ScreenEdit::duration,
		"Duration seconds",
		true, EditMode_Practice, true, false, 3, "5","10","15","20","25","30","35","40","45" ),
	MenuRowDef(ScreenEdit::set_mods,
		"Set modifiers",
		true, EditMode_Practice, true, true, 0, "Press Start" ),
	MenuRowDef(ScreenEdit::remove,
		"Remove",
		true, EditMode_Practice, true, true, 0, "Press Start" )
);

static MenuDef g_InsertStepAttack(
	"ScreenMiniMenuInsertCourseAttack",
	MenuRowDef(ScreenEdit::sa_duration,
		"Duration seconds",
		true, EditMode_Practice, true, false, 3, "5","10","15","20","25","30","35","40","45" ),
	MenuRowDef(ScreenEdit::sa_set_mods,
		"Set modifiers",
		true, EditMode_Practice, true, true, 0, "Press Start" ),
	MenuRowDef(ScreenEdit::sa_remove,
		"Remove",
		true, EditMode_Practice, true, true, 0, "Press Start" ));

static MenuDef g_CourseMode(
	"ScreenMiniMenuCourseDisplay",
	MenuRowDef( -1, "Play mods from course",	true, EditMode_Practice, true, false, 0, NULL )
);

// HACK: need to remember the track we're inserting on so that we can lay the
// attack note after coming back from menus.
static int g_iLastInsertTapAttackTrack = -1;
static float g_fLastInsertAttackDurationSeconds = -1;
static float g_fLastInsertAttackPositionSeconds = -1;
static BackgroundLayer g_CurrentBGChangeLayer = BACKGROUND_LAYER_Invalid;

static void SetDefaultEditorNoteSkin( size_t num, RString &sNameOut, RString &defaultValueOut )
{
	sNameOut = ssprintf( "EditorNoteSkinP%d", int(num + 1) );

	// XXX: We need more supported noteskins.
	defaultValueOut = "default";
}

static Preference1D<RString> EDITOR_NOTE_SKINS( SetDefaultEditorNoteSkin, NUM_PLAYERS );

static ThemeMetric<RString> EDIT_MODIFIERS		("ScreenEdit","EditModifiers");

static ThemeMetric<bool> LOOP_ON_CHART_END		("ScreenEdit","LoopOnChartEnd");

REGISTER_SCREEN_CLASS( ScreenEdit );

void ScreenEdit::Init()
{
	m_pSoundMusic = NULL;
	
	GAMESTATE->m_bIsUsingStepTiming = false;
	GAMESTATE->m_bInStepEditor = true;

	SubscribeToMessage( "Judgment" );

	ASSERT( GAMESTATE->m_pCurSong != NULL );
	ASSERT( GAMESTATE->m_pCurSteps[PLAYER_1] != NULL );

	EDIT_MODE.Load( m_sName, "EditMode" );
	ScreenWithMenuElements::Init();

	InitEditMappings();
	
	currentCycleSegment = SEGMENT_LABEL;

	// save the originals for reverting later
	CopyToLastSave();

	m_CurrentAction = MAIN_MENU_CHOICE_INVALID;
	m_InputPlayerNumber = PLAYER_INVALID;

	if( GAMESTATE->GetCurrentStyle()->m_StyleType == StyleType_TwoPlayersSharedSides )
		m_InputPlayerNumber = PLAYER_1;

	FOREACH_PlayerNumber( p )
		GAMESTATE->m_bSideIsJoined[p] = false;
	GAMESTATE->m_bSideIsJoined[PLAYER_1] = true;

	m_pSong = GAMESTATE->m_pCurSong;
	m_pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
	
	/*	The user will most likely switch into Step Timing after laying down 
		some initial notes. It also throws off many people at first glance.
	*/
	if( m_pSong->IsStepsUsingDifferentTiming(m_pSteps) )
		GAMESTATE->m_bIsUsingStepTiming = true;

	m_bReturnToRecordMenuAfterPlay = false;
	m_fBeatToReturnTo = 0;
	
	m_selectedTap = TAP_ORIGINAL_TAP;

	GAMESTATE->m_bGameplayLeadIn.Set( true );
	GAMESTATE->m_EditMode = EDIT_MODE.GetValue();
	SetBeat(0);
	m_fTrailingBeat = 0;

	m_iShiftAnchor = -1;
	m_iStartPlayingAt = -1;
	m_iStopPlayingAt = -1;
	
	attackInProcess = -1;
	modInProcess = -1;
	
	this->AddChild( &m_Background );

	m_SnapDisplay.SetXY( EDIT_X, PLAYER_Y_STANDARD );
	m_SnapDisplay.Load();
	// xxx: hardcoded command -aj
	m_SnapDisplay.SetZoom( 0.5f );
	this->AddChild( &m_SnapDisplay );

	// We keep track of this bit of state so that when the user is in Edit/Sync Songs and makes a change to the NoteSkins,
	// that change is "sticky" across multiple ScreenEdits.  That is the way the rest of the options work.
	// TODO: It would be cleaner to do this by making it possible to set an option in metrics.ini.
	if( !GAMESTATE->m_bDidModeChangeNoteSkin ) 
	{
		GAMESTATE->m_bDidModeChangeNoteSkin = true;
		FOREACH_PlayerNumber( pn ) 
		{
			const RString &sNoteSkin = EDITOR_NOTE_SKINS[pn].Get();
			if( NOTESKIN->DoesNoteSkinExist( sNoteSkin ) )
				PO_GROUP_ASSIGN( GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions, 
				                 ModsLevel_Preferred, m_sNoteSkin, sNoteSkin );
		}
	}
	m_PlayerStateEdit.m_PlayerNumber = PLAYER_1;
	// If we always go with the GAMESTATE NoteSkin, we will have fun effects
	// like Vivid or Flat in the editor notefield. This is not conducive to
	// productive editing.
	// todo: We should allow certain noteskins (note-colored/rhythm) to be
	// displayed. (Perhaps this should be a noteskin metric.) -aj
	if( NOTESKIN->DoesNoteSkinExist( EDITOR_NOTE_SKINS[PLAYER_1].Get() ) )
	{
		PO_GROUP_ASSIGN( m_PlayerStateEdit.m_PlayerOptions, ModsLevel_Stage, m_sNoteSkin, EDITOR_NOTE_SKINS[PLAYER_1].Get() );
	}
	else
	{
		PO_GROUP_ASSIGN( m_PlayerStateEdit.m_PlayerOptions, ModsLevel_Stage, m_sNoteSkin, GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetStage().m_sNoteSkin );
	}
	m_PlayerStateEdit.m_PlayerOptions.FromString( ModsLevel_Stage, EDIT_MODIFIERS );
	
	this->originalPlayerOptions.FromString(ModsLevel_Stage, EDIT_MODIFIERS);

	m_pSteps->GetNoteData( m_NoteDataEdit );
	m_NoteFieldEdit.SetXY( EDIT_X, EDIT_Y );
	m_NoteFieldEdit.SetZoom( 0.5f );
	m_NoteFieldEdit.Init( &m_PlayerStateEdit, PLAYER_HEIGHT*2 );
	m_NoteFieldEdit.Load( &m_NoteDataEdit, -240, 850 );
	this->AddChild( &m_NoteFieldEdit );

	m_NoteDataRecord.SetNumTracks( m_NoteDataEdit.GetNumTracks() );
	m_NoteFieldRecord.SetXY( RECORD_X, RECORD_Y );
	m_NoteFieldRecord.Init( GAMESTATE->m_pPlayerState[PLAYER_1], PLAYER_HEIGHT );
	m_NoteFieldRecord.Load( &m_NoteDataRecord, -120, 425 );
	this->AddChild( &m_NoteFieldRecord );

	m_EditState = EditState_Invalid;
	TransitionEditState( STATE_EDITING );

	m_bRemoveNoteButtonDown = false;

	m_Clipboard.SetNumTracks( m_NoteDataEdit.GetNumTracks() );
	
	clipboardFullTiming = GAMESTATE->m_pCurSong->m_SongTiming; // always have a backup.

	m_bHasUndo = false;
	m_Undo.SetNumTracks( m_NoteDataEdit.GetNumTracks() );

	m_bDirty = m_NoteDataEdit.IsEmpty(); // require the usage of saving if empty.

	m_Player->Init( "Player", GAMESTATE->m_pPlayerState[PLAYER_1], NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
	m_Player->CacheAllUsedNoteSkins();
	GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerController = PC_HUMAN;
	m_Player->SetXY( PLAYER_X, PLAYER_Y );
	this->AddChild( m_Player );

	this->AddChild( &m_Foreground );

	m_textInputTips.SetName( "EditHelp" );
	m_textInputTips.LoadFromFont( THEME->GetPathF("ScreenEdit","EditHelp") );
	m_textInputTips.SetText( EDIT_HELP_TEXT );
	LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textInputTips );
	this->AddChild( &m_textInputTips );

	m_textInfo.SetName( "Info" );
	m_textInfo.LoadFromFont( THEME->GetPathF("ScreenEdit","Info") );
	LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textInfo );
	this->AddChild( &m_textInfo );

	m_textPlayRecordHelp.SetName( "PlayRecordHelp" );
	m_textPlayRecordHelp.LoadFromFont( THEME->GetPathF("ScreenEdit","PlayRecordHelp") );
	m_textPlayRecordHelp.SetText( PLAY_RECORD_HELP_TEXT );
	LOAD_ALL_COMMANDS_AND_SET_XY_AND_ON_COMMAND( m_textPlayRecordHelp );
	this->AddChild( &m_textPlayRecordHelp );

	m_soundAddNote.Load(		THEME->GetPathS("ScreenEdit","AddNote"), true );
	m_soundRemoveNote.Load(		THEME->GetPathS("ScreenEdit","RemoveNote"), true );
	m_soundChangeLine.Load(		THEME->GetPathS("ScreenEdit","line"), true );
	m_soundChangeSnap.Load(		THEME->GetPathS("ScreenEdit","snap"), true );
	m_soundMarker.Load(		THEME->GetPathS("ScreenEdit","marker"), true );
	m_soundValueIncrease.Load(	THEME->GetPathS("ScreenEdit","value increase"), true );
	m_soundValueDecrease.Load(	THEME->GetPathS("ScreenEdit","value decrease"), true );
	m_soundSwitchPlayer.Load(	THEME->GetPathS("ScreenEdit","switch player"), true );
	m_soundSwitchTiming.Load(	THEME->GetPathS("ScreenEdit","switch timing"), true );
	m_soundSwitchSteps.Load(	THEME->GetPathS("ScreenEdit","switch steps") );
	m_soundSave.Load(		THEME->GetPathS("ScreenEdit","save") );
	m_GameplayAssist.Init();

	m_AutoKeysounds.FinishLoading();
	m_pSoundMusic = m_AutoKeysounds.GetSound();

	this->HandleScreenMessage( SM_UpdateTextInfo );
	m_bTextInfoNeedsUpdate = true;

	SubscribeToMessage( Message_SongModified );
}

ScreenEdit::~ScreenEdit()
{
	// UGLY: Don't delete the Song's steps.
	m_SongLastSave.DetachSteps();

	LOG->Trace( "ScreenEdit::~ScreenEdit()" );
	m_pSoundMusic->StopPlaying();
	
	// Go back to Step Timing on leave.
	GAMESTATE->m_bIsUsingStepTiming = true;
	// DEFINITELY reset the InStepEditor variable.
	GAMESTATE->m_bInStepEditor = false;
}

void ScreenEdit::BeginScreen()
{
	ScreenWithMenuElements::BeginScreen();

	/* We do this ourself. */
	SOUND->HandleSongTimer( false );
}

void ScreenEdit::EndScreen()
{
	ScreenWithMenuElements::EndScreen();

	SOUND->HandleSongTimer( true );
}

// play assist ticks
void ScreenEdit::PlayTicks()
{
	if( m_EditState != STATE_PLAYING )
		return;

	m_GameplayAssist.PlayTicks( m_Player->GetNoteData(), m_Player->GetPlayerState() );
}

static ThemeMetric<float> FADE_IN_PREVIEW("ScreenEdit", "FadeInPreview");
static ThemeMetric<float> FADE_OUT_PREVIEW("ScreenEdit", "FadeOutPreview");

void ScreenEdit::PlayPreviewMusic()
{
	SOUND->StopMusic();
	SOUND->PlayMusic( 
		m_pSong->GetMusicPath(), 
		NULL,
		false,
		m_pSong->m_fMusicSampleStartSeconds,
		m_pSong->m_fMusicSampleLengthSeconds,
		FADE_IN_PREVIEW,
		FADE_OUT_PREVIEW );
}

void ScreenEdit::MakeFilteredMenuDef( const MenuDef* pDef, MenuDef &menu )
{
	menu = *pDef;
	menu.rows.clear();

	vector<MenuRowDef> aRows;
	FOREACH_CONST( MenuRowDef, pDef->rows, r )
	{
		// Don't add rows that aren't applicable to this edit mode.
		if( EDIT_MODE >= r->emShowIn )
			menu.rows.push_back( *r );
	}
}

void ScreenEdit::EditMiniMenu( const MenuDef* pDef, ScreenMessage SM_SendOnOK, ScreenMessage SM_SendOnCancel )
{
	// Reload options.
	MenuDef menu("");
	MakeFilteredMenuDef( pDef, menu );
	ScreenMiniMenu::MiniMenu( &menu, SM_SendOnOK, SM_SendOnCancel );
}

void ScreenEdit::Update( float fDeltaTime )
{
	m_PlayerStateEdit.Update( fDeltaTime );

	if( m_pSoundMusic->IsPlaying() )
	{
		RageTimer tm;
		const float fSeconds = m_pSoundMusic->GetPositionSeconds( NULL, &tm );
		GAMESTATE->UpdateSongPosition( fSeconds, GAMESTATE->m_pCurSong->m_SongTiming, tm, true );
	}

	if( m_EditState == STATE_RECORDING  )	
	{
		// TODO: Find a way to prevent STATE_RECORDING when in Song Timing.
		
		for( int t=0; t<GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; t++ )	// for each track
		{
			GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( t, PLAYER_1 );
			float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI );
			fSecsHeld = min( fSecsHeld, m_RemoveNoteButtonLastChanged.Ago() );
			if( fSecsHeld == 0 )
				continue;

			// TODO: Currently, this will ignore a hold note that's
			// held past the end of selection.  Ideally the hold
			// note should be created, ending right at the end of
			// selection (or appropriate quantization).
			float fStartPlayingAtBeat = NoteRowToBeat(m_iStartPlayingAt);
			float fStopPlayingAtBeat = NoteRowToBeat(m_iStopPlayingAt);
			if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat <= fStartPlayingAtBeat ||
			    GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat >= fStopPlayingAtBeat )
				continue;

			float fStartedHoldingSeconds = m_pSoundMusic->GetPositionSeconds() - fSecsHeld;
			float fStartBeat = max( fStartPlayingAtBeat, m_pSteps->GetTimingData()->GetBeatFromElapsedTime(fStartedHoldingSeconds) );
			float fEndBeat = max( fStartBeat, GetBeat() );
			fEndBeat = min( fEndBeat, fStopPlayingAtBeat );

			// Round start and end to the nearest snap interval
			fStartBeat = Quantize( fStartBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) );
			fEndBeat = Quantize( fEndBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) );

			if( m_bRemoveNoteButtonDown )
			{
				m_NoteDataRecord.ClearRangeForTrack( BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat), t );
			}
			else if( fSecsHeld > RECORD_HOLD_SECONDS )
			{
				// create or extend a hold or roll note
				TapNote tn = EditIsBeingPressed(EDIT_BUTTON_LAY_ROLL) ? TAP_ORIGINAL_ROLL_HEAD: TAP_ORIGINAL_HOLD_HEAD;

				tn.pn = m_InputPlayerNumber;
				m_NoteDataRecord.AddHoldNote( t, BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat), tn );
			}
		}
	}

	//
	// check for end of playback/record
	//
	if( m_EditState == STATE_RECORDING  ||  m_EditState == STATE_PLAYING )
	{
		/*
		 * If any arrow is being held, continue for up to half a second after
		 * the end marker.  This makes it possible to start a hold note near
		 * the end of the range.  We won't allow placing anything outside of the
		 * range.
		 */
		bool bButtonIsBeingPressed = false;
		for( int t=0; t<GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; t++ )	// for each track
		{
			GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( t, PLAYER_1 );
			if( INPUTMAPPER->IsBeingPressed(GameI) )
				bButtonIsBeingPressed = true;
		}

		float fLastBeat = NoteRowToBeat(m_iStopPlayingAt);
		if( bButtonIsBeingPressed && m_EditState == STATE_RECORDING )
		{
			float fSeconds = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( fLastBeat );
			fLastBeat = m_pSteps->GetTimingData()->GetBeatFromElapsedTime( fSeconds + 0.5f );
		}

		float fStopAtSeconds = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( NoteRowToBeat(m_iStopPlayingAt) ) + 1;
		if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fMusicSeconds > fStopAtSeconds )
		{
			TransitionEditState( ( LOOP_ON_CHART_END ? STATE_PLAYING : STATE_EDITING ) );
		}
	}


	//LOG->Trace( "ScreenEdit::Update(%f)", fDeltaTime );
	ScreenWithMenuElements::Update( fDeltaTime );


	// Update trailing beat
	float fDelta = GetBeat() - m_fTrailingBeat;
	if( fabsf(fDelta) < 10 )
		fapproach( m_fTrailingBeat, GetBeat(),
			fDeltaTime*40 / m_NoteFieldEdit.GetPlayerState()->m_PlayerOptions.GetCurrent().m_fScrollSpeed );
	else
		fapproach( m_fTrailingBeat, GetBeat(),
			fabsf(fDelta) * fDeltaTime*5 );

	PlayTicks();
}

static vector<int> FindAllAttacksAtTime(const AttackArray& attacks, float fStartTime)
{
	vector<int> ret;
	for (unsigned i = 0; i < attacks.size(); ++i)
	{
		if (fabs(attacks[i].fStartSecond - fStartTime) < 0.001f)
		{
			ret.push_back(i);
		}
	}
	return ret;
}

static int FindAttackAtTime( const AttackArray& attacks, float fStartTime )
{
	for( unsigned i = 0; i < attacks.size(); ++i )
	{
		if( fabs(attacks[i].fStartSecond - fStartTime) < 0.001f )
			return i;
	}
	return -1;
}

// TODO: Fix this mess. -Colby
static LocalizedString CURRENT_BEAT("ScreenEdit", "Current beat");
static LocalizedString CURRENT_SECOND("ScreenEdit", "Current second");
static LocalizedString SNAP_TO("ScreenEdit", "Snap to");
static LocalizedString NOTES("ScreenEdit", "%s notes");
static LocalizedString SELECTION_BEAT("ScreenEdit", "Selection beat");
static LocalizedString DIFFICULTY("ScreenEdit", "Difficulty");
static LocalizedString ROUTINE_PLAYER("ScreenEdit", "Routine Player");
static LocalizedString CHART_NAME("ScreenEdit", "Chart Name");
static LocalizedString DESCRIPTION("ScreenEdit", "Description");
static LocalizedString CHART_STYLE("ScreenEdit", "Chart Style");
static LocalizedString STEP_AUTHOR("ScreenEdit", "Step Author");
static LocalizedString MAIN_TITLE("ScreenEdit", "Main title");
static LocalizedString SUBTITLE("ScreenEdit", "Subtitle");
static LocalizedString TAP_NOTE_TYPE("ScreenEdit", "Tap Note");
static LocalizedString SEGMENT_TYPE("ScreenEdit", "Segment");
static LocalizedString TAP_STEPS("ScreenEdit", "Tap Steps");
static LocalizedString JUMPS("ScreenEdit", "Jumps");
static LocalizedString HANDS("ScreenEdit", "Hands");
static LocalizedString HOLDS("ScreenEdit", "Holds");
static LocalizedString MINES("ScreenEdit", "Mines");
static LocalizedString ROLLS("ScreenEdit", "Rolls");
static LocalizedString LIFTS("ScreenEdit", "Lifts");
static LocalizedString FAKES("ScreenEdit", "Fakes");
static LocalizedString TIMING_MODE("ScreenEdit", "Timing Mode");
static LocalizedString STEP_TIMING("ScreenEdit", "Step Timing");
static LocalizedString SONG_TIMING("ScreenEdit", "Song Timing");
static LocalizedString BEAT_0_OFFSET("ScreenEdit", "Beat 0 offset");
static LocalizedString PREVIEW_START("ScreenEdit", "Preview Start");
static LocalizedString PREVIEW_LENGTH("ScreenEdit", "Preview Length");

static ThemeMetric<RString> CURRENT_BEAT_FORMAT("ScreenEdit", "CurrentBeatFormat");
static ThemeMetric<RString> CURRENT_SECOND_FORMAT("ScreenEdit", "CurrentSecondFormat");
static ThemeMetric<RString> SNAP_TO_FORMAT("ScreenEdit", "SnapToFormat");
static ThemeMetric<RString> SELECTION_BEAT_BEGIN_FORMAT("ScreenEdit", "SelectionBeatBeginFormat");
static ThemeMetric<RString> SELECTION_BEAT_UNFINISHED_FORMAT("ScreenEdit", "SelectionBeatUnfinishedFormat");
static ThemeMetric<RString> SELECTION_BEAT_END_FORMAT("ScreenEdit", "SelectionBeatEndFormat");
static ThemeMetric<RString> DIFFICULTY_FORMAT("ScreenEdit", "DifficultyFormat");
static ThemeMetric<RString> ROUTINE_PLAYER_FORMAT("ScreenEdit", "RoutinePlayerFormat");
static ThemeMetric<RString> CHART_NAME_FORMAT("ScreenEdit", "ChartNameFormat");
static ThemeMetric<RString> DESCRIPTION_FORMAT("ScreenEdit", "DescriptionFormat");
static ThemeMetric<RString> CHART_STYLE_FORMAT("ScreenEdit", "ChartStyleFormat");
static ThemeMetric<RString> STEP_AUTHOR_FORMAT("ScreenEdit", "StepAuthorFormat");
static ThemeMetric<RString> MAIN_TITLE_FORMAT("ScreenEdit", "MainTitleFormat");
static ThemeMetric<RString> SUBTITLE_FORMAT("ScreenEdit", "SubtitleFormat");
static ThemeMetric<RString> TAP_NOTE_TYPE_FORMAT("ScreenEdit", "TapNoteTypeFormat");
static ThemeMetric<RString> SEGMENT_TYPE_FORMAT("ScreenEdit", "SegmentTypeFormat");
static ThemeMetric<RString> NUM_STEPS_FORMAT("ScreenEdit", "NumStepsFormat");
static ThemeMetric<RString> NUM_JUMPS_FORMAT("ScreenEdit", "NumJumpsFormat");
static ThemeMetric<RString> NUM_HOLDS_FORMAT("ScreenEdit", "NumHoldsFormat");
static ThemeMetric<RString> NUM_MINES_FORMAT("ScreenEdit", "NumMinesFormat");
static ThemeMetric<RString> NUM_HANDS_FORMAT("ScreenEdit", "NumHandsFormat");
static ThemeMetric<RString> NUM_ROLLS_FORMAT("ScreenEdit", "NumRollsFormat");
static ThemeMetric<RString> NUM_LIFTS_FORMAT("ScreenEdit", "NumLiftsFormat");
static ThemeMetric<RString> NUM_FAKES_FORMAT("ScreenEdit", "NumFakesFormat");
static ThemeMetric<RString> NUM_STEPS_FORMAT_TWO_PLAYER("ScreenEdit", "NumStepsFormatTwoPlayer");
static ThemeMetric<RString> NUM_JUMPS_FORMAT_TWO_PLAYER("ScreenEdit", "NumJumpsFormatTwoPlayer");
static ThemeMetric<RString> NUM_HOLDS_FORMAT_TWO_PLAYER("ScreenEdit", "NumHoldsFormatTwoPlayer");
static ThemeMetric<RString> NUM_MINES_FORMAT_TWO_PLAYER("ScreenEdit", "NumMinesFormatTwoPlayer");
static ThemeMetric<RString> NUM_HANDS_FORMAT_TWO_PLAYER("ScreenEdit", "NumHandsFormatTwoPlayer");
static ThemeMetric<RString> NUM_ROLLS_FORMAT_TWO_PLAYER("ScreenEdit", "NumRollsFormatTwoPlayer");
static ThemeMetric<RString> NUM_LIFTS_FORMAT_TWO_PLAYER("ScreenEdit", "NumLiftsFormatTwoPlayer");
static ThemeMetric<RString> NUM_FAKES_FORMAT_TWO_PLAYER("ScreenEdit", "NumFakesFormatTwoPlayer");
static ThemeMetric<RString> TIMING_MODE_FORMAT("ScreenEdit", "TimingModeFormat");
static ThemeMetric<RString> BEAT_0_OFFSET_FORMAT("ScreenEdit", "Beat0OffsetFormat");
static ThemeMetric<RString> PREVIEW_START_FORMAT("ScreenEdit", "PreviewStartFormat");
static ThemeMetric<RString> PREVIEW_LENGTH_FORMAT("ScreenEdit", "PreviewLengthFormat");
void ScreenEdit::UpdateTextInfo()
{
	if( m_pSteps == NULL )
		return;

	// Don't update the text during playback or record.  It causes skips.
	if( m_EditState != STATE_EDITING )
		return;

	if( !m_bTextInfoNeedsUpdate )
		return;

	m_bTextInfoNeedsUpdate = false;

	RString sNoteType = ssprintf( NOTES.GetValue(), NoteTypeToLocalizedString(m_SnapDisplay.GetNoteType()).c_str() );

	RString sText;
	sText += ssprintf( CURRENT_BEAT_FORMAT.GetValue(), CURRENT_BEAT.GetValue().c_str(), GetBeat() );
	sText += ssprintf( CURRENT_SECOND_FORMAT.GetValue(), CURRENT_SECOND.GetValue().c_str(), GetAppropriateTiming().GetElapsedTimeFromBeat(GetBeat()) );
	switch( EDIT_MODE.GetValue() )
	{
	DEFAULT_FAIL( EDIT_MODE.GetValue() );
	case EditMode_Practice:
		break;
	case EditMode_CourseMods:
	case EditMode_Home:
	case EditMode_Full:
		sText += ssprintf( SNAP_TO_FORMAT.GetValue(), SNAP_TO.GetValue().c_str(), sNoteType.c_str() );
		break;
	}

	if( m_NoteFieldEdit.m_iBeginMarker != -1 )
	{
		sText += ssprintf( SELECTION_BEAT_BEGIN_FORMAT.GetValue(), SELECTION_BEAT.GetValue().c_str(), NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );

		if( m_NoteFieldEdit.m_iEndMarker != -1 )
			sText += ssprintf( SELECTION_BEAT_END_FORMAT.GetValue(), NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
		else
			sText += SELECTION_BEAT_UNFINISHED_FORMAT;
	}

	if (EDIT_MODE.GetValue() == EditMode_Full)
	{
		sText += ssprintf( DIFFICULTY_FORMAT.GetValue(), DIFFICULTY.GetValue().c_str(), DifficultyToString( m_pSteps->GetDifficulty() ).c_str() );
		if ( m_InputPlayerNumber != PLAYER_INVALID )
			sText += ssprintf( ROUTINE_PLAYER_FORMAT.GetValue(), ROUTINE_PLAYER.GetValue().c_str(), m_InputPlayerNumber + 1 );
		//sText += ssprintf( DESCRIPTION_FORMAT.GetValue(), DESCRIPTION.GetValue().c_str(), m_pSteps->GetDescription().c_str() );
		sText += ssprintf( CHART_NAME_FORMAT.GetValue(), CHART_NAME.GetValue().c_str(), m_pSteps->GetChartName().c_str() );
		sText += ssprintf( STEP_AUTHOR_FORMAT.GetValue(), STEP_AUTHOR.GetValue().c_str(), m_pSteps->GetCredit().c_str() );
		//sText += ssprintf( CHART_STYLE_FORMAT.GetValue(), CHART_STYLE.GetValue().c_str(), m_pSteps->GetChartStyle().c_str() );
		sText += ssprintf( MAIN_TITLE_FORMAT.GetValue(), MAIN_TITLE.GetValue().c_str(), m_pSong->m_sMainTitle.c_str() );
		if( m_pSong->m_sSubTitle.size() )
			sText += ssprintf( SUBTITLE_FORMAT.GetValue(), SUBTITLE.GetValue().c_str(), m_pSong->m_sSubTitle.c_str() );
		sText += ssprintf( SEGMENT_TYPE_FORMAT.GetValue(), SEGMENT_TYPE.GetValue().c_str(), TimingSegmentTypeToString(currentCycleSegment).c_str() );
        const RString tapnoteType = TapNoteTypeToString( m_selectedTap.type );
		sText += ssprintf( TAP_NOTE_TYPE_FORMAT.GetValue(), TAP_NOTE_TYPE.GetValue().c_str(), tapnoteType.c_str() );
		
		AttackArray &attacks =
			(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
		float beat = GetAppropriateTiming().GetElapsedTimeFromBeat(GetBeat());
		sText += ssprintf("Attack here?: %s\n", FindAttackAtTime(attacks, beat) > -1 ? "YES" : "NO");
	}
	
	GAMESTATE->SetProcessedTimingData(m_pSteps->GetTimingData());
	const StepsTypeCategory &cat = GAMEMAN->GetStepsTypeInfo(m_pSteps->m_StepsType).m_StepsTypeCategory;
	if (cat == StepsTypeCategory_Couple || cat == StepsTypeCategory_Routine)
	{
		pair<int, int> tmp = m_NoteDataEdit.GetNumTapNotesTwoPlayer();
		sText += ssprintf(NUM_STEPS_FORMAT_TWO_PLAYER.GetValue(),
						  TAP_STEPS.GetValue().c_str(),
						  tmp.first, tmp.second);
		tmp = m_NoteDataEdit.GetNumJumpsTwoPlayer();
		sText += ssprintf(NUM_JUMPS_FORMAT_TWO_PLAYER.GetValue(),
						  JUMPS.GetValue().c_str(),
						  tmp.first, tmp.second);
		tmp = m_NoteDataEdit.GetNumHandsTwoPlayer();
		sText += ssprintf(NUM_HANDS_FORMAT_TWO_PLAYER.GetValue(),
						  HANDS.GetValue().c_str(),
						  tmp.first, tmp.second);
		tmp = m_NoteDataEdit.GetNumHoldNotesTwoPlayer();
		sText += ssprintf(NUM_HOLDS_FORMAT_TWO_PLAYER.GetValue(),
						  HOLDS.GetValue().c_str(),
						  tmp.first, tmp.second);
		tmp = m_NoteDataEdit.GetNumMinesTwoPlayer();
		sText += ssprintf(NUM_MINES_FORMAT_TWO_PLAYER.GetValue(),
						  MINES.GetValue().c_str(),
						  tmp.first, tmp.second);
		tmp = m_NoteDataEdit.GetNumRollsTwoPlayer();
		sText += ssprintf(NUM_ROLLS_FORMAT_TWO_PLAYER.GetValue(),
						  ROLLS.GetValue().c_str(),
						  tmp.first, tmp.second);
		tmp = m_NoteDataEdit.GetNumLiftsTwoPlayer();
		sText += ssprintf(NUM_LIFTS_FORMAT_TWO_PLAYER.GetValue(),
						  LIFTS.GetValue().c_str(),
						  tmp.first, tmp.second);
		tmp = m_NoteDataEdit.GetNumFakesTwoPlayer();
		sText += ssprintf(NUM_FAKES_FORMAT_TWO_PLAYER.GetValue(),
						  FAKES.GetValue().c_str(),
						  tmp.first, tmp.second);
	}
	else
	{
		sText += ssprintf( NUM_STEPS_FORMAT.GetValue(), TAP_STEPS.GetValue().c_str(), m_NoteDataEdit.GetNumTapNotes() );
		sText += ssprintf( NUM_JUMPS_FORMAT.GetValue(), JUMPS.GetValue().c_str(), m_NoteDataEdit.GetNumJumps() );
		sText += ssprintf( NUM_HANDS_FORMAT.GetValue(), HANDS.GetValue().c_str(), m_NoteDataEdit.GetNumHands() );
		sText += ssprintf( NUM_HOLDS_FORMAT.GetValue(), HOLDS.GetValue().c_str(), m_NoteDataEdit.GetNumHoldNotes() );
		sText += ssprintf( NUM_MINES_FORMAT.GetValue(), MINES.GetValue().c_str(), m_NoteDataEdit.GetNumMines() );
		sText += ssprintf( NUM_ROLLS_FORMAT.GetValue(), ROLLS.GetValue().c_str(), m_NoteDataEdit.GetNumRolls() );
		sText += ssprintf( NUM_LIFTS_FORMAT.GetValue(), LIFTS.GetValue().c_str(), m_NoteDataEdit.GetNumLifts() );
		sText += ssprintf( NUM_FAKES_FORMAT.GetValue(), FAKES.GetValue().c_str(), m_NoteDataEdit.GetNumFakes() );
	}
	switch( EDIT_MODE.GetValue() )
	{
	DEFAULT_FAIL( EDIT_MODE.GetValue() );
	case EditMode_Practice:
	case EditMode_CourseMods:
	case EditMode_Home:
		break;
	case EditMode_Full:
		sText += ssprintf( TIMING_MODE_FORMAT.GetValue(),
				  TIMING_MODE.GetValue().c_str(),
				  ( GAMESTATE->m_bIsUsingStepTiming ?
				   STEP_TIMING.GetValue().c_str() :
				   SONG_TIMING.GetValue().c_str() ) );
		sText += ssprintf( BEAT_0_OFFSET_FORMAT.GetValue(), 
				  BEAT_0_OFFSET.GetValue().c_str(), 
				  GetAppropriateTiming().m_fBeat0OffsetInSeconds );
		sText += ssprintf( PREVIEW_START_FORMAT.GetValue(), PREVIEW_START.GetValue().c_str(), m_pSong->m_fMusicSampleStartSeconds );
		sText += ssprintf( PREVIEW_LENGTH_FORMAT.GetValue(), PREVIEW_LENGTH.GetValue().c_str(), m_pSong->m_fMusicSampleLengthSeconds );
		break;
	}

	m_textInfo.SetText( sText );
	
	GAMESTATE->SetProcessedTimingData(NULL);
}

void ScreenEdit::DrawPrimitives()
{

	if( m_pSoundMusic->IsPlaying() )
	{
		ScreenWithMenuElements::DrawPrimitives();
		return;
	}

	// HACK:  Draw using the trailing beat
	PlayerState *pPlayerState = const_cast<PlayerState *> (m_NoteFieldEdit.GetPlayerState());
	
	float fPlayerSongBeat = pPlayerState->m_Position.m_fSongBeat;	// save song beat
	float fPlayerSongBeatNoOffset = pPlayerState->m_Position.m_fSongBeatNoOffset;
	float fPlayerSongBeatVisible = pPlayerState->m_Position.m_fSongBeatVisible;

	float fGameSongBeat = GAMESTATE->m_Position.m_fSongBeat;	// save song beat
	float fGameSongBeatNoOffset = GAMESTATE->m_Position.m_fSongBeatNoOffset;
	float fGameSongBeatVisible = GAMESTATE->m_Position.m_fSongBeatVisible;

	pPlayerState->m_Position.m_fSongBeat = m_fTrailingBeat;	// put trailing beat in effect
	pPlayerState->m_Position.m_fSongBeatNoOffset = m_fTrailingBeat;	// put trailing beat in effect
	pPlayerState->m_Position.m_fSongBeatVisible = m_fTrailingBeat;	// put trailing beat in effect
	
	GAMESTATE->m_Position.m_fSongBeat = m_fTrailingBeat;	// put trailing beat in effect
	GAMESTATE->m_Position.m_fSongBeatNoOffset = m_fTrailingBeat;	// put trailing beat in effect
	GAMESTATE->m_Position.m_fSongBeatVisible = m_fTrailingBeat;	// put trailing beat in effect

	ScreenWithMenuElements::DrawPrimitives();

	pPlayerState->m_Position.m_fSongBeat = fPlayerSongBeat;	// restore real song beat
	pPlayerState->m_Position.m_fSongBeatNoOffset = fPlayerSongBeatNoOffset;
	pPlayerState->m_Position.m_fSongBeatVisible = fPlayerSongBeatVisible;
	
	GAMESTATE->m_Position.m_fSongBeat = fGameSongBeat;	// restore real song beat
	GAMESTATE->m_Position.m_fSongBeatNoOffset = fGameSongBeatNoOffset;
	GAMESTATE->m_Position.m_fSongBeatVisible = fGameSongBeatVisible;

}

bool ScreenEdit::Input( const InputEventPlus &input )
{
//	LOG->Trace( "ScreenEdit::Input()" );
	
	// invalidate input if cmd/meta is being held.
	if( INPUTFILTER->IsBeingPressed(DeviceInput(DEVICE_KEYBOARD, KEY_LMETA)) ||
	    INPUTFILTER->IsBeingPressed(DeviceInput(DEVICE_KEYBOARD, KEY_RMETA)) )
		return false;

	if( m_In.IsTransitioning() || m_Out.IsTransitioning() )
		return false;

	EditButton EditB = DeviceToEdit( input.DeviceI );
	if( EditB == EditButton_Invalid )
		EditB = MenuButtonToEditButton( input.MenuI );

	if( EditB == EDIT_BUTTON_REMOVE_NOTE )
	{
		/* Ugly: we need to know when the button was pressed or released, so we
		 * can clamp operations to that time.  Should InputFilter keep track of
		 * last release, too? */
		m_bRemoveNoteButtonDown = (input.type != IET_RELEASE);
		m_RemoveNoteButtonLastChanged.Touch();
	}

	switch( m_EditState )
	{
	DEFAULT_FAIL( m_EditState );
	case STATE_EDITING:
		m_bTextInfoNeedsUpdate = true;
		return InputEdit( input, EditB );
	case STATE_RECORDING:
		return InputRecord( input, EditB );
	case STATE_RECORDING_PAUSED:
		return InputRecordPaused( input, EditB );
	case STATE_PLAYING:
		return InputPlay( input, EditB );
	}
}

static void ShiftToRightSide( int &iCol, int iNumTracks )
{
	switch( GAMESTATE->GetCurrentStyle()->m_StyleType )
	{
	DEFAULT_FAIL( GAMESTATE->GetCurrentStyle()->m_StyleType );
	case StyleType_OnePlayerOneSide:
		break;
	case StyleType_TwoPlayersTwoSides:
	case StyleType_OnePlayerTwoSides:
	case StyleType_TwoPlayersSharedSides:
		iCol += iNumTracks/2;
		break;
	}
}

static LocalizedString BG_CHANGE_STEP_TIMING	( "ScreenEdit", "You must be in Song Timing Mode to edit BG Changes." );
static LocalizedString ALTER_MENU_NO_SELECTION	( "ScreenEdit", "You must have an area selected to enter the Alter Menu." );
static LocalizedString SWITCHED_TO		( "ScreenEdit", "Switched to" );
static LocalizedString NO_BACKGROUNDS_AVAILABLE	( "ScreenEdit", "No backgrounds available" );
static ThemeMetric<bool> INVERT_SCROLL_BUTTONS	( "ScreenEdit", "InvertScrollSpeedButtons" );

bool ScreenEdit::InputEdit( const InputEventPlus &input, EditButton EditB )
{
	if( input.type == IET_RELEASE )
	{
		if( EditPressed( EDIT_BUTTON_SCROLL_SELECT, input.DeviceI ) )
			m_iShiftAnchor = -1;
		// XXX Key releases usually don't count as "handled," but what
		// does it mean in this case?
		return false;
	}
	const TimingData &sTiming = GetAppropriateTiming();
	float playerBeat = GetAppropriatePosition().m_fSongBeat;
	int beatsPerMeasure = sTiming.GetTimeSignatureSegmentAtBeat( playerBeat )->GetNum();
	
	switch( EditB )
	{
	case EDIT_BUTTON_COLUMN_0:
	case EDIT_BUTTON_COLUMN_1:
	case EDIT_BUTTON_COLUMN_2:
	case EDIT_BUTTON_COLUMN_3:
	case EDIT_BUTTON_COLUMN_4:
	case EDIT_BUTTON_COLUMN_5:
	case EDIT_BUTTON_COLUMN_6:
	case EDIT_BUTTON_COLUMN_7:
	case EDIT_BUTTON_COLUMN_8:
	case EDIT_BUTTON_COLUMN_9:
		{
			if( input.type != IET_FIRST_PRESS )
				return false;	// We only care about first presses

			int iCol = EditB - EDIT_BUTTON_COLUMN_0;


			// Alt + number = input to right half
			if( EditIsBeingPressed(EDIT_BUTTON_RIGHT_SIDE) )
				ShiftToRightSide( iCol, m_NoteDataEdit.GetNumTracks() );
			
			if( iCol >= m_NoteDataEdit.GetNumTracks() )
				return false; // this button is not in the range of columns for this Style

			const float fSongBeat = GetBeat();
			const int iSongIndex = BeatToNoteRow( fSongBeat );
			
			// check for to see if the user intended to remove a HoldNote
			int iHeadRow;
			if( m_NoteDataEdit.IsHoldNoteAtRow( iCol, iSongIndex, &iHeadRow ) )
			{
				m_soundRemoveNote.Play();
				SetDirty( true );
				SaveUndo();
				m_NoteDataEdit.SetTapNote( iCol, iHeadRow, TAP_EMPTY );
				// Don't CheckNumberOfNotesAndUndo.  We don't want to revert any change that removes notes.
			}
			else if( m_NoteDataEdit.GetTapNote(iCol, iSongIndex).type != TapNote::empty )
			{
				m_soundRemoveNote.Play();
				SetDirty( true );
				SaveUndo();
				m_NoteDataEdit.SetTapNote( iCol, iSongIndex, TAP_EMPTY );
				// Don't CheckNumberOfNotesAndUndo.  We don't want to revert any change that removes notes.
			}
			else if( EditIsBeingPressed(EDIT_BUTTON_LAY_TAP_ATTACK) )
			{
				g_iLastInsertTapAttackTrack = iCol;
				EditMiniMenu( &g_InsertTapAttack, SM_BackFromInsertTapAttack );
			}
			else
			{
				m_soundAddNote.Play();
				SetDirty( true );
				SaveUndo();
				TapNote tn = m_selectedTap;
				tn.pn = m_InputPlayerNumber;
				m_NoteDataEdit.SetTapNote(iCol, iSongIndex, tn );
				CheckNumberOfNotesAndUndo();
			}
		}
		return true;
			
	case EDIT_BUTTON_CYCLE_TAP_LEFT:
		{
			switch ( m_selectedTap.type )
			{
				case TapNote::tap:	m_selectedTap = TAP_ORIGINAL_FAKE;	break;
				case TapNote::mine:	m_selectedTap = TAP_ORIGINAL_TAP;	break;
				case TapNote::lift:	m_selectedTap = TAP_ORIGINAL_MINE;	break;
				case TapNote::fake:	m_selectedTap = TAP_ORIGINAL_LIFT;	break;
				DEFAULT_FAIL( m_selectedTap.type );
			}
			return true;
		}
	case EDIT_BUTTON_CYCLE_TAP_RIGHT:
		{
			switch ( m_selectedTap.type )
			{
				case TapNote::tap:	m_selectedTap = TAP_ORIGINAL_MINE;	break;
				case TapNote::mine:	m_selectedTap = TAP_ORIGINAL_LIFT;	break;
				case TapNote::lift:	m_selectedTap = TAP_ORIGINAL_FAKE;	break;
				case TapNote::fake:	m_selectedTap = TAP_ORIGINAL_TAP;	break;
				DEFAULT_FAIL( m_selectedTap.type );
			}
			return true;
		}
	case EDIT_BUTTON_CYCLE_SEGMENT_LEFT:
	{
		int tmp = enum_add2( this->currentCycleSegment, -1 );
		wrap( *ConvertValue<int>(&tmp), NUM_TimingSegmentType );
		currentCycleSegment = (TimingSegmentType)tmp;
		return true;
	}
	case EDIT_BUTTON_CYCLE_SEGMENT_RIGHT:
	{
		int tmp = enum_add2( this->currentCycleSegment, +1 );
		wrap( *ConvertValue<int>(&tmp), NUM_TimingSegmentType );
		currentCycleSegment = (TimingSegmentType)tmp;
		return true;
	}
	case EDIT_BUTTON_SCROLL_SPEED_UP:
	case EDIT_BUTTON_SCROLL_SPEED_DOWN:
		{
			PlayerState *pPlayerState = const_cast<PlayerState *> (m_NoteFieldEdit.GetPlayerState());
			float fScrollSpeed = pPlayerState->m_PlayerOptions.GetSong().m_fScrollSpeed;

			const float fSpeeds[] = { 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f, 8.0f };
			int iSpeed = 0;
			for( unsigned i = 0; i < ARRAYLEN(fSpeeds); ++i )
			{
				if( fSpeeds[i] == fScrollSpeed )
				{
					iSpeed = i;
					break;
				}
			}

			switch( EditB )
			{
			DEFAULT_FAIL(EditB);
			case EDIT_BUTTON_SCROLL_SPEED_DOWN:
				INVERT_SCROLL_BUTTONS ? ++iSpeed : --iSpeed;
				break;
			case EDIT_BUTTON_SCROLL_SPEED_UP:
				INVERT_SCROLL_BUTTONS ? --iSpeed : ++iSpeed;
				break;
			}
			iSpeed = clamp( iSpeed, 0, (int) ARRAYLEN(fSpeeds)-1 );

			if( fSpeeds[iSpeed] != fScrollSpeed )
			{
				m_soundMarker.Play();
				fScrollSpeed = fSpeeds[iSpeed];
			}

			PO_GROUP_ASSIGN( pPlayerState->m_PlayerOptions, ModsLevel_Song, m_fScrollSpeed, fScrollSpeed );
		}

		return true;
	case EDIT_BUTTON_SCROLL_HOME:
		ScrollTo( 0 );
		return true;
	case EDIT_BUTTON_SCROLL_END:
		ScrollTo( m_NoteDataEdit.GetLastBeat() );
		return true;
	case EDIT_BUTTON_SCROLL_UP_LINE:
	case EDIT_BUTTON_SCROLL_UP_PAGE:
	case EDIT_BUTTON_SCROLL_UP_TS:
	case EDIT_BUTTON_SCROLL_DOWN_LINE:
	case EDIT_BUTTON_SCROLL_DOWN_PAGE:
	case EDIT_BUTTON_SCROLL_DOWN_TS:
		{
			float fBeatsToMove=0.f;
			switch( EditB )
			{
			DEFAULT_FAIL( EditB );
			case EDIT_BUTTON_SCROLL_UP_LINE:
			case EDIT_BUTTON_SCROLL_DOWN_LINE:
				fBeatsToMove = NoteTypeToBeat( m_SnapDisplay.GetNoteType() );
				if( EditB == EDIT_BUTTON_SCROLL_UP_LINE )
					fBeatsToMove *= -1;
				break;
			case EDIT_BUTTON_SCROLL_UP_PAGE:
			case EDIT_BUTTON_SCROLL_DOWN_PAGE:
				fBeatsToMove = 4;
				if( EditB == EDIT_BUTTON_SCROLL_UP_PAGE )
					fBeatsToMove *= -1;
				break;
			case EDIT_BUTTON_SCROLL_UP_TS:
			case EDIT_BUTTON_SCROLL_DOWN_TS:
				fBeatsToMove = beatsPerMeasure;
				if( EditB == EDIT_BUTTON_SCROLL_UP_TS )
					fBeatsToMove *= -1;
				break;
			}

			if( m_PlayerStateEdit.m_PlayerOptions.GetSong().m_fScrolls[PlayerOptions::SCROLL_REVERSE] > 0.5 )
				fBeatsToMove *= -1;

			float fDestinationBeat = GetBeat() + fBeatsToMove;
			fDestinationBeat = Quantize( fDestinationBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) );

			ScrollTo( fDestinationBeat );
		}
		return true;
	case EDIT_BUTTON_SCROLL_NEXT_MEASURE:
		{
			float fDestinationBeat = GetBeat() + beatsPerMeasure;
			fDestinationBeat = ftruncf( fDestinationBeat, (float)beatsPerMeasure );
			ScrollTo( fDestinationBeat );
			return true;
		}
	case EDIT_BUTTON_SCROLL_PREV_MEASURE:
		{
			float fDestinationBeat = QuantizeUp( GetBeat(), (float)beatsPerMeasure );
			fDestinationBeat -= (float)beatsPerMeasure;
			ScrollTo( fDestinationBeat );
			return true;
		}
	case EDIT_BUTTON_SCROLL_NEXT:
		{
			int iRow = BeatToNoteRow( GetBeat() );
			NoteDataUtil::GetNextEditorPosition( m_NoteDataEdit, iRow );
			ScrollTo( NoteRowToBeat(iRow) );
		}
		return true;
	case EDIT_BUTTON_SCROLL_PREV:
		{
			int iRow = BeatToNoteRow( GetBeat() );
			NoteDataUtil::GetPrevEditorPosition( m_NoteDataEdit, iRow );
			ScrollTo( NoteRowToBeat(iRow) );
		}
		return true;
	case EDIT_BUTTON_SEGMENT_NEXT:
		{
			// TODO: Work around Stops and Delays. We MAY have to separate them.
			const TimingData &timing = GetAppropriateTiming();
			ScrollTo(timing.GetNextSegmentBeatAtBeat(this->currentCycleSegment,
													 GetBeat()));
		}
		return true;
	case EDIT_BUTTON_SEGMENT_PREV:
		{
			// TODO: Work around Stops and Delays. We MAY have to separate them.
			const TimingData &timing = GetAppropriateTiming();
			ScrollTo(timing.GetPreviousSegmentBeatAtBeat(this->currentCycleSegment,
														 GetBeat()));
		}
		return true;
	case EDIT_BUTTON_SNAP_NEXT:
		if( m_SnapDisplay.PrevSnapMode() )
			OnSnapModeChange();
		return true;
	case EDIT_BUTTON_SNAP_PREV:
		if( m_SnapDisplay.NextSnapMode() )
			OnSnapModeChange();
		return true;
	case EDIT_BUTTON_LAY_SELECT:
		{
			const int iCurrentRow = BeatToNoteRow(GetAppropriatePosition().m_fSongBeat);
			if( m_NoteFieldEdit.m_iBeginMarker==-1 && m_NoteFieldEdit.m_iEndMarker==-1 )
			{
				// lay begin marker
				m_NoteFieldEdit.m_iBeginMarker = BeatToNoteRow(GetAppropriatePosition().m_fSongBeat);
			}
			else if( m_NoteFieldEdit.m_iEndMarker==-1 )	// only begin marker is laid
			{
				if( iCurrentRow == m_NoteFieldEdit.m_iBeginMarker )
				{
					m_NoteFieldEdit.m_iBeginMarker = -1;
				}
				else
				{
					m_NoteFieldEdit.m_iEndMarker = max( m_NoteFieldEdit.m_iBeginMarker, iCurrentRow );
					m_NoteFieldEdit.m_iBeginMarker = min( m_NoteFieldEdit.m_iBeginMarker, iCurrentRow );
				}
			}
			else	// both markers are laid
			{
				m_NoteFieldEdit.m_iBeginMarker = iCurrentRow;
				m_NoteFieldEdit.m_iEndMarker = -1;
			}
			m_soundMarker.Play();
		}
		return true;
	case EDIT_BUTTON_OPEN_AREA_MENU:
		{
			// TODO: Improve behavior if timing changes are shifted down on beat 0.
			g_AreaMenu.rows[shift_pauses_forward].bEnabled = (GetBeat() != 0);
			g_AreaMenu.rows[paste_at_current_beat].bEnabled = !m_Clipboard.IsEmpty();
			g_AreaMenu.rows[paste_at_begin_marker].bEnabled = !m_Clipboard.IsEmpty() != 0 && m_NoteFieldEdit.m_iBeginMarker!=-1;
			g_AreaMenu.rows[undo].bEnabled = m_bHasUndo;
			EditMiniMenu( &g_AreaMenu, SM_BackFromAreaMenu );
		}
		return true;
	case EDIT_BUTTON_OPEN_ALTER_MENU:
		{
			bool bAreaSelected = m_NoteFieldEdit.m_iBeginMarker!=-1 && m_NoteFieldEdit.m_iEndMarker!=-1;
			if (!bAreaSelected)
			{
				SCREENMAN->SystemMessage( ALTER_MENU_NO_SELECTION );
				SCREENMAN->PlayInvalidSound();
			}
			else 
			{
				// TODO: Improve interaction when field is 0.
				g_AlterMenu.rows[convert_to_delay].bEnabled = (m_NoteFieldEdit.m_iBeginMarker > 0);
				g_AlterMenu.rows[convert_to_pause].bEnabled = (m_NoteFieldEdit.m_iBeginMarker > 0);
				bool isRoutine = (m_InputPlayerNumber != PLAYER_INVALID);
				g_AlterMenu.rows[routine_invert_notes].bEnabled = isRoutine;
				g_AlterMenu.rows[routine_mirror_1_to_2].bEnabled = isRoutine;
				g_AlterMenu.rows[routine_mirror_2_to_1].bEnabled = isRoutine;
				EditMiniMenu(&g_AlterMenu, SM_BackFromAlterMenu);
			}
			return true;

		}
	case EDIT_BUTTON_OPEN_EDIT_MENU:
		EditMiniMenu( &g_MainMenu, SM_BackFromMainMenu );
		return true;
	case EDIT_BUTTON_OPEN_INPUT_HELP:
		DoHelp();
		return true;
	case EDIT_BUTTON_OPEN_TIMING_MENU:
		{
			DisplayTimingMenu();
			return true;
		}
	case EDIT_BUTTON_OPEN_NEXT_STEPS:
	case EDIT_BUTTON_OPEN_PREV_STEPS:
		{
			// don't keep undo when changing Steps
			ClearUndo();

			// get the second of the current step.
			float curSecond = GetAppropriateTiming().GetElapsedTimeFromBeat(GetBeat());
			
			// save current steps
			Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
			ASSERT( pSteps != NULL );
			pSteps->SetNoteData( m_NoteDataEdit );

			// Get all Steps of this StepsType
			const StepsType st = pSteps->m_StepsType;
			vector<Steps*> vSteps;
			SongUtil::GetSteps( GAMESTATE->m_pCurSong, vSteps, st );

			// Sort them by difficulty.
			StepsUtil::SortStepsByTypeAndDifficulty( vSteps );

			// Find out what index the current Steps are
			vector<Steps*>::iterator it = find( vSteps.begin(), vSteps.end(), pSteps );
			ASSERT( it != vSteps.end() );

			switch( EditB )
			{
			DEFAULT_FAIL( EditB );
			case EDIT_BUTTON_OPEN_PREV_STEPS:	
				if( it==vSteps.begin() )
				{
					SCREENMAN->PlayInvalidSound();
					return true;
				}
				it--;
				break;
			case EDIT_BUTTON_OPEN_NEXT_STEPS:
				it++;
				if( it==vSteps.end() )
				{
					SCREENMAN->PlayInvalidSound();
					return true;
				}
				break;
			}

			pSteps = *it;
			GAMESTATE->m_pCurSteps[PLAYER_1].Set( pSteps );
			m_pSteps = pSteps;
			pSteps->GetNoteData( m_NoteDataEdit );

			RString s = ssprintf(
				SWITCHED_TO.GetValue() + " %s %s '%s' (%d of %d)",
				GAMEMAN->GetStepsTypeInfo( st ).szName,
				DifficultyToString( pSteps->GetDifficulty() ).c_str(),
				pSteps->GetChartName().c_str(),
				it - vSteps.begin() + 1,
				int(vSteps.size()) );
			SCREENMAN->SystemMessage( s );
			m_soundSwitchSteps.Play();
			
			ScrollTo( GetAppropriateTiming().GetBeatFromElapsedTime(curSecond) );
		}
		return true;
	case EDIT_BUTTON_BPM_UP:
	case EDIT_BUTTON_BPM_DOWN:
		{
			float fBPM = GetAppropriateTiming().GetBPMAtBeat( GetBeat() );
			float fDelta;
			switch( EditB )
			{
			DEFAULT_FAIL( EditB );
			case EDIT_BUTTON_BPM_UP:	fDelta = +0.020f;	break;
			case EDIT_BUTTON_BPM_DOWN:	fDelta = -0.020f;	break;
			}
			if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
			{
				fDelta /= 20;	// .001 bpm
			}
			else if( input.type == IET_REPEAT )
			{
				if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
					fDelta *= 10;
				else
					fDelta *= 40;
			}

			float fNewBPM = fBPM + fDelta;
			GetAppropriateTimingForUpdate().SetBPMAtBeat( GetBeat(), fNewBPM );
			(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
			SetDirty( true );
		}
		return true;
	case EDIT_BUTTON_STOP_UP:
	case EDIT_BUTTON_STOP_DOWN:
		{
			float fDelta;
			switch( EditB )
			{
			DEFAULT_FAIL( EditB );
			case EDIT_BUTTON_STOP_UP:	fDelta = +0.020f;	break;
			case EDIT_BUTTON_STOP_DOWN:	fDelta = -0.020f;	break;
			}
			if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
			{
				fDelta /= 20; // 1ms
			}
			else if( input.type == IET_REPEAT )
			{
				if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
					fDelta *= 10;
				else
					fDelta *= 40;
			}

			// is there a StopSegment on the current row?
			TimingData & timing = GetAppropriateTimingForUpdate();
			StopSegment *seg = timing.GetStopSegmentAtRow( GetRow() );
			int i = timing.GetSegmentIndexAtRow(SEGMENT_STOP, GetRow());
			if (i == -1 || seg->GetRow() != GetRow()) // invalid
			{
				if( fDelta > 0 )
					timing.AddSegment( StopSegment(GetRow(), fDelta) );
				else
					return false;
			}
			else
			{
				vector<TimingSegment *> &stops = timing.GetTimingSegments(SEGMENT_STOP);
				seg->SetPause(seg->GetPause() + fDelta);
				if( seg->GetPause() <= 0 )
					stops.erase( stops.begin()+i, stops.begin()+i+1);
			}

			(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
			SetDirty( true );
		}
		return true;
	// TODO: Combine the stop and delay call somehow?
	case EDIT_BUTTON_DELAY_UP:
	case EDIT_BUTTON_DELAY_DOWN:
		{
			float fDelta;
			switch( EditB )
			{
					DEFAULT_FAIL( EditB );
				case EDIT_BUTTON_DELAY_UP:		fDelta = +0.020f;	break;
				case EDIT_BUTTON_DELAY_DOWN:	fDelta = -0.020f;	break;
			}
			if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
			{
				fDelta /= 20; // 1ms
			}
			else if( input.type == IET_REPEAT )
			{
				if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
					fDelta *= 10;
				else
					fDelta *= 40;
			}
			
			// is there a StopSegment on the current row?
			TimingData & timing = GetAppropriateTimingForUpdate();
			DelaySegment *seg = timing.GetDelaySegmentAtRow( GetRow() );
			int i = timing.GetSegmentIndexAtRow(SEGMENT_DELAY, GetRow());
			if (i == -1 || seg->GetRow() != GetRow()) // invalid
			{
				if( fDelta > 0 )
					timing.AddSegment( DelaySegment(GetRow(), fDelta) );
				else
					return false;
			}
			else
			{
				vector<TimingSegment *> &stops = timing.GetTimingSegments(SEGMENT_DELAY);
				seg->SetPause(seg->GetPause() + fDelta);
				if( seg->GetPause() <= 0 )
					stops.erase( stops.begin()+i, stops.begin()+i+1);
			}
			
			(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
			SetDirty( true );
		}
		return true;
	case EDIT_BUTTON_OFFSET_UP:
	case EDIT_BUTTON_OFFSET_DOWN:
		{
			float fDelta;
			switch( EditB )
			{
			DEFAULT_FAIL( EditB );
			case EDIT_BUTTON_OFFSET_DOWN:	fDelta = -0.02f;	break;
			case EDIT_BUTTON_OFFSET_UP:	fDelta = +0.02f;	break;
			}
			if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
			{
				fDelta /= 20; /* 1ms */
			}
			else if( input.type == IET_REPEAT )
			{
				if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
					fDelta *= 10;
				else
					fDelta *= 40;
			}
			GetAppropriateTimingForUpdate().m_fBeat0OffsetInSeconds += fDelta;
			(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
			if (GAMESTATE->m_bIsUsingStepTiming)
			{
				GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.UpdateStartTimes(fDelta);
			}
			else
			{
				GAMESTATE->m_pCurSong->m_Attacks.UpdateStartTimes(fDelta);
				GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds += fDelta;
			}
			SetDirty( true );
		}
		return true;
	case EDIT_BUTTON_SAMPLE_START_UP:
	case EDIT_BUTTON_SAMPLE_START_DOWN:
	case EDIT_BUTTON_SAMPLE_LENGTH_DOWN:
	case EDIT_BUTTON_SAMPLE_LENGTH_UP:
		{
			float fDelta;
			switch( EditB )
			{
			DEFAULT_FAIL( EditB );
			case EDIT_BUTTON_SAMPLE_START_DOWN:		fDelta = -0.02f;	break;
			case EDIT_BUTTON_SAMPLE_START_UP:		fDelta = +0.02f;	break;
			case EDIT_BUTTON_SAMPLE_LENGTH_DOWN:		fDelta = -0.02f;	break;
			case EDIT_BUTTON_SAMPLE_LENGTH_UP:		fDelta = +0.02f;	break;
			}
			
			if( input.type == IET_REPEAT )
			{
				if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
					fDelta *= 10;
				else
					fDelta *= 40;
			}

			if( EditB == EDIT_BUTTON_SAMPLE_LENGTH_DOWN || EditB == EDIT_BUTTON_SAMPLE_LENGTH_UP )
			{
				m_pSong->m_fMusicSampleLengthSeconds += fDelta;
				m_pSong->m_fMusicSampleLengthSeconds = max(m_pSong->m_fMusicSampleLengthSeconds,0);
			}
			else
			{
				m_pSong->m_fMusicSampleStartSeconds += fDelta;
				m_pSong->m_fMusicSampleStartSeconds = max(m_pSong->m_fMusicSampleStartSeconds,0);
			}
			(fDelta>0 ? m_soundValueIncrease : m_soundValueDecrease).Play();
			SetDirty( true );
		}
		return true;
	case EDIT_BUTTON_PLAY_SAMPLE_MUSIC:
		PlayPreviewMusic();
		return true;
	case EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU:
	case EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU:
		if( !GAMESTATE->m_bIsUsingStepTiming )
		{
			switch( EditB )
			{
			DEFAULT_FAIL( EditB );
			case EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU: g_CurrentBGChangeLayer = BACKGROUND_LAYER_1; break;
			case EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU: g_CurrentBGChangeLayer = BACKGROUND_LAYER_2; break;
			}

			{
				// Fill in option names
				vector<RString> vThrowAway;

				MenuDef &menu = g_BackgroundChange;

				menu.rows[layer].choices[0] = ssprintf("%d",g_CurrentBGChangeLayer);
				BackgroundUtil::GetBackgroundTransitions(	"", vThrowAway, menu.rows[transition].choices );
				g_BackgroundChange.rows[transition].choices.insert( g_BackgroundChange.rows[transition].choices.begin(), "None" );	// add "no transition"
				BackgroundUtil::GetBackgroundEffects(		"", vThrowAway, menu.rows[effect].choices );
				menu.rows[effect].choices.insert( menu.rows[effect].choices.begin(), "Default" );	// add "default effect"

				BackgroundUtil::GetSongBGAnimations(   m_pSong, "", vThrowAway, menu.rows[file1_song_bganimation].choices );
				BackgroundUtil::GetSongMovies(         m_pSong, "", vThrowAway, menu.rows[file1_song_movie].choices );
				BackgroundUtil::GetSongBitmaps(        m_pSong, "", vThrowAway, menu.rows[file1_song_still].choices );
				BackgroundUtil::GetGlobalBGAnimations( m_pSong, "", vThrowAway, menu.rows[file1_global_bganimation].choices );	// NULL to get all background files
				BackgroundUtil::GetGlobalRandomMovies( m_pSong, "", vThrowAway, menu.rows[file1_global_movie].choices, false, false );	// all backgrounds
				BackgroundUtil::GetGlobalRandomMovies( m_pSong, "", vThrowAway, menu.rows[file1_global_movie_song_group].choices, false, true );	// song group's backgrounds
				BackgroundUtil::GetGlobalRandomMovies( m_pSong, "", vThrowAway, menu.rows[file1_global_movie_song_group_and_genre].choices, true, true );	// song group and genre's backgrounds

				menu.rows[file2_type].choices					= menu.rows[file1_type].choices;
				menu.rows[file2_song_bganimation].choices			= menu.rows[file1_song_bganimation].choices;
				menu.rows[file2_song_movie].choices				= menu.rows[file1_song_movie].choices;
				menu.rows[file2_song_still].choices				= menu.rows[file1_song_still].choices;
				menu.rows[file2_global_bganimation].choices			= menu.rows[file1_global_bganimation].choices;
				menu.rows[file2_global_movie].choices				= menu.rows[file1_global_movie].choices;
				menu.rows[file2_global_movie_song_group].choices		= menu.rows[file1_global_movie_song_group].choices;
				menu.rows[file2_global_movie_song_group_and_genre].choices	= menu.rows[file1_global_movie_song_group_and_genre].choices;


				// Fill in lines enabled/disabled
				bool bAlreadyBGChangeHere = false;
				BackgroundChange bgChange; 
				FOREACH( BackgroundChange, m_pSong->GetBackgroundChanges(g_CurrentBGChangeLayer), bgc )
				{
					if( bgc->m_fStartBeat == GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat )
					{
						bAlreadyBGChangeHere = true;
						bgChange = *bgc;
					}
				}

#define FILL_ENABLED( x )	menu.rows[x].bEnabled	= menu.rows[x].choices.size() > 0;
				FILL_ENABLED( transition );
				FILL_ENABLED( effect );
				FILL_ENABLED( file1_song_bganimation );
				FILL_ENABLED( file1_song_movie );
				FILL_ENABLED( file1_song_still );
				FILL_ENABLED( file1_global_bganimation );
				FILL_ENABLED( file1_global_movie );
				FILL_ENABLED( file1_global_movie_song_group );
				FILL_ENABLED( file1_global_movie_song_group_and_genre );
				FILL_ENABLED( file2_song_bganimation );
				FILL_ENABLED( file2_song_movie );
				FILL_ENABLED( file2_song_still );
				FILL_ENABLED( file2_global_bganimation );
				FILL_ENABLED( file2_global_movie );
				FILL_ENABLED( file2_global_movie_song_group );
				FILL_ENABLED( file2_global_movie_song_group_and_genre );
#undef FILL_ENABLED
				menu.rows[delete_change].bEnabled = bAlreadyBGChangeHere;

				// set default choices
				menu.rows[rate].					SetDefaultChoiceIfPresent( ssprintf("%2.0f%%",bgChange.m_fRate*100) );
				menu.rows[transition].					SetDefaultChoiceIfPresent( bgChange.m_sTransition );
				menu.rows[effect].					SetDefaultChoiceIfPresent( bgChange.m_def.m_sEffect );
				menu.rows[file1_type].iDefaultChoice			= none;
				if( bgChange.m_def.m_sFile1 == RANDOM_BACKGROUND_FILE )								menu.rows[file1_type].iDefaultChoice	= dynamic_random;
				if( menu.rows[file1_song_bganimation].			SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) )	menu.rows[file1_type].iDefaultChoice	= song_bganimation;
				if( menu.rows[file1_song_movie].			SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) )	menu.rows[file1_type].iDefaultChoice	= song_movie;
				if( menu.rows[file1_song_still].			SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) )	menu.rows[file1_type].iDefaultChoice	= song_bitmap;
				if( menu.rows[file1_global_bganimation].		SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) )	menu.rows[file1_type].iDefaultChoice	= global_bganimation;
				if( menu.rows[file1_global_movie].			SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) )	menu.rows[file1_type].iDefaultChoice	= global_movie;
				if( menu.rows[file1_global_movie_song_group].		SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) )	menu.rows[file1_type].iDefaultChoice	= global_movie_song_group;
				if( menu.rows[file1_global_movie_song_group_and_genre].	SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile1 ) )	menu.rows[file1_type].iDefaultChoice	= global_movie_song_group_and_genre;
				menu.rows[file2_type].iDefaultChoice			= none;
				if( bgChange.m_def.m_sFile2 == RANDOM_BACKGROUND_FILE )								menu.rows[file2_type].iDefaultChoice	= dynamic_random;
				if( menu.rows[file2_song_bganimation].			SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) )	menu.rows[file2_type].iDefaultChoice	= song_bganimation;
				if( menu.rows[file2_song_movie].			SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) )	menu.rows[file2_type].iDefaultChoice	= song_movie;
				if( menu.rows[file2_song_still].			SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) )	menu.rows[file2_type].iDefaultChoice	= song_bitmap;
				if( menu.rows[file2_global_bganimation].		SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) )	menu.rows[file2_type].iDefaultChoice	= global_bganimation;
				if( menu.rows[file2_global_movie].			SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) )	menu.rows[file2_type].iDefaultChoice	= global_movie;
				if( menu.rows[file2_global_movie_song_group].		SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) )	menu.rows[file2_type].iDefaultChoice	= global_movie_song_group;
				if( menu.rows[file2_global_movie_song_group_and_genre].	SetDefaultChoiceIfPresent( bgChange.m_def.m_sFile2 ) )	menu.rows[file2_type].iDefaultChoice	= global_movie_song_group_and_genre;
				menu.rows[color1].					SetDefaultChoiceIfPresent( bgChange.m_def.m_sColor1 );
				menu.rows[color2].					SetDefaultChoiceIfPresent( bgChange.m_def.m_sColor2 );

				EditMiniMenu( &g_BackgroundChange, SM_BackFromBGChange );
			}
		}
		else
		{
			SCREENMAN->SystemMessage( BG_CHANGE_STEP_TIMING );
			SCREENMAN->PlayInvalidSound();
		}
		return true;

	case EDIT_BUTTON_OPEN_COURSE_MENU:
		{
			g_CourseMode.rows[0].choices.clear();
			g_CourseMode.rows[0].choices.push_back( CommonMetrics::LocalizeOptionItem("Off",false) );
			g_CourseMode.rows[0].iDefaultChoice = 0;

			vector<Course*> courses;
			SONGMAN->GetAllCourses( courses, false );
			for( unsigned i = 0; i < courses.size(); ++i )
			{
				const Course *crs = courses[i];

				bool bUsesThisSong = false;
				for( unsigned e = 0; e < crs->m_vEntries.size(); ++e )
				{
					if( crs->m_vEntries[e].songID.ToSong() != m_pSong )
						continue;
					bUsesThisSong = true;
				}

				if( bUsesThisSong )
				{
					g_CourseMode.rows[0].choices.push_back( crs->GetDisplayFullTitle() );
					if( crs == GAMESTATE->m_pCurCourse )
						g_CourseMode.rows[0].iDefaultChoice = g_CourseMode.rows[0].choices.size()-1;
				}
			}

			EditMiniMenu( &g_CourseMode, SM_BackFromCourseModeMenu );
		}
		return true;
	case EDIT_BUTTON_OPEN_STEP_ATTACK_MENU:
	{
		this->DoStepAttackMenu();
		return true;
	}
	case EDIT_BUTTON_OPEN_COURSE_ATTACK_MENU:
		{
			// TODO: Give Song/Step Timing switches/functions here?
			Course *pCourse = GAMESTATE->m_pCurCourse;
			if( pCourse == NULL )
				return false;
			CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];
			float fStartTime = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat );
			int iAttack = FindAttackAtTime( ce.attacks, fStartTime );

			if( iAttack >= 0 )
			{
				const RString sDuration = FloatToString(ce.attacks[iAttack].fSecsRemaining );

				g_InsertCourseAttack.rows[remove].bEnabled = true;
				if( g_InsertCourseAttack.rows[duration].choices.size() == 9 )
					g_InsertCourseAttack.rows[duration].choices.push_back( sDuration );
				else
					g_InsertCourseAttack.rows[duration].choices.back() = sDuration;
				g_InsertCourseAttack.rows[duration].iDefaultChoice = 9;
			}
			else
			{
				if( g_InsertCourseAttack.rows[duration].choices.size() == 10 )
					g_InsertCourseAttack.rows[duration].choices.pop_back();
				g_InsertCourseAttack.rows[duration].iDefaultChoice = 3;
			}

			EditMiniMenu( &g_InsertCourseAttack, SM_BackFromInsertCourseAttack );
		}
		return true;
	case EDIT_BUTTON_ADD_STEP_MODS:
	{
		float start = -1;
		float end = -1;
		PlayerOptions po;
		
		if (m_NoteFieldEdit.m_iBeginMarker == -1) // not highlighted
		{
			po.FromString("");
		}
		else
		{
			const TimingData &timing = GetAppropriateTiming();
			start = timing.GetElapsedTimeFromBeat(NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker));
			AttackArray &attacks = 
				(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
			int index = FindAttackAtTime(attacks, start);
			
			if (index >= 0)
			{
				po.FromString("");
			}
			if (m_NoteFieldEdit.m_iEndMarker == -1)
			{
				end = m_pSong->m_fMusicLengthSeconds;
			}
			else
			{
				end = timing.GetElapsedTimeFromBeat(NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker));
			}
			
		}
		ModsGroup<PlayerOptions> &toEdit = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions;
		this->originalPlayerOptions.Assign(ModsLevel_Preferred, toEdit.GetPreferred());
		g_fLastInsertAttackPositionSeconds = start;
		g_fLastInsertAttackDurationSeconds = end - start;
		toEdit.Assign( ModsLevel_Stage, po );
		SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertStepAttackPlayerOptions );
		return true;
	}
	case EDIT_BUTTON_ADD_COURSE_MODS:
		{
			float fStart, fEnd;
			PlayerOptions po;
			const Course *pCourse = GAMESTATE->m_pCurCourse;
			if( pCourse == NULL )
				return false;
			const CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];

			if( m_NoteFieldEdit.m_iBeginMarker == -1 )
			{
				fStart = -1;
				fEnd = -1;
				po.FromString( ce.sModifiers );
			}
			else
			{
				// TODO: Give Song/Step Timing switches/functions here? 
				TimingData *timing = m_pSteps->GetTimingData();
				fStart = timing->GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );
				int iAttack = FindAttackAtTime( ce.attacks, fStart );

				if( iAttack >= 0 )
					po.FromString( ce.attacks[iAttack].sModifiers );

				if( m_NoteFieldEdit.m_iEndMarker == -1 )
					fEnd = m_pSong->m_fMusicLengthSeconds;
				else
					fEnd = timing->GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
			}
			g_fLastInsertAttackPositionSeconds = fStart;
			g_fLastInsertAttackDurationSeconds = fEnd - fStart;
			GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.Assign( ModsLevel_Stage, po );
			SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertCourseAttackPlayerOptions );

		}
		return true;
	case EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP:
	case EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE:
		{
			bool bTryGenre = EditB == EDIT_BUTTON_BAKE_RANDOM_FROM_SONG_GROUP_AND_GENRE;
			RString sName = GetOneBakedRandomFile(m_pSong, bTryGenre);
			if( sName.empty() )
			{
				SCREENMAN->PlayInvalidSound();
				SCREENMAN->SystemMessage( NO_BACKGROUNDS_AVAILABLE );
			}
			else
			{
				BackgroundLayer iLayer = BACKGROUND_LAYER_1;
				BackgroundChange bgChange;
				bgChange.m_fStartBeat = GAMESTATE->m_Position.m_fSongBeat;
				FOREACH( BackgroundChange, m_pSong->GetBackgroundChanges(iLayer), bgc )
				{
					if( bgc->m_fStartBeat == GAMESTATE->m_Position.m_fSongBeat )
					{
						bgChange = *bgc;
						m_pSong->GetBackgroundChanges(iLayer).erase( bgc );
						break;
					}
				}
				bgChange.m_def.m_sFile1 = sName;
				m_pSong->AddBackgroundChange( iLayer, bgChange );
				m_soundMarker.Play();
			}
		}
		return true;

	case EDIT_BUTTON_PLAY_FROM_START:
		HandleMainMenuChoice( play_whole_song );
		return true;

	case EDIT_BUTTON_PLAY_FROM_CURSOR:
		HandleMainMenuChoice( play_current_beat_to_end );
		return true;

	case EDIT_BUTTON_PLAY_SELECTION:
		HandleMainMenuChoice( play_selection );
		return true;
	case EDIT_BUTTON_RECORD_SELECTION:
		if( m_NoteFieldEdit.m_iBeginMarker!=-1 && m_NoteFieldEdit.m_iEndMarker!=-1 )
		{
			HandleAlterMenuChoice( record );
		}
		else
		{
			if( g_iDefaultRecordLength.Get() == -1 )
			{
				m_iStartPlayingAt = BeatToNoteRow(GetAppropriatePosition().m_fSongBeat);
				m_iStopPlayingAt = max( m_iStartPlayingAt, m_NoteDataEdit.GetLastRow() + 1 );
			}
			else
			{
				m_iStartPlayingAt = BeatToNoteRow( ftruncf(GetAppropriatePosition().m_fSongBeat, g_iDefaultRecordLength.Get()) );
				m_iStopPlayingAt = m_iStartPlayingAt + BeatToNoteRow( g_iDefaultRecordLength.Get() );
			}

			if( GAMESTATE->m_pCurSteps[0]->IsAnEdit() )
				m_iStopPlayingAt = min( m_iStopPlayingAt, BeatToNoteRow(GetMaximumBeatForNewNote()) );

			if( m_iStartPlayingAt >= m_iStopPlayingAt )
			{
				SCREENMAN->PlayInvalidSound();
				return true;
			}

			TransitionEditState( STATE_RECORDING );
		}
		return true;
	case EDIT_BUTTON_RECORD_FROM_CURSOR:
		m_iStartPlayingAt = BeatToNoteRow(GetAppropriatePosition().m_fSongBeat);
		m_iStopPlayingAt = max( m_iStartPlayingAt, m_NoteDataEdit.GetLastRow() );
		TransitionEditState( STATE_RECORDING );
		return true;

	case EDIT_BUTTON_INSERT:
		HandleAreaMenuChoice( insert_and_shift );
		SCREENMAN->PlayInvalidSound();
		return true;

	case EDIT_BUTTON_INSERT_SHIFT_PAUSES:
		HandleAreaMenuChoice( shift_pauses_forward );
		SCREENMAN->PlayInvalidSound();
		return true;

	case EDIT_BUTTON_DELETE:
		HandleAreaMenuChoice( delete_and_shift );
		SCREENMAN->PlayInvalidSound();
		return true;

	case EDIT_BUTTON_DELETE_SHIFT_PAUSES:
		HandleAreaMenuChoice( shift_pauses_backward );
		SCREENMAN->PlayInvalidSound();
		return true;

	case EDIT_BUTTON_SAVE:
		HandleMainMenuChoice( ScreenEdit::save );
		return true;

	case EDIT_BUTTON_UNDO:
		Undo();
		return true;

	case EDIT_BUTTON_SWITCH_PLAYERS:
		if( m_InputPlayerNumber == PLAYER_INVALID )
			return false;
		enum_add( m_InputPlayerNumber, 1 );
		if( m_InputPlayerNumber == NUM_PLAYERS )
			m_InputPlayerNumber = PLAYER_1;
		m_soundSwitchPlayer.Play();
		return true;
	
	case EDIT_BUTTON_SWITCH_TIMINGS:
		GAMESTATE->m_bIsUsingStepTiming = !GAMESTATE->m_bIsUsingStepTiming;
		m_soundSwitchTiming.Play();
		return true;
	default:
		return false;
	}
}

bool ScreenEdit::InputRecord( const InputEventPlus &input, EditButton EditB )
{
	if( input.type == IET_FIRST_PRESS  &&  EditB == EDIT_BUTTON_RETURN_TO_EDIT )
	{
		TransitionEditState( STATE_EDITING );
		return true;
	}

	if( input.pn != PLAYER_1 )
		return false;		// ignore

	const int iCol = GAMESTATE->GetCurrentStyle()->GameInputToColumn( input.GameI );

	//Is this actually a column? If not, ignore the input.
	if( iCol == -1 )
		return false;

	switch( input.type )
	{
	DEFAULT_FAIL( input.type );
	case IET_FIRST_PRESS:
		{
			if( EditIsBeingPressed(EDIT_BUTTON_REMOVE_NOTE) )
			{
				// Remove notes in Update.
				return false;
			}

			// Add a tap
			float fBeat = GetBeat();
			fBeat = Quantize( fBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) );

			const int iRow = BeatToNoteRow( fBeat );
			if( iRow < 0 )
				return false;

			// Don't add outside of the range.
			if( iRow < m_iStartPlayingAt || iRow >= m_iStopPlayingAt )
				return false;

			// Remove hold if any so that we don't have taps inside of a hold.
			int iHeadRow;
			if( m_NoteDataRecord.IsHoldNoteAtRow( iCol, iRow, &iHeadRow ) )
				m_NoteDataRecord.SetTapNote( iCol, iHeadRow, TAP_EMPTY );

			TapNote tn = TAP_ORIGINAL_TAP;
			tn.pn = m_InputPlayerNumber;
			m_NoteDataRecord.SetTapNote( iCol, iRow, tn );
			m_NoteFieldRecord.Step( iCol, TNS_W1 );
		}
		return true;
	case IET_REPEAT:
	case IET_RELEASE:
		// don't add or extend holds here; we do it in Update()
		return false;
	}
}

bool ScreenEdit::InputRecordPaused( const InputEventPlus &input, EditButton EditB )
{
	if( input.type != IET_FIRST_PRESS )
		return false;	// don't care

	switch( EditB )
	{
	case EDIT_BUTTON_UNDO:
		/* We've already actually committed changes to m_NoteDataEdit, so all we have
		 * to do to undo is Undo() as usual, and copy the note data back in. */
		Undo();
		m_NoteDataRecord.CopyAll( m_NoteDataEdit );
		return true;

	case EDIT_BUTTON_PLAY_SELECTION:
		TransitionEditState( STATE_PLAYING );
		return true;

	case EDIT_BUTTON_RECORD_SELECTION:
		TransitionEditState( STATE_RECORDING );
		return true;

	case EDIT_BUTTON_RECORD_FROM_CURSOR:
		if( GAMESTATE->m_pCurSteps[0]->IsAnEdit() )
		{
			float fMaximumBeat = GetMaximumBeatForNewNote();
			if( NoteRowToBeat(m_iStopPlayingAt) >= fMaximumBeat )
			{
				// We're already at the end.
				SCREENMAN->PlayInvalidSound();
				return true;
			}
		}

		// Pick up where we left off.
		{
			int iSize = m_iStopPlayingAt - m_iStartPlayingAt;
			m_iStartPlayingAt = m_iStopPlayingAt;
			m_iStopPlayingAt += iSize;

			if( GAMESTATE->m_pCurSteps[0]->IsAnEdit() )
				m_iStopPlayingAt = min( m_iStopPlayingAt, BeatToNoteRow(GetMaximumBeatForNewNote()) );
		}

		TransitionEditState( STATE_RECORDING );
		return true;

	case EDIT_BUTTON_RETURN_TO_EDIT:
		TransitionEditState( STATE_EDITING );
		return true;
	default:
		return false;
	}
}

bool ScreenEdit::InputPlay( const InputEventPlus &input, EditButton EditB )
{
	switch( input.type )
	{
	case IET_RELEASE:
	case IET_FIRST_PRESS:
		break;
	default:
		return false;
	}

	GameButtonType gbt = GAMESTATE->m_pCurGame->GetPerButtonInfo(input.GameI.button)->m_gbt;

	if( GamePreferences::m_AutoPlay == PC_HUMAN && GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_fPlayerAutoPlay == 0 )
	{
		const int iCol = GAMESTATE->GetCurrentStyle()->GameInputToColumn( input.GameI );
		bool bRelease = input.type == IET_RELEASE;
		switch( input.pn )
		{
		case PLAYER_2:
			// ignore player 2 input unless this mode requires it
			if( GAMESTATE->GetCurrentStyle()->m_StyleType != StyleType_TwoPlayersSharedSides )
				break;

		// fall through to input handling logic:
		case PLAYER_1:
			{
				switch( gbt )
				{
				case GameButtonType_Step:
					if( iCol != -1 )
						m_Player->Step( iCol, -1, input.DeviceI.ts, false, bRelease );
					return true;
				case GameButtonType_Fret:
					if( iCol != -1 )
						m_Player->Fret( iCol, -1, input.DeviceI.ts, false, bRelease );
					return true;
				case GameButtonType_Strum:
					m_Player->Strum( iCol, -1, input.DeviceI.ts, false, bRelease );
					return true;
				default:
					break;
				}
			}
		default: break;
		}
	}

	if( gbt == GameButtonType_INVALID  &&  input.type == IET_FIRST_PRESS )
	{
		switch( EditB )
		{
		case EDIT_BUTTON_RETURN_TO_EDIT:
			/* When exiting play mode manually, leave the cursor where it is. */
			m_fBeatToReturnTo = GetAppropriatePosition().m_fSongBeat;
			TransitionEditState( STATE_EDITING );
			return true;
		case EDIT_BUTTON_OFFSET_UP:
		case EDIT_BUTTON_OFFSET_DOWN:
		{
			float fOffsetDelta;
			switch( EditB )
			{
				DEFAULT_FAIL( EditB );
				case EDIT_BUTTON_OFFSET_DOWN:	fOffsetDelta = -0.020f;		break;
				case EDIT_BUTTON_OFFSET_UP:	fOffsetDelta = +0.020f;		break;
			}

			if( EditIsBeingPressed( EDIT_BUTTON_ADJUST_FINE ) )
			{
				fOffsetDelta /= 20; /* 1ms */
			}
			else if( input.type == IET_REPEAT )
			{
				if( INPUTFILTER->GetSecsHeld(input.DeviceI) < 1.0f )
					fOffsetDelta *= 10;
				else
					fOffsetDelta *= 40;
			}

			GetAppropriateTimingForUpdate().m_fBeat0OffsetInSeconds += fOffsetDelta;
			if (!GAMESTATE->m_bIsUsingStepTiming)
			{
				GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds += fOffsetDelta;
			}
		}
			return true;
		default: break;
		}
	}
	return false;
}

void ScreenEdit::TransitionEditState( EditState em )
{
	EditState old = m_EditState;

	// If we're going from recording to paused, come back when we're done.
	if( old == STATE_RECORDING_PAUSED && em == STATE_PLAYING )
		m_bReturnToRecordMenuAfterPlay = true;

	const bool bStateChanging = em != old;

#if 0
	// If switching out of record, open the menu.
	{
		bool bGoToRecordMenu = (old == STATE_RECORDING);
		if( m_bReturnToRecordMenuAfterPlay && old == STATE_PLAYING )
		{
			bGoToRecordMenu = true;
			m_bReturnToRecordMenuAfterPlay = false;
		}
		
		if( bGoToRecordMenu )
			em = STATE_RECORDING_PAUSED;
	}
#endif

	// If we're playing music or assist ticks when changing modes, stop.
	SOUND->StopMusic();
	if( m_pSoundMusic )
		m_pSoundMusic->StopPlaying();
	m_GameplayAssist.StopPlaying();
	GAMESTATE->m_bGameplayLeadIn.Set( true );

	if( bStateChanging )
	{
		switch( old )
		{
		case STATE_EDITING:
			// If exiting EDIT mode, save the cursor position.
			m_fBeatToReturnTo = GetAppropriatePosition().m_fSongBeat;
			break;

		case STATE_PLAYING:
			AdjustSync::HandleSongEnd();
			if (!GAMESTATE->m_bIsUsingStepTiming)
				GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing = backupStepTiming;
			if( AdjustSync::IsSyncDataChanged() )
				ScreenSaveSync::PromptSaveSync();
			break;

		case STATE_RECORDING:
			SetDirty( true );
			if (!GAMESTATE->m_bIsUsingStepTiming)
				GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing = backupStepTiming;
			SaveUndo();

			// delete old TapNotes in the range
			m_NoteDataEdit.ClearRange( m_iStartPlayingAt, m_iStopPlayingAt );
			m_NoteDataEdit.CopyRange( m_NoteDataRecord, m_iStartPlayingAt, m_iStopPlayingAt, m_iStartPlayingAt );
			m_NoteDataRecord.ClearAll();

			CheckNumberOfNotesAndUndo();
			break;
		default: break;
		}
	}

	// Set up player options for this mode. (EDITING uses m_PlayerStateEdit,
	// which we don't need to change.)
	if( em != STATE_EDITING )
	{
		// Stop displaying course attacks, if any.
		GAMESTATE->m_pPlayerState[PLAYER_1]->RemoveActiveAttacks();
		// Load the player's default PlayerOptions.
		GAMESTATE->m_pPlayerState[PLAYER_1]->RebuildPlayerOptionsFromActiveAttacks();

		// Snap to current options.
		GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.SetCurrentToLevel( ModsLevel_Stage );
	}

	switch( em )
	{
	DEFAULT_FAIL( em );
	case STATE_EDITING:
		// Important: people will stop playing, change the BG and start again; make sure we reload
		m_Background.Unload();
		m_Foreground.Unload();

		// Restore the cursor position + Quantize + Clamp
		SetBeat( max( 0, Quantize( m_fBeatToReturnTo, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) ) ) );
		GAMESTATE->m_bInStepEditor = true;
		break;

	case STATE_PLAYING:
	case STATE_RECORDING:
	{
		if( bStateChanging )
			AdjustSync::ResetOriginalSyncData();

		/* Give a 1 second lead-in.  If we're loading Player, this must be done first.
		 * Also be sure to get the right timing. */
		float fSeconds = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_iStartPlayingAt) ) - 1;
		GAMESTATE->UpdateSongPosition( fSeconds, GetAppropriateTiming(), RageZeroTimer, true );

		GAMESTATE->m_bGameplayLeadIn.Set( false );
		
		if (!GAMESTATE->m_bIsUsingStepTiming)
		{
			// Substitute the song timing for the step timing during
			// previuw if we're in song mode
			backupStepTiming = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing;
			GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing.Clear();
		}

		/* Reset the note skin, in case preferences have changed. */
		// XXX
		// GAMESTATE->ResetNoteSkins();
		//GAMESTATE->res
		GAMESTATE->m_bInStepEditor = false;
		break;
	}
	case STATE_RECORDING_PAUSED:
		GAMESTATE->m_bInStepEditor = false;
		break;
	}

	switch( em )
	{
	case STATE_PLAYING:
		// If we're in course display mode, set that up.
		SetupCourseAttacks();

		m_Player.Load( m_NoteDataEdit );

		if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_fPlayerAutoPlay != 0 )
			GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerController = PC_AUTOPLAY;
		else
			GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerController = GamePreferences::m_AutoPlay;

		if( g_bEditorShowBGChangesPlay )
		{
			/* FirstBeat affects backgrounds, so commit changes to memory (not to disk)
			 * and recalc it. */
			Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
			ASSERT( pSteps != NULL );
			pSteps->SetNoteData( m_NoteDataEdit );
			m_pSong->ReCalculateRadarValuesAndLastSecond();

			m_Background.Unload();
			m_Background.LoadFromSong( m_pSong );

			m_Foreground.Unload();
			m_Foreground.LoadFromSong( m_pSong );
		}

		break;
	case STATE_RECORDING:
	case STATE_RECORDING_PAUSED:
		// initialize m_NoteFieldRecord
		m_NoteDataRecord.CopyAll( m_NoteDataEdit );

		// highlight the section being recorded
		m_NoteFieldRecord.m_iBeginMarker = m_iStartPlayingAt;
		m_NoteFieldRecord.m_iEndMarker = m_iStopPlayingAt;

		break;
	default: break;
	}

	// Show/hide depending on edit state (em)
	m_sprOverlay->PlayCommand( EditStateToString(em) );
	m_sprUnderlay->PlayCommand( EditStateToString(em) );

	m_Background.SetVisible( g_bEditorShowBGChangesPlay  &&  em != STATE_EDITING );
	m_textInputTips.SetVisible( em == STATE_EDITING );
	m_textInfo.SetVisible( em == STATE_EDITING );
	// Play the OnCommands again so that these will be re-hidden if the OnCommand hides them.
	if( em == STATE_EDITING )
	{
		m_textInputTips.PlayCommand( "On" );
		m_textInfo.PlayCommand( "On" );
	}
	m_textPlayRecordHelp.SetVisible( em != STATE_EDITING );
	m_SnapDisplay.SetVisible( em == STATE_EDITING );
	m_NoteFieldEdit.SetVisible( em == STATE_EDITING );
	m_NoteFieldRecord.SetVisible( em == STATE_RECORDING  ||  em == STATE_RECORDING_PAUSED );
	m_Player->SetVisible( em == STATE_PLAYING );
	m_Foreground.SetVisible( g_bEditorShowBGChangesPlay  &&  em != STATE_EDITING );

	switch( em )
	{
	case STATE_PLAYING:
	case STATE_RECORDING:
		{
		const float fStartSeconds = GetAppropriateTiming().GetElapsedTimeFromBeat( GetBeat() );
		LOG->Trace( "Starting playback at %f", fStartSeconds );

		RageSoundParams p;
		p.m_fSpeed = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
		p.m_StartSecond = fStartSeconds;
		p.StopMode = RageSoundParams::M_CONTINUE;
		m_pSoundMusic->SetProperty( "AccurateSync", true );
		m_pSoundMusic->Play( &p );
		break;
		}
	default: break;
	}

	m_EditState = em;
}

void ScreenEdit::ScrollTo( float fDestinationBeat )
{
	CLAMP( fDestinationBeat, 0, GetMaximumBeatForMoving() );

	// Don't play the sound and do the hold note logic below if our position didn't change.
	const float fOriginalBeat = GetAppropriatePosition().m_fSongBeat;
	if( fOriginalBeat == fDestinationBeat )
		return;

	SetBeat(fDestinationBeat);

	// check to see if they're holding a button
	for( int n=0; n<NUM_EDIT_BUTTON_COLUMNS; n++ )
	{
		int iCol = n;

		// Ctrl + number = input to right half
		if( EditIsBeingPressed(EDIT_BUTTON_RIGHT_SIDE) )
			ShiftToRightSide( iCol, m_NoteDataEdit.GetNumTracks() );

		if( iCol >= m_NoteDataEdit.GetNumTracks() )
			continue;	// skip

		EditButton b = EditButton(EDIT_BUTTON_COLUMN_0+n);
		if( !EditIsBeingPressed(b) )
			continue;

		// create a new hold note
		int iStartRow = BeatToNoteRow( min(fOriginalBeat, fDestinationBeat) );
		int iEndRow = BeatToNoteRow( max(fOriginalBeat, fDestinationBeat) );

		// Don't SaveUndo.  We want to undo the whole hold, not just the last segment
		// that the user made.  Dragging the hold bigger can only absorb and remove
		// other taps, so dragging won't cause us to exceed the note limit.
		TapNote tn = EditIsBeingPressed(EDIT_BUTTON_LAY_ROLL) ? TAP_ORIGINAL_ROLL_HEAD : TAP_ORIGINAL_HOLD_HEAD;

		tn.pn = m_InputPlayerNumber;
		m_NoteDataEdit.AddHoldNote( iCol, iStartRow, iEndRow, tn );
	}

	if( EditIsBeingPressed(EDIT_BUTTON_SCROLL_SELECT) )
	{
		/* Shift is being held.
		 * If this is the first time we've moved since shift was depressed,
		 * the old position (before this move) becomes the start pos: */
		int iDestinationRow = BeatToNoteRow( fDestinationBeat );
		if( m_iShiftAnchor == -1 )
			m_iShiftAnchor = BeatToNoteRow(fOriginalBeat);

		if( iDestinationRow == m_iShiftAnchor )
		{
			// We're back at the anchor, so we have nothing selected.
			m_NoteFieldEdit.m_iBeginMarker = m_NoteFieldEdit.m_iEndMarker = -1;
		}
		else
		{
			m_NoteFieldEdit.m_iBeginMarker = m_iShiftAnchor;
			m_NoteFieldEdit.m_iEndMarker = iDestinationRow;
			if( m_NoteFieldEdit.m_iBeginMarker > m_NoteFieldEdit.m_iEndMarker )
				swap( m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker );
		}
	}

	m_soundChangeLine.Play();
}

static LocalizedString NEW_KEYSOUND_FILE("ScreenEdit", "Enter New Keysound File");

void ScreenEdit::HandleMessage( const Message &msg )
{
	if( msg == "Judgment" )
	{
		PlayerNumber pn;
		msg.GetParam( "Player", pn );

		if( GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_bMuteOnError )
		{
			RageSoundReader *pSoundReader = m_AutoKeysounds.GetPlayerSound( pn );
			if( pSoundReader == NULL )
				pSoundReader = m_AutoKeysounds.GetSharedSound();

			HoldNoteScore hns;
			msg.GetParam( "HoldNoteScore", hns );
			TapNoteScore tns;
			msg.GetParam( "TapNoteScore", tns );

			bool bOn = false;
			if( hns != HoldNoteScore_Invalid )
				bOn = hns != HNS_LetGo;
			else
				bOn = tns != TNS_Miss;

			if( pSoundReader )
				pSoundReader->SetProperty( "Volume", bOn? 1.0f:0.0f );
		}
	}
	if( msg == Message_SongModified )
	{
		SetDirty( true );
	}
	Screen::HandleMessage( msg );
}

static LocalizedString SAVE_SUCCESSFUL				( "ScreenEdit", "Save successful." );

static LocalizedString ADD_NEW_MOD ("ScreenEdit", "Adding New Mod");
static LocalizedString ADD_NEW_ATTACK ("ScreenEdit", "Adding New Attack");
static LocalizedString EDIT_EXISTING_MOD ("ScreenEdit", "Edit Existing Mod");
static LocalizedString EDIT_ATTACK_START ("ScreenEdit", "Edit Attack Start");
static LocalizedString EDIT_ATTACK_LENGTH ("ScreenEdit", "Edit Attack Length");

void ScreenEdit::HandleScreenMessage( const ScreenMessage SM )
{
	if( SM != SM_UpdateTextInfo )
		m_bTextInfoNeedsUpdate = true;

	if( SM == SM_UpdateTextInfo )
	{
		UpdateTextInfo();
		this->PostScreenMessage( SM_UpdateTextInfo, 0.1f );
	}
	else if( SM == SM_GoToNextScreen )
	{
		GAMESTATE->m_EditMode = EditMode_Invalid;
	}
	else if( SM == SM_BackFromMainMenu )
	{
		HandleMainMenuChoice( (MainMenuChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
	}
	else if( SM == SM_BackFromAreaMenu )
	{
		AreaMenuChoice amc = static_cast<AreaMenuChoice>(ScreenMiniMenu::s_iLastRowCode);
		const vector<int> &answers = ScreenMiniMenu::s_viLastAnswers;
		HandleAreaMenuChoice( amc, answers );
	}
	else if( SM == SM_BackFromAlterMenu )
	{
		HandleAlterMenuChoice( (AlterMenuChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
	}
	else if( SM == SM_BackFromStepsInformation )
	{
		HandleStepsInformationChoice( (StepsInformationChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
	}
	else if (SM == SM_BackFromStepsData)
	{
		HandleStepsDataChoice((StepsDataChoice)ScreenMiniMenu::s_iLastRowCode,
							  ScreenMiniMenu::s_viLastAnswers);
	}
	
	else if( SM == SM_BackFromSongInformation )
	{
		HandleSongInformationChoice( (SongInformationChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
	}
	else if( SM == SM_BackFromTimingDataInformation )
	{
		HandleTimingDataInformationChoice( (TimingDataInformationChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
	}
	else if( SM == SM_BackFromDifficultyMeterChange )
	{
		int i = StringToInt( ScreenTextEntry::s_sLastAnswer );
		GAMESTATE->m_pCurSteps[PLAYER_1]->SetMeter(i);
		SetDirty( true );
	}
	else if( SM == SM_BackFromBeat0Change && !ScreenTextEntry::s_bCancelledLast )
	{
		float fBeat0 = StringToFloat( ScreenTextEntry::s_sLastAnswer );

		TimingData &timing = GetAppropriateTimingForUpdate();
		float old = timing.m_fBeat0OffsetInSeconds;
		timing.m_fBeat0OffsetInSeconds = fBeat0;
		float delta = timing.m_fBeat0OffsetInSeconds - old;

		if (GAMESTATE->m_bIsUsingStepTiming)
		{
			GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.UpdateStartTimes(delta);
		}
		else
		{
			GAMESTATE->m_pCurSong->m_Attacks.UpdateStartTimes(delta);
			GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds += delta;
		}

		SetDirty( true );
	}
	else if( SM == SM_BackFromBPMChange && !ScreenTextEntry::s_bCancelledLast )
	{
		float fBPM = StringToFloat( ScreenTextEntry::s_sLastAnswer );

		if( fBPM > 0 )
			GetAppropriateTimingForUpdate().AddSegment( BPMSegment(GetRow(), fBPM) );

		SetDirty( true );
	}
	else if( SM == SM_BackFromStopChange && !ScreenTextEntry::s_bCancelledLast )
	{
		float fStop = StringToFloat( ScreenTextEntry::s_sLastAnswer );

		if( fStop >= 0 )
			GetAppropriateTimingForUpdate().AddSegment( StopSegment(GetRow(), fStop) );

		SetDirty( true );
	}
	else if( SM == SM_BackFromDelayChange && !ScreenTextEntry::s_bCancelledLast )
	{
		float fDelay = StringToFloat( ScreenTextEntry::s_sLastAnswer );

		if( fDelay >= 0 )
			GetAppropriateTimingForUpdate().AddSegment( DelaySegment(GetRow(), fDelay) );

		SetDirty( true );
	}
	else if( SM == SM_BackFromTimeSignatureChange && !ScreenTextEntry::s_bCancelledLast )
	{
		int iNum, iDen;

		if( sscanf( ScreenTextEntry::s_sLastAnswer.c_str(), " %d / %d ", &iNum, &iDen ) == 2 )
			GetAppropriateTimingForUpdate().AddSegment( TimeSignatureSegment(GetRow(), iNum, iDen) );

		SetDirty( true );
	}
	else if ( SM == SM_BackFromTickcountChange && !ScreenTextEntry::s_bCancelledLast )
	{
		int iTick = StringToInt( ScreenTextEntry::s_sLastAnswer );

		if ( iTick >= 0 && iTick <= ROWS_PER_BEAT )
			GetAppropriateTimingForUpdate().AddSegment( TickcountSegment( GetRow(), iTick) );

		SetDirty( true );
	}
	else if ( SM == SM_BackFromComboChange && !ScreenTextEntry::s_bCancelledLast )
	{
		int iCombo, iMiss;

		if (sscanf(ScreenTextEntry::s_sLastAnswer.c_str(), " %d / %d ", &iCombo, &iMiss) == 2)
			GetAppropriateTimingForUpdate().AddSegment( ComboSegment(GetRow(), iCombo, iMiss) );

		SetDirty( true );
	}
	else if ( SM == SM_BackFromLabelChange && !ScreenTextEntry::s_bCancelledLast )
	{
		RString sLabel = ScreenTextEntry::s_sLastAnswer;

		if ( !GetAppropriateTiming().DoesLabelExist(sLabel) )
		{
			// XXX: these should be in the NotesWriters where they're needed.
			sLabel.Replace("=", "_");
			sLabel.Replace(",", "_");
			GetAppropriateTimingForUpdate().AddSegment( LabelSegment(GetRow(), sLabel) );
			SetDirty( true );
		}
	}
	else if ( SM == SM_BackFromWarpChange && !ScreenTextEntry::s_bCancelledLast )
	{
		float fWarp = StringToFloat( ScreenTextEntry::s_sLastAnswer );
		if( fWarp >= 0 ) // allow 0 to kill a warp.
		{
			GetAppropriateTimingForUpdate().SetWarpAtBeat( GetBeat(), fWarp );
			SetDirty( true );
		}
	}
	else if( SM == SM_BackFromSpeedPercentChange && !ScreenTextEntry::s_bCancelledLast )
	{
		float fNum = StringToFloat( ScreenTextEntry::s_sLastAnswer );
		GetAppropriateTimingForUpdate().SetSpeedPercentAtBeat( GetBeat(), fNum );
		SetDirty( true );
	}
	else if ( SM == SM_BackFromSpeedWaitChange && !ScreenTextEntry::s_bCancelledLast )
	{
		float fDen = StringToFloat( ScreenTextEntry::s_sLastAnswer );
		if( fDen >= 0)
		{
			GetAppropriateTimingForUpdate().SetSpeedWaitAtBeat( GetBeat(), fDen );
		}
		SetDirty( true );
	}
	else if ( SM == SM_BackFromSpeedModeChange && !ScreenTextEntry::s_bCancelledLast )
	{
		if( ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "b" || ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "B" )
		{
			GetAppropriateTimingForUpdate().SetSpeedModeAtBeat( GetBeat(), SpeedSegment::UNIT_BEATS );
		}
		else if( ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "s" || ScreenTextEntry::s_sLastAnswer.substr(0, 1) == "S" )
		{
			GetAppropriateTimingForUpdate().SetSpeedModeAtBeat( GetBeat(), SpeedSegment::UNIT_SECONDS );
		}
		else
		{
			int tmp = StringToInt(ScreenTextEntry::s_sLastAnswer );

			SpeedSegment::BaseUnit unit = (tmp == 0 ) ?
			  SpeedSegment::UNIT_BEATS : SpeedSegment::UNIT_SECONDS;

			GetAppropriateTimingForUpdate().SetSpeedModeAtBeat( GetBeat(), unit );
		}
		SetDirty( true );
	}
	else if( SM == SM_BackFromScrollChange && !ScreenTextEntry::s_bCancelledLast )
	{
		float fNum = StringToFloat( ScreenTextEntry::s_sLastAnswer );
		GetAppropriateTimingForUpdate().SetScrollAtBeat( GetBeat(), fNum );
		SetDirty( true );
	}
	else if ( SM == SM_BackFromFakeChange && !ScreenTextEntry::s_bCancelledLast )
	{
		float fFake = StringToFloat( ScreenTextEntry::s_sLastAnswer );
		if( fFake >= 0 ) // allow 0 to kill a fake.
		{
			GetAppropriateTimingForUpdate().AddSegment( FakeSegment(GetRow(), fFake) );
			SetDirty( true );
		}
	}
	
	else if( SM == SM_BackFromBGChange )
	{
		HandleBGChangeChoice( (BGChangeChoice)ScreenMiniMenu::s_iLastRowCode, ScreenMiniMenu::s_viLastAnswers );
		SetDirty( true );
	}
	else if( SM == SM_BackFromCourseModeMenu )
	{
		const int num = ScreenMiniMenu::s_viLastAnswers[0];
		GAMESTATE->m_pCurCourse.Set( NULL );
		if( num != 0 )
		{
			const RString name = g_CourseMode.rows[0].choices[num];
			Course *pCourse = SONGMAN->FindCourse( name );

			int iCourseEntryIndex = -1;
			FOREACH_CONST( CourseEntry, pCourse->m_vEntries, i )
			{
				if( i->songID.ToSong() == GAMESTATE->m_pCurSong.Get() )
					iCourseEntryIndex = i - pCourse->m_vEntries.begin();
			}

			ASSERT( iCourseEntryIndex != -1 );

			GAMESTATE->m_pCurCourse.Set( pCourse );
			GAMESTATE->m_iEditCourseEntryIndex.Set( iCourseEntryIndex );
			ASSERT( GAMESTATE->m_pCurCourse != NULL );
		}
	}
	else if (SM == SM_BackFromKeysoundTrack)
	{
		const int track = ScreenMiniMenu::s_iLastRowCode;
		const int tracks = m_NoteDataEdit.GetNumTracks();
		const int row = this->GetRow();
		unsigned int sound = ScreenMiniMenu::s_viLastAnswers[track];
		vector<RString> &kses = m_pSong->m_vsKeysoundFile;
		
		if (track < tracks)
		{
			if (sound == kses.size())
			{
				// create a new sound (filename), point it.
				// if it's empty, make it an auto keysound.
				ScreenTextEntry::TextEntry(SM_BackFromNewKeysound, NEW_KEYSOUND_FILE, "", 64);
				return;
			}
			const TapNote &oldNote = m_NoteDataEdit.GetTapNote(track, row);
			TapNote newNote = oldNote; // need to lose the const. not feeling like casting.
			if (sound < kses.size())
			{
				// set note at this row to use this keysound file.
				// if it's empty, make it an auto keysound.
				newNote.iKeysoundIndex = sound;
				if (newNote.type == TapNote::empty)
				{
					newNote.type = TapNote::autoKeysound; // keysounds need something non empty.
				}
			}
			else // sound > kses.size()
			{
				// remove the sound. if it's an auto keysound, make it empty.
				newNote.iKeysoundIndex = -1;
				if (newNote.type == TapNote::autoKeysound)
				{
					newNote.type = TapNote::empty; // autoKeysound with no sound is pointless.
				}
			}
			m_NoteDataEdit.SetTapNote(track, row, newNote);
		}
		else if (track == tracks)
		{
			kses.erase(kses.begin() + sound);
			// TODO: Make the following a part of NoteData?
			for (int t = 0; t < tracks; ++t)
			{
				FOREACH_NONEMPTY_ROW_IN_TRACK(m_NoteDataEdit, t, r)
				{
					const TapNote &oldNote = m_NoteDataEdit.GetTapNote(t, r);
					TapNote newNote = oldNote; // need to lose the const. not feeling like casting.
					if (newNote.iKeysoundIndex == static_cast<int>(sound))
					{
						newNote.iKeysoundIndex = -1;
						if (newNote.type == TapNote::autoKeysound)
							newNote.type = TapNote::empty;
					}
					else if (newNote.iKeysoundIndex > static_cast<int>(sound))
						newNote.iKeysoundIndex--;
					
					m_NoteDataEdit.SetTapNote(t, r, newNote);
				}
			}
		}
		SetDirty(true);
	}
	else if (SM == SM_BackFromNewKeysound && !ScreenTextEntry::s_bCancelledLast)
	{
		RString answer = ScreenTextEntry::s_sLastAnswer;
		const int track = ScreenMiniMenu::s_iLastRowCode; // still keeps the same value.
		const int row = this->GetRow();
		const TapNote &oldNote = m_NoteDataEdit.GetTapNote(track, row);
		TapNote newNote = oldNote; // need to lose the const. not feeling like casting.
		vector<RString> &kses = m_pSong->m_vsKeysoundFile;
		unsigned pos = find(kses.begin(), kses.end(), answer) - kses.begin();
		if (pos == kses.size())
		{
			newNote.iKeysoundIndex = kses.size();
			kses.push_back(answer);
		}
		else
		{
			newNote.iKeysoundIndex = pos;
		}
		if (newNote.type == TapNote::empty)
			newNote.type = TapNote::autoKeysound; // keysounds need something non empty.
		m_NoteDataEdit.SetTapNote(track, row, newNote);
		SetDirty(true);
	}
	else if( SM == SM_BackFromOptions )
	{
		// The options may have changed the note skin.
		m_NoteFieldRecord.CacheAllUsedNoteSkins();
		m_Player->CacheAllUsedNoteSkins();

		// stop any music that screen may have been playing
		SOUND->StopMusic();
	}
	else if( SM == SM_BackFromInsertTapAttack )
	{
		int iDurationChoice = ScreenMiniMenu::s_viLastAnswers[0];
		g_fLastInsertAttackDurationSeconds = StringToFloat( g_InsertTapAttack.rows[0].choices[iDurationChoice] );

		SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertTapAttackPlayerOptions );
	}
	else if( SM == SM_BackFromInsertTapAttackPlayerOptions )
	{
		PlayerOptions poChosen = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetPreferred();
		RString sMods = poChosen.GetString();
		const int row = BeatToNoteRow( GAMESTATE->m_Position.m_fSongBeat );

		TapNote tn(
			TapNote::attack, 
			TapNote::SubType_Invalid,
			TapNote::original, 
			sMods,
			g_fLastInsertAttackDurationSeconds, 
			-1 );
		tn.pn = m_InputPlayerNumber;
		SetDirty( true );
		SaveUndo();
		m_NoteDataEdit.SetTapNote( g_iLastInsertTapAttackTrack, row, tn );
		CheckNumberOfNotesAndUndo();
	}
	else if (SM == SM_BackFromEditingAttackStart && !ScreenTextEntry::s_bCancelledLast)
	{
		float time = StringToFloat(ScreenTextEntry::s_sLastAnswer);
		AttackArray &attacks = 
		(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
		Attack &attack = attacks[attackInProcess];
		attack.fStartSecond = time;
		SetDirty(true);
	}
	else if (SM == SM_BackFromEditingAttackLength && !ScreenTextEntry::s_bCancelledLast)
	{
		float time = StringToFloat(ScreenTextEntry::s_sLastAnswer);
		AttackArray &attacks = 
		(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
		Attack &attack = attacks[attackInProcess];
		attack.fSecsRemaining = time;
		SetDirty(true);
	}
	else if (SM == SM_BackFromAddingModToExistingAttack && !ScreenTextEntry::s_bCancelledLast)
	{
		RString mod = ScreenTextEntry::s_sLastAnswer;
		Trim(mod);
		if (mod.length() > 0)
		{
			AttackArray &attacks = 
			(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
			Attack &attack = attacks[attackInProcess];
			attack.sModifiers += "," + mod;
			SetDirty(true);
		}
		
	}
	else if (SM == SM_BackFromEditingModToExistingAttack && !ScreenTextEntry::s_bCancelledLast)
	{
		AttackArray &attacks = 
		(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
		Attack &attack = attacks[attackInProcess];
		vector<RString> mods;
		split(attack.sModifiers, ",", mods);		
		RString mod = ScreenTextEntry::s_sLastAnswer;
		Trim(mod);
		if (mod.length() > 0)
		{
			mods[modInProcess - 2] = mod;
		}
		else
		{
			mods.erase(mods.begin() + (modInProcess - 2));
		}
		if (mods.size() > 0)
		{
			attack.sModifiers = join(",", mods);
		}
		else
		{
			attacks.erase(attacks.begin() + attackInProcess);
		}
		SetDirty(true);
	}
	else if (SM == SM_BackFromInsertStepAttack)
	{
		unsigned option = ScreenMiniMenu::s_iLastRowCode;
		AttackArray &attacks = 
		(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
		Attack &attack = attacks[attackInProcess];
		vector<RString> mods;
		split(attack.sModifiers, ",", mods);
		modInProcess = option;
		if (option == 0) // adjusting the starting time
		{
			ScreenTextEntry::TextEntry(SM_BackFromEditingAttackStart,
									   EDIT_ATTACK_START,
									   FloatToString(attack.fStartSecond),
									   10);
		}
		else if (option == 1) // adjusting the length of the attack
		{
			ScreenTextEntry::TextEntry(SM_BackFromEditingAttackLength,
									   EDIT_ATTACK_LENGTH,
									   FloatToString(attack.fSecsRemaining),
									   10);
		}
		else if (option >= 2 + mods.size()) // adding a new mod
		{
			ScreenTextEntry::TextEntry(SM_BackFromAddingModToExistingAttack,
									   ADD_NEW_MOD,
									   "",
									   64);
		}
		else // modifying existing mod.
		{
			ScreenTextEntry::TextEntry(SM_BackFromEditingModToExistingAttack,
									   EDIT_EXISTING_MOD,
									   mods[option - 2],
									   64);
		}
	}
	else if (SM == SM_BackFromAddingAttackToChart && !ScreenTextEntry::s_bCancelledLast)
	{
		RString mod = ScreenTextEntry::s_sLastAnswer;
		Trim(mod);
		if (mod.length() > 0)
		{
			AttackArray &attacks = 
			(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
			Attack attack;
			attack.fStartSecond = GetAppropriateTiming().GetElapsedTimeFromBeat(GetBeat());
			attack.fSecsRemaining = 5; // Users can change later.
			attack.sModifiers = mod;
			attacks.push_back(attack);
			SetDirty(true);
		}
	}
	else if (SM == SM_BackFromAttackAtTime)
	{
		int attackChoice = ScreenMiniMenu::s_iLastRowCode;
		int attackDecision = ScreenMiniMenu::s_viLastAnswers[attackChoice];
		const TimingData &timing = GetAppropriateTiming();
		float startTime = timing.GetElapsedTimeFromBeat(GetBeat());
		AttackArray &attacks = 
		(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
		vector<int> points = FindAllAttacksAtTime(attacks, startTime);
		if (attackChoice == (int)points.size())
		{
			// TODO: Add attack code.
			ScreenTextEntry::TextEntry(SM_BackFromAddingAttackToChart,
									   ADD_NEW_ATTACK,
									   "",
									   64);
		}
		else
		{
			attackInProcess = points[attackChoice];
			if (attackDecision == 1) // remove
			{
				attacks.erase(attacks.begin() + attackInProcess);
				SetDirty(true);
			}
			else
			{
				Attack &attack = attacks[attackInProcess];
				g_IndividualAttack.rows.clear();
				
				g_IndividualAttack.rows.push_back(MenuRowDef(0,
															 "Starting Time",
															 true,
															 EditMode_CourseMods,
															 true,
															 true,
															 0,
															 NULL));
				g_IndividualAttack.rows[0].SetOneUnthemedChoice(FloatToString(attack.fStartSecond));
				g_IndividualAttack.rows.push_back(MenuRowDef(1,
															 "Secs Remaining",
															 true,
															 EditMode_CourseMods,
															 true,
															 true,
															 0,
															 NULL));
				g_IndividualAttack.rows[1].SetOneUnthemedChoice(FloatToString(attack.fSecsRemaining));
				vector<RString> mods;
				split(attack.sModifiers, ",", mods);
				for (unsigned i = 0; i < mods.size(); ++i)
				{
					unsigned col = i + 2;
					g_IndividualAttack.rows.push_back(MenuRowDef(col,
																 ssprintf("Attack %d", i + 1),
																 true,
																 EditMode_CourseMods,
																 false,
																 true,
																 0,
																 NULL));
					g_IndividualAttack.rows[col].SetOneUnthemedChoice(mods[i].c_str());
				}
				
				g_IndividualAttack.rows.push_back(MenuRowDef(mods.size() + 2,
															 "Add Mod",
															 true,
															 EditMode_CourseMods,
															 true,
															 true,
															 0,
															 NULL));
				
				EditMiniMenu(&g_IndividualAttack, SM_BackFromInsertStepAttack);
			}
		}
	}
	else if (SM == SM_BackFromInsertStepAttack)
	{
		int iDurationChoice = ScreenMiniMenu::s_viLastAnswers[0];
		const TimingData &timing = GetAppropriateTiming();
		g_fLastInsertAttackPositionSeconds = timing.GetElapsedTimeFromBeat( GetBeat() );
		g_fLastInsertAttackDurationSeconds = StringToFloat( g_InsertStepAttack.rows[0].choices[iDurationChoice] );
		AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks;
		int iAttack = FindAttackAtTime(attacks, g_fLastInsertAttackPositionSeconds);
		
		if (ScreenMiniMenu::s_iLastRowCode == ScreenEdit::remove )
		{
			if (iAttack > 0)
				attacks.erase(attacks.begin() + iAttack);
		}
		else
		{
			ModsGroup<PlayerOptions> &toEdit = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions;
			this->originalPlayerOptions.Assign(ModsLevel_Preferred, toEdit.GetPreferred());
			PlayerOptions po;
			if (iAttack >= 0)
				po.FromString(attacks[iAttack].sModifiers);
			
			toEdit.Assign( ModsLevel_Preferred, po );
			SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertStepAttackPlayerOptions );
		}
		SetDirty(true);
	}
	else if( SM == SM_BackFromInsertCourseAttack )
	{
		int iDurationChoice = ScreenMiniMenu::s_viLastAnswers[0];
		Course *pCourse = GAMESTATE->m_pCurCourse;
		CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];
		int iAttack;
		
		// TODO: Handle Song/Step Timing functions/switches here?

		g_fLastInsertAttackPositionSeconds = m_pSteps->GetTimingData()->GetElapsedTimeFromBeat( GAMESTATE->m_Position.m_fSongBeat );
		g_fLastInsertAttackDurationSeconds = StringToFloat( g_InsertCourseAttack.rows[0].choices[iDurationChoice] );
		iAttack = FindAttackAtTime( ce.attacks, g_fLastInsertAttackPositionSeconds );

		if( ScreenMiniMenu::s_iLastRowCode == ScreenEdit::remove )
		{
			ASSERT( iAttack >= 0 );
			ce.attacks.erase( ce.attacks.begin() + iAttack );
		}
		else
		{
			PlayerOptions po;

			if( iAttack >= 0 )
				po.FromString( ce.attacks[iAttack].sModifiers );

			GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.Assign( ModsLevel_Preferred, po );
			SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertCourseAttackPlayerOptions );
		}
	}
	else if (SM == SM_BackFromInsertStepAttackPlayerOptions)
	{
		ModsGroup<PlayerOptions> &toRestore = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions;
		PlayerOptions poChosen = toRestore.GetPreferred();
		RString mods = poChosen.GetString();
		
		if (g_fLastInsertAttackPositionSeconds >= 0)
		{
			Attack a(ATTACK_LEVEL_1,
				 g_fLastInsertAttackPositionSeconds,
				 g_fLastInsertAttackDurationSeconds,
				 mods,
				 false,
				 false);
			AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks;
			int index = FindAttackAtTime(attacks, g_fLastInsertAttackPositionSeconds);
			if (index >= 0)
				attacks[index] = a;
			else
				attacks.push_back(a);
		}
		toRestore.Assign(ModsLevel_Preferred, this->originalPlayerOptions.GetPreferred());
	}
	else if( SM == SM_BackFromInsertCourseAttackPlayerOptions )
	{
		PlayerOptions poChosen = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetPreferred();
		RString sMods = poChosen.GetString();

		Course *pCourse = GAMESTATE->m_pCurCourse;
		CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex];
		if( g_fLastInsertAttackPositionSeconds < 0 )
		{
			ce.sModifiers = sMods;
		}
		else
		{
			Attack a(
				 ATTACK_LEVEL_1,
				 g_fLastInsertAttackPositionSeconds,
				 g_fLastInsertAttackDurationSeconds,
				 sMods,
				 false,
				 false );
			int iAttack = FindAttackAtTime( ce.attacks, g_fLastInsertAttackPositionSeconds );

			if( iAttack >= 0 )
				ce.attacks[iAttack] = a;
			else
				ce.attacks.push_back( a );
		}
	}
	else if( SM == SM_DoRevertToLastSave )
	{
		if( ScreenPrompt::s_LastAnswer == ANSWER_YES )
		{
			SaveUndo();
			CopyFromLastSave();
			m_pSteps->GetNoteData( m_NoteDataEdit );
			SetDirty( false );
		}
	}
	else if( SM == SM_DoRevertFromDisk )
	{
		if( ScreenPrompt::s_LastAnswer == ANSWER_YES )
		{
			SaveUndo();
			RevertFromDisk();
			m_pSteps->GetNoteData( m_NoteDataEdit );
			SetDirty( false );
		}
	}
	else if( SM == SM_DoEraseStepTiming )
	{
		if( ScreenPrompt::s_LastAnswer == ANSWER_YES )
		{
			SaveUndo();
			m_pSteps->m_Timing.Clear();
			SetDirty( true );
		}
	}
	else if( SM == SM_DoSaveAndExit ) // just asked "save before exiting? yes, no, cancel"
	{
		switch( ScreenPrompt::s_LastAnswer )
		{
		DEFAULT_FAIL( ScreenPrompt::s_LastAnswer );
		case ANSWER_YES:
			// This will send SM_SaveSuccessful or SM_SaveFailed.
			HandleMainMenuChoice( ScreenEdit::save_on_exit );
			return;
		case ANSWER_NO:
			// Don't save; just exit.
			SCREENMAN->SendMessageToTopScreen( SM_DoExit );
			return;
		case ANSWER_CANCEL:
			break; // do nothing
		}
	}
	else if( SM == SM_SaveSuccessful )
	{
		LOG->Trace( "Save successful." );
		CopyToLastSave();
		SetDirty( false );
		SONGMAN->Invalidate( GAMESTATE->m_pCurSong );

		if( m_CurrentAction == save_on_exit )
			ScreenPrompt::Prompt( SM_DoExit, SAVE_SUCCESSFUL );
		else
			SCREENMAN->SystemMessage( SAVE_SUCCESSFUL );
	}
	else if( SM == SM_SaveFailed ) // save failed; stay in the editor
	{
		/* We committed the steps to SongManager. Revert to the last save, and
		 * recommit the reversion to SongManager. */
		LOG->Trace( "Save failed. Changes uncommitted from memory." );
		CopyFromLastSave();
		m_pSteps->SetNoteData( m_NoteDataEdit );
	}
	else if( SM == SM_DoExit )
	{
		// IMPORTANT: CopyFromLastSave before deleting the Steps below
		CopyFromLastSave();

		/* The user has been given a choice to save.
		 * Delete all unsaved steps before exiting the editor. */
		/* FIXME: This code causes all the steps to be deleted if you quit
		 * without saving. However, without this code, any new steps will get
		 * saved on quit. -aj */
		
		// At this point, the last good song copy is in use.
		Song *pSong = GAMESTATE->m_pCurSong;
		const vector<Steps*> &apSteps = pSong->GetAllSteps();
		vector<Steps*> apToDelete;
		FOREACH_CONST( Steps *, apSteps, s )
		{
			// If we're not on the same style, let it go.
			if( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType != (*s)->m_StepsType )
				continue;
			// If autogenned, it isn't being saved.
			if( (*s)->IsAutogen() )
				continue;
			// If the notedata has content, let it go.
			if( !(*s)->GetNoteData().IsEmpty() )
				continue;
			// It's hard to say if these steps were saved to disk or not.
			/*
			if( !(*s)->GetSavedToDisk() )
				continue;
			 */
			apToDelete.push_back( *s );
		}
		FOREACH_CONST( Steps *, apToDelete, s )
		{
			Steps *pSteps = *s;
			pSong->DeleteSteps( pSteps );
			if( m_pSteps == pSteps )
				m_pSteps = NULL;
			if( GAMESTATE->m_pCurSteps[PLAYER_1].Get() == pSteps )
				GAMESTATE->m_pCurSteps[PLAYER_1].Set( NULL );
		}
		

		m_Out.StartTransitioning( SM_GoToNextScreen );
	}
	else if( SM == SM_GainFocus )
	{
		/* When another screen comes up, RageSounds takes over the sound timer.  When we come
		 * back, put the timer back to where it was. */
		SetBeat(m_fTrailingBeat);
	}
	else if( SM == SM_LoseFocus )
	{
		// Snap the trailing beat, in case we lose focus while tweening.
		m_fTrailingBeat = GetBeat(); //GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat;
	}

	ScreenWithMenuElements::HandleScreenMessage( SM );
}

void ScreenEdit::OnSnapModeChange()
{
	m_soundChangeSnap.Play();

	NoteType nt = m_SnapDisplay.GetNoteType();
	int iStepIndex = BeatToNoteRow( GetBeat() );
	int iElementsPerNoteType = BeatToNoteRow( NoteTypeToBeat(nt) );
	int iStepIndexHangover = iStepIndex % iElementsPerNoteType;
	SetBeat( GetBeat() - NoteRowToBeat( iStepIndexHangover ) );
}



// Helper function for below

// Begin helper functions for InputEdit


static void ChangeDescription( const RString &sNew )
{
	Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];

	// Don't erase edit descriptions.
	if( sNew.empty() && pSteps->GetDifficulty() == Difficulty_Edit )
		return;

	pSteps->SetDescription(sNew);
}

static void ChangeChartName( const RString &sNew )
{
	Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
	pSteps->SetChartName(sNew);
}

static void ChangeChartStyle( const RString &sNew )
{
	Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
	pSteps->SetChartStyle(sNew);
}

static void ChangeStepCredit( const RString &sNew )
{
	Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
	pSteps->SetCredit(sNew);
}

static void ChangeStepMeter( const RString &sNew )
{
	int diff = StringToInt(sNew);
	GAMESTATE->m_pCurSteps[PLAYER_1]->SetMeter(max(diff, 1));
}

static void ChangeMainTitle( const RString &sNew )
{
	Song* pSong = GAMESTATE->m_pCurSong;
	pSong->m_sMainTitle = sNew;
}

static void ChangeSubTitle( const RString &sNew )
{
	Song* pSong = GAMESTATE->m_pCurSong;
	pSong->m_sSubTitle = sNew;
}

static void ChangeArtist( const RString &sNew )
{
	Song* pSong = GAMESTATE->m_pCurSong;
	pSong->m_sArtist = sNew;
}

static void ChangeGenre( const RString &sNew )
{
	Song* pSong = GAMESTATE->m_pCurSong;
	pSong->m_sGenre = sNew;
}

static void ChangeCredit( const RString &sNew )
{
	Song* pSong = GAMESTATE->m_pCurSong;
	pSong->m_sCredit = sNew;
}

static void ChangeMainTitleTranslit( const RString &sNew )
{
	Song* pSong = GAMESTATE->m_pCurSong;
	pSong->m_sMainTitleTranslit = sNew;
}

static void ChangeSubTitleTranslit( const RString &sNew )
{
	Song* pSong = GAMESTATE->m_pCurSong;
	pSong->m_sSubTitleTranslit = sNew;
}

static void ChangeArtistTranslit( const RString &sNew )
{
	Song* pSong = GAMESTATE->m_pCurSong;
	pSong->m_sArtistTranslit = sNew;
}

static void ChangeLastSecondHint( const RString &sNew )
{
	Song &s = *GAMESTATE->m_pCurSong;
	s.SetSpecifiedLastSecond(StringToFloat(sNew));
}

static void ChangePreviewStart( const RString &sNew )
{
	GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds = StringToFloat( sNew );
}

static void ChangePreviewLength( const RString &sNew )
{
	GAMESTATE->m_pCurSong->m_fMusicSampleLengthSeconds = StringToFloat( sNew );
}

static void ChangeMinBPM( const RString &sNew )
{
	GAMESTATE->m_pCurSong->m_fSpecifiedBPMMin = StringToFloat( sNew );
}

static void ChangeStepsMinBPM(const RString &sNew)
{
	Steps *step = GAMESTATE->m_pCurSteps[PLAYER_1];
	step->SetMinBPM(StringToFloat(sNew));
}

static void ChangeMaxBPM( const RString &sNew )
{
	GAMESTATE->m_pCurSong->m_fSpecifiedBPMMax = StringToFloat( sNew );
}

static void ChangeStepsMaxBPM(const RString &sNew)
{
	Steps *step = GAMESTATE->m_pCurSteps[PLAYER_1];
	step->SetMaxBPM(StringToFloat(sNew));
}

const TimingData & ScreenEdit::GetAppropriateTiming() const
{
	if( GAMESTATE->m_bIsUsingStepTiming )
	{
		return *m_pSteps->GetTimingData();
	}
	return m_pSong->m_SongTiming;
}

TimingData & ScreenEdit::GetAppropriateTimingForUpdate()
{
	if( GAMESTATE->m_bIsUsingStepTiming )
	{
		// Copy from song if there is no step timing
		if( m_pSteps->m_Timing.empty() )
			m_pSteps->m_Timing = m_pSong->m_SongTiming;
		return m_pSteps->m_Timing;
	}
	return m_pSong->m_SongTiming;
}

SongPosition & ScreenEdit::GetAppropriatePosition() const
{
	if (GAMESTATE->m_bIsUsingStepTiming)
	{
		return GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position;
	}
	return GAMESTATE->m_Position;
}

inline void ScreenEdit::SetBeat(float fBeat)
{
	if( !GAMESTATE->m_bIsUsingStepTiming )
	{
		GAMESTATE->m_Position.m_fSongBeat = fBeat;
		GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat = m_pSteps->GetTimingData()->GetBeatFromElapsedTime(m_pSong->m_SongTiming.GetElapsedTimeFromBeat(fBeat));
	}
	else
	{
		GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat = fBeat;
		GAMESTATE->m_Position.m_fSongBeat = m_pSong->m_SongTiming.GetBeatFromElapsedTime(m_pSteps->GetTimingData()->GetElapsedTimeFromBeat(fBeat));
	}
}

inline float ScreenEdit::GetBeat()
{
	if( !GAMESTATE->m_bIsUsingStepTiming )
	{
		return GAMESTATE->m_Position.m_fSongBeat;
	}
	return GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat;
}

inline int ScreenEdit::GetRow()
{
	return BeatToNoteRow(GetBeat());
}

void ScreenEdit::DisplayTimingMenu()
{
	int row = GetRow();
	const TimingData &pTime = GetAppropriateTiming();
	bool bHasSpeedOnThisRow = pTime.GetSpeedSegmentAtRow( row )->GetRow() == row;
	// bool bIsSelecting = ( (m_NoteFieldEdit.m_iEndMarker != -1) && (m_NoteFieldEdit.m_iBeginMarker != -1) );


	g_TimingDataInformation.rows[beat_0_offset].SetOneUnthemedChoice( FloatToString(pTime.m_fBeat0OffsetInSeconds) );
	g_TimingDataInformation.rows[bpm].SetOneUnthemedChoice( FloatToString(pTime.GetBPMAtRow( row ) ) );
	g_TimingDataInformation.rows[stop].SetOneUnthemedChoice( FloatToString(pTime.GetStopAtRow( row ) ) ) ;
	g_TimingDataInformation.rows[delay].SetOneUnthemedChoice( FloatToString(pTime.GetDelayAtRow( row ) ) );

	g_TimingDataInformation.rows[time_signature].SetOneUnthemedChoice(
		ssprintf( "%d / %d",
			pTime.GetTimeSignatureSegmentAtRow(row)->GetNum(),
			pTime.GetTimeSignatureSegmentAtRow(row)->GetDen()
		)
	);

	g_TimingDataInformation.rows[label].SetOneUnthemedChoice( pTime.GetLabelAtRow( row ).c_str() );
	g_TimingDataInformation.rows[tickcount].SetOneUnthemedChoice( ssprintf("%d", pTime.GetTickcountAtRow( row ) ) );
	g_TimingDataInformation.rows[combo].SetOneUnthemedChoice( ssprintf("%d / %d",
																	   pTime.GetComboAtRow( row ),
																	   pTime.GetMissComboAtRow( row ) ) );
	g_TimingDataInformation.rows[warp].SetOneUnthemedChoice( FloatToString(pTime.GetWarpAtRow( row ) ) );
	g_TimingDataInformation.rows[speed_percent].SetOneUnthemedChoice( bHasSpeedOnThisRow ? FloatToString(pTime.GetSpeedPercentAtRow( row ) ) : "---" );
	g_TimingDataInformation.rows[speed_wait].SetOneUnthemedChoice( bHasSpeedOnThisRow ? FloatToString(pTime.GetSpeedWaitAtRow( row ) ) : "---" );

	RString starting = ( pTime.GetSpeedModeAtRow( row ) == 1 ? "Seconds" : "Beats" );
	g_TimingDataInformation.rows[speed_mode].SetOneUnthemedChoice( starting.c_str() );

	g_TimingDataInformation.rows[scroll].SetOneUnthemedChoice( FloatToString(pTime.GetScrollAtRow( row ) ) );
	g_TimingDataInformation.rows[fake].SetOneUnthemedChoice( FloatToString(pTime.GetFakeAtRow( row ) ) );

	// g_TimingDataInformation.rows[speed_percent].bEnabled = !bIsSelecting;
	g_TimingDataInformation.rows[speed_wait].bEnabled = bHasSpeedOnThisRow;
	g_TimingDataInformation.rows[speed_mode].bEnabled = bHasSpeedOnThisRow;

	EditMiniMenu( &g_TimingDataInformation, SM_BackFromTimingDataInformation );
}

// End helper functions

static LocalizedString REVERT_LAST_SAVE			( "ScreenEdit", "Do you want to revert to your last save?" );
static LocalizedString DESTROY_ALL_UNSAVED_CHANGES	( "ScreenEdit", "This will destroy all unsaved changes." );
static LocalizedString REVERT_FROM_DISK			( "ScreenEdit", "Do you want to revert from disk?" );
static LocalizedString SAVE_CHANGES_BEFORE_EXITING	( "ScreenEdit", "Do you want to save changes before exiting?" );

void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector<int> &iAnswers )
{
	GAMESTATE->SetProcessedTimingData(m_pSteps->GetTimingData());
	switch( c )
	{
		DEFAULT_FAIL( c );
		case play_selection:
			if( m_NoteFieldEdit.m_iBeginMarker!=-1 && m_NoteFieldEdit.m_iEndMarker!=-1 )
				HandleAlterMenuChoice( play );
			else if( m_NoteFieldEdit.m_iBeginMarker!=-1 )
				HandleMainMenuChoice( play_selection_start_to_end );
			else
				HandleMainMenuChoice( play_current_beat_to_end );
			break;
		case play_whole_song:
			{
				m_iStartPlayingAt = 0;
				m_iStopPlayingAt = m_NoteDataEdit.GetLastRow();
				TransitionEditState( STATE_PLAYING );
			}
			break;
		case play_selection_start_to_end:
			{
				m_iStartPlayingAt = m_NoteFieldEdit.m_iBeginMarker;
				m_iStopPlayingAt = max( m_iStartPlayingAt, m_NoteDataEdit.GetLastRow() );
				TransitionEditState( STATE_PLAYING );
			}
			break;
		case play_current_beat_to_end:
			{
				m_iStartPlayingAt = BeatToNoteRow(GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat);
				m_iStopPlayingAt = max( m_iStartPlayingAt, m_NoteDataEdit.GetLastRow() );
				TransitionEditState( STATE_PLAYING );
			}
			break;
		case set_selection_start:
			{
				const int iCurrentRow = BeatToNoteRow(GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat);
				if( m_NoteFieldEdit.m_iEndMarker!=-1 && iCurrentRow >= m_NoteFieldEdit.m_iEndMarker )
				{
					SCREENMAN->PlayInvalidSound();
				}
				else
				{
					m_NoteFieldEdit.m_iBeginMarker = iCurrentRow;
					m_soundMarker.Play();
				}
			}
			break;
		case set_selection_end:
			{
				const int iCurrentRow = BeatToNoteRow(GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat);
				if( m_NoteFieldEdit.m_iBeginMarker!=-1 && iCurrentRow <= m_NoteFieldEdit.m_iBeginMarker )
				{
					SCREENMAN->PlayInvalidSound();
				}
				else
				{
					m_NoteFieldEdit.m_iEndMarker = iCurrentRow;
					m_soundMarker.Play();
				}
			}
			break;
		case edit_steps_information:
			{
				/* XXX: If the difficulty is changed from EDIT, and pSteps->WasLoadedFromProfile()
				 * is true, we should warn that the steps will no longer be saved to the profile. */
				Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];

				g_StepsInformation.rows[difficulty].choices.clear();
				FOREACH_ENUM( Difficulty, dc )
				{
					g_StepsInformation.rows[difficulty].choices.push_back( "|" + CustomDifficultyToLocalizedString( GetCustomDifficulty(pSteps->m_StepsType, dc, CourseType_Invalid) ) );
				}
				g_StepsInformation.rows[difficulty].iDefaultChoice = pSteps->GetDifficulty();
				g_StepsInformation.rows[difficulty].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
				g_StepsInformation.rows[meter].SetOneUnthemedChoice( ssprintf("%d", pSteps->GetMeter()) );
				g_StepsInformation.rows[meter].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Home);
				g_StepsInformation.rows[predict_meter].SetOneUnthemedChoice( ssprintf("%.2f",pSteps->PredictMeter()) );
				g_StepsInformation.rows[chartname].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
				g_StepsInformation.rows[chartname].SetOneUnthemedChoice(pSteps->GetChartName());
				g_StepsInformation.rows[description].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
				g_StepsInformation.rows[description].SetOneUnthemedChoice( pSteps->GetDescription() );
				g_StepsInformation.rows[chartstyle].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
				g_StepsInformation.rows[chartstyle].SetOneUnthemedChoice( pSteps->GetChartStyle() );
				g_StepsInformation.rows[step_credit].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full);
				g_StepsInformation.rows[step_credit].SetOneUnthemedChoice( pSteps->GetCredit() );
				g_StepsInformation.rows[step_display_bpm].iDefaultChoice = pSteps->GetDisplayBPM();
				g_StepsInformation.rows[step_min_bpm].SetOneUnthemedChoice( FloatToString(pSteps->GetMinBPM()));
				g_StepsInformation.rows[step_max_bpm].SetOneUnthemedChoice( FloatToString(pSteps->GetMaxBPM()));
				EditMiniMenu( &g_StepsInformation, SM_BackFromStepsInformation, SM_None );
			}
			break;
		case view_steps_data:
		{
			float fMusicSeconds = m_pSoundMusic->GetLengthSeconds();
			Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
			const StepsTypeCategory &cat = GAMEMAN->GetStepsTypeInfo(pSteps->m_StepsType).m_StepsTypeCategory;
			if (cat == StepsTypeCategory_Couple || cat == StepsTypeCategory_Routine)
			{
				pair<int, int> tmp = m_NoteDataEdit.GetNumTapNotesTwoPlayer();
				g_StepsData.rows[tap_notes].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
				tmp = m_NoteDataEdit.GetNumJumpsTwoPlayer();
				g_StepsData.rows[jumps].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
				tmp = m_NoteDataEdit.GetNumHandsTwoPlayer();
				g_StepsData.rows[hands].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
				tmp = m_NoteDataEdit.GetNumQuadsTwoPlayer();
				g_StepsData.rows[quads].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
				tmp = m_NoteDataEdit.GetNumHoldNotesTwoPlayer();
				g_StepsData.rows[holds].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
				tmp = m_NoteDataEdit.GetNumMinesTwoPlayer();
				g_StepsData.rows[mines].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
				tmp = m_NoteDataEdit.GetNumRollsTwoPlayer();
				g_StepsData.rows[rolls].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
				tmp = m_NoteDataEdit.GetNumLiftsTwoPlayer();
				g_StepsData.rows[lifts].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
				tmp = m_NoteDataEdit.GetNumFakesTwoPlayer();
				g_StepsData.rows[fakes].SetOneUnthemedChoice( ssprintf("%d / %d", tmp.first, tmp.second) );
			}
			else
			{
				g_StepsData.rows[tap_notes].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumTapNotes()) );
				g_StepsData.rows[jumps].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumJumps()) );
				g_StepsData.rows[hands].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumHands()) );
				g_StepsData.rows[quads].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumQuads()) );
				g_StepsData.rows[holds].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumHoldNotes()) );
				g_StepsData.rows[mines].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumMines()) );
				g_StepsData.rows[rolls].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumRolls()) );
				g_StepsData.rows[lifts].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumLifts()) );
				g_StepsData.rows[fakes].SetOneUnthemedChoice( ssprintf("%d", m_NoteDataEdit.GetNumFakes()) );
			}
			g_StepsData.rows[stream].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetStreamRadarValue(m_NoteDataEdit,fMusicSeconds)) );
			g_StepsData.rows[voltage].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetVoltageRadarValue(m_NoteDataEdit,fMusicSeconds)) );
			g_StepsData.rows[air].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetAirRadarValue(m_NoteDataEdit,fMusicSeconds)) );
			g_StepsData.rows[freeze].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetFreezeRadarValue(m_NoteDataEdit,fMusicSeconds)) );
			g_StepsData.rows[chaos].SetOneUnthemedChoice( ssprintf("%.2f", NoteDataUtil::GetChaosRadarValue(m_NoteDataEdit,fMusicSeconds)) );
			EditMiniMenu( &g_StepsData, SM_BackFromStepsData, SM_None );
			break;
		}
		case save:
		case save_on_exit:
			{
				m_CurrentAction = c;

				// copy edit into current Steps
				m_pSteps->SetNoteData( m_NoteDataEdit );
				
				// don't forget the attacks.
				m_pSong->m_Attacks = GAMESTATE->m_pCurSong->m_Attacks;
				m_pSong->m_sAttackString = GAMESTATE->m_pCurSong->m_Attacks.ToVectorString();
				m_pSteps->m_Attacks = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks;
				m_pSteps->m_sAttackString = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.ToVectorString();

				switch( EDIT_MODE.GetValue() )
				{
				DEFAULT_FAIL( EDIT_MODE.GetValue() );
				case EditMode_Home:
					{
						ASSERT( m_pSteps->IsAnEdit() );

						RString sError;
						
						m_pSteps->CalculateRadarValues( m_pSong->m_fMusicLengthSeconds );
						if( !NotesWriterSM::WriteEditFileToMachine(m_pSong, m_pSteps, sError) )
						{
							ScreenPrompt::Prompt( SM_None, sError );
							break;
						}

						m_pSteps->SetSavedToDisk( true );

						// HACK: clear undo, so "exit" below knows we don't need to save.
						// This only works because important non-steps data can't be changed in
						// home mode (BPMs, stops).
						ClearUndo();

						SCREENMAN->ZeroNextUpdate();

						HandleScreenMessage( SM_SaveSuccessful );

						/* FIXME
						RString s;
						switch( c )
						{
						case save:			s = "ScreenMemcardSaveEditsAfterSave";	break;
						case save_on_exit:	s = "ScreenMemcardSaveEditsAfterExit";	break;
						default:		FAIL_M(ssprintf("Invalid menu choice: %i", c));
						}
						SCREENMAN->AddNewScreenToTop( s );
						*/
					}
					break;
				case EditMode_Full: 
					{
						// This will recalculate radar values.
						m_pSong->Save();
						SCREENMAN->ZeroNextUpdate();

						HandleScreenMessage( SM_SaveSuccessful );
					}
					break;
				case EditMode_CourseMods:
				case EditMode_Practice:
					break;
				}

				m_soundSave.Play();
			}
			break;
		case revert_to_last_save:
			ScreenPrompt::Prompt( SM_DoRevertToLastSave, REVERT_LAST_SAVE.GetValue() + "\n\n" + DESTROY_ALL_UNSAVED_CHANGES.GetValue(), PROMPT_YES_NO, ANSWER_NO );
			break;
		case revert_from_disk:
			ScreenPrompt::Prompt( SM_DoRevertFromDisk, REVERT_FROM_DISK.GetValue() + "\n\n" + DESTROY_ALL_UNSAVED_CHANGES.GetValue(), PROMPT_YES_NO, ANSWER_NO );
			break;
		case options:
			SCREENMAN->AddNewScreenToTop( "ScreenEditOptions", SM_BackFromOptions );
			break;
		case edit_song_info:
			{
				const Song* pSong = GAMESTATE->m_pCurSong;
				g_SongInformation.rows[main_title].SetOneUnthemedChoice( pSong->m_sMainTitle );
				g_SongInformation.rows[sub_title].SetOneUnthemedChoice( pSong->m_sSubTitle );
				g_SongInformation.rows[artist].SetOneUnthemedChoice( pSong->m_sArtist );
				g_SongInformation.rows[genre].SetOneUnthemedChoice( pSong->m_sGenre );
				g_SongInformation.rows[credit].SetOneUnthemedChoice( pSong->m_sCredit );
				g_SongInformation.rows[main_title_transliteration].SetOneUnthemedChoice( pSong->m_sMainTitleTranslit );
				g_SongInformation.rows[sub_title_transliteration].SetOneUnthemedChoice( pSong->m_sSubTitleTranslit );
				g_SongInformation.rows[artist_transliteration].SetOneUnthemedChoice( pSong->m_sArtistTranslit );
				g_SongInformation.rows[last_second_hint].SetOneUnthemedChoice( FloatToString(pSong->GetSpecifiedLastSecond()) );
				g_SongInformation.rows[preview_start].SetOneUnthemedChoice( FloatToString(pSong->m_fMusicSampleStartSeconds) );
				g_SongInformation.rows[preview_length].SetOneUnthemedChoice( FloatToString(pSong->m_fMusicSampleLengthSeconds) );
				g_SongInformation.rows[display_bpm].iDefaultChoice = pSong->m_DisplayBPMType;
				g_SongInformation.rows[min_bpm].SetOneUnthemedChoice( FloatToString(pSong->m_fSpecifiedBPMMin) );
				g_SongInformation.rows[max_bpm].SetOneUnthemedChoice( FloatToString(pSong->m_fSpecifiedBPMMax) );

				EditMiniMenu( &g_SongInformation, SM_BackFromSongInformation );
			}
			break;
		case edit_timing_data:
			{
				DisplayTimingMenu();
			}
			break;
		
		case play_preview_music:
			PlayPreviewMusic();
			break;
		case exit:
			switch( EDIT_MODE.GetValue() )
			{
			DEFAULT_FAIL( EDIT_MODE.GetValue() );
			case EditMode_Full:
			case EditMode_Home:
				if( IsDirty() )
					ScreenPrompt::Prompt( SM_DoSaveAndExit, SAVE_CHANGES_BEFORE_EXITING, PROMPT_YES_NO_CANCEL, ANSWER_CANCEL );
				else
					SCREENMAN->SendMessageToTopScreen( SM_DoExit );
				break;
			case EditMode_Practice:
			case EditMode_CourseMods:
				SCREENMAN->SendMessageToTopScreen( SM_DoExit );
				break;
			}
			break;
	};
	GAMESTATE->SetProcessedTimingData(NULL);
}

void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector<int> &iAnswers, bool bAllowUndo)
{
	ASSERT_M(m_NoteFieldEdit.m_iBeginMarker!=-1 && m_NoteFieldEdit.m_iEndMarker!=-1,
			 "You can only alter a selection of notes with a selection to begin with!");
	
	bool bSaveUndo = true;
	switch (c)
	{
		case play:
		case record:
		case cut:
		case copy:
		{
			bSaveUndo = false;
		}
		default:
			break;
	}
	
	if( bSaveUndo )
		SetDirty( true );
	
	/* We call HandleAreaMenuChoice recursively. Only the outermost
	 * HandleAreaMenuChoice should allow Undo so that the inner calls don't
	 * also save Undo and mess up the outermost */
	if( !bAllowUndo )
		bSaveUndo = false;
	
	if( bSaveUndo )
		SaveUndo();
	
	switch(c)
	{
		case cut:
		{
			HandleAlterMenuChoice( copy );
			HandleAlterMenuChoice( clear );
		}
			break;
		case copy:
		{
			
			m_Clipboard.ClearAll();
			m_Clipboard.CopyRange( m_NoteDataEdit, m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker );
		}
			break;
		case clear:
		{
			m_NoteDataEdit.ClearRange( m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker );
		}
			break;
		case quantize:
		{
			NoteType nt = (NoteType)iAnswers[c];
			NoteDataUtil::SnapToNearestNoteType(m_NoteDataEdit, nt, nt,
							    m_NoteFieldEdit.m_iBeginMarker,
							    m_NoteFieldEdit.m_iEndMarker );
			break;
		}
		case turn:
		{
			const NoteData OldClipboard( m_Clipboard );
			HandleAlterMenuChoice( cut );
			
			StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType;
			TurnType tt = (TurnType)iAnswers[c];
			switch( tt )
			{
				DEFAULT_FAIL( tt );
				case left:		NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::left );		break;
				case right:		NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::right );		break;
				case mirror:		NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::mirror );		break;
				case turn_backwards:		NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::backwards );		break;
				case shuffle:		NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::shuffle );		break;
				case super_shuffle:	NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::super_shuffle );	break;
			}
			
			HandleAreaMenuChoice( paste_at_begin_marker );
			m_Clipboard = OldClipboard;
		}
			break;
		case transform:
		{
			int iBeginRow = m_NoteFieldEdit.m_iBeginMarker;
			int iEndRow = m_NoteFieldEdit.m_iEndMarker;
			TransformType tt = (TransformType)iAnswers[c];
			StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType;
			
			switch( tt )
			{
					DEFAULT_FAIL( tt );
				case noholds:		NoteDataUtil::RemoveHoldNotes( m_NoteDataEdit, iBeginRow, iEndRow );	break;
				case nomines:		NoteDataUtil::RemoveMines( m_NoteDataEdit, iBeginRow, iEndRow );	break;
				case little:		NoteDataUtil::Little( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case wide:		NoteDataUtil::Wide( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case big:		NoteDataUtil::Big( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case quick:		NoteDataUtil::Quick( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case skippy:		NoteDataUtil::Skippy( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case add_mines:		NoteDataUtil::AddMines( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case echo:		NoteDataUtil::Echo( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case stomp:		NoteDataUtil::Stomp( m_NoteDataEdit, st, iBeginRow, iEndRow );		break;
				case planted:		NoteDataUtil::Planted( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case floored:		NoteDataUtil::Floored( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case twister:		NoteDataUtil::Twister( m_NoteDataEdit, iBeginRow, iEndRow );		break;
				case nojumps:		NoteDataUtil::RemoveJumps( m_NoteDataEdit, iBeginRow, iEndRow );	break;
				case nohands:		NoteDataUtil::RemoveHands( m_NoteDataEdit, iBeginRow, iEndRow );	break;
				case noquads:		NoteDataUtil::RemoveQuads( m_NoteDataEdit, iBeginRow, iEndRow );	break;
				case nostretch:		NoteDataUtil::RemoveStretch( m_NoteDataEdit, st, iBeginRow, iBeginRow );break;
			}
			
			// bake in the additions
			NoteDataUtil::ConvertAdditionsToRegular( m_NoteDataEdit );
			break;
		}
		case alter:
		{
			const NoteData OldClipboard( m_Clipboard );
			HandleAlterMenuChoice( cut );
			
			AlterType at = (AlterType)iAnswers[c];
			switch( at )
			{
				DEFAULT_FAIL( at );
				case autogen_to_fill_width:
				{
					NoteData temp( m_Clipboard );
					int iMaxNonEmptyTrack = NoteDataUtil::GetMaxNonEmptyTrack( temp );
					if( iMaxNonEmptyTrack == -1 )
						break;
					temp.SetNumTracks( iMaxNonEmptyTrack+1 );
					NoteDataUtil::LoadTransformedSlidingWindow( temp, m_Clipboard, m_Clipboard.GetNumTracks() );
					NoteDataUtil::RemoveStretch( m_Clipboard, GAMESTATE->m_pCurSteps[0]->m_StepsType );
				}
					break;
				case backwards:			NoteDataUtil::Backwards( m_Clipboard );			break;
				case swap_sides:		NoteDataUtil::SwapSides( m_Clipboard );			break;
				case copy_left_to_right:	NoteDataUtil::CopyLeftToRight( m_Clipboard );		break;
				case copy_right_to_left:	NoteDataUtil::CopyRightToLeft( m_Clipboard );		break;
				case clear_left:		NoteDataUtil::ClearLeft( m_Clipboard );			break;
				case clear_right:		NoteDataUtil::ClearRight( m_Clipboard );		break;
				case collapse_to_one:		NoteDataUtil::CollapseToOne( m_Clipboard );		break;
				case collapse_left:		NoteDataUtil::CollapseLeft( m_Clipboard );		break;
				case shift_left:		NoteDataUtil::ShiftLeft( m_Clipboard );			break;
				case shift_right:		NoteDataUtil::ShiftRight( m_Clipboard );		break;
			}
			
			HandleAreaMenuChoice( paste_at_begin_marker );
			m_Clipboard = OldClipboard;
			break;
		}
		case tempo:
		{
			// This affects all steps.
			AlterType at = (AlterType)iAnswers[c];
			float fScale = -1;
			
			switch( at )
			{
					DEFAULT_FAIL( at );
				case compress_2x:	fScale = 0.5f;		break;
				case compress_3_2:	fScale = 2.0f/3;	break;
				case compress_4_3:	fScale = 0.75f;		break;
				case expand_4_3:	fScale = 4.0f/3;	break;
				case expand_3_2:	fScale = 1.5f;		break;
				case expand_2x:		fScale = 2;		break;
			}
			
			int iStartIndex  = m_NoteFieldEdit.m_iBeginMarker;
			int iEndIndex    = m_NoteFieldEdit.m_iEndMarker;
			int iNewEndIndex = iEndIndex + lrintf( (iEndIndex - iStartIndex) * (fScale - 1) );
			
			// scale currently editing notes
			NoteDataUtil::ScaleRegion( m_NoteDataEdit, fScale, iStartIndex, iEndIndex );
			
			// scale timing data
			GetAppropriateTimingForUpdate().ScaleRegion(fScale, 
							   m_NoteFieldEdit.m_iBeginMarker, 
							   m_NoteFieldEdit.m_iEndMarker, true );
			
			m_NoteFieldEdit.m_iEndMarker = iNewEndIndex;
			break;
			
		}
			
		case play:
			m_iStartPlayingAt = m_NoteFieldEdit.m_iBeginMarker;
			m_iStopPlayingAt = m_NoteFieldEdit.m_iEndMarker;
			TransitionEditState( STATE_PLAYING );
			break;
		case record:
			m_iStartPlayingAt = m_NoteFieldEdit.m_iBeginMarker;
			m_iStopPlayingAt = m_NoteFieldEdit.m_iEndMarker;
			TransitionEditState( STATE_RECORDING );
			break;
		case preview_designation:
		{
			float fMarkerStart = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );
			float fMarkerEnd = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
			GAMESTATE->m_pCurSong->m_fMusicSampleStartSeconds = fMarkerStart;
			GAMESTATE->m_pCurSong->m_fMusicSampleLengthSeconds = fMarkerEnd - fMarkerStart;
			break;
		}
		case convert_to_pause:
		{
			float fMarkerStart = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );
			float fMarkerEnd = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
			
			// The length of the stop segment we're going to create.  This includes time spent in any
			// stops in the selection, which will be deleted and subsumed into the new stop.
			float fStopLength = fMarkerEnd - fMarkerStart;
			
			// be sure not to clobber the row at the start - a row at the end
			// can be dropped safely, though
			NoteDataUtil::DeleteRows( m_NoteDataEdit, 
						 m_NoteFieldEdit.m_iBeginMarker + 1,
						 m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker
						 );
			GetAppropriateTimingForUpdate().DeleteRows( m_NoteFieldEdit.m_iBeginMarker + 1,
							  m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker );
			GetAppropriateTimingForUpdate().SetStopAtRow( m_NoteFieldEdit.m_iBeginMarker, fStopLength );
			m_NoteFieldEdit.m_iBeginMarker = -1;
			m_NoteFieldEdit.m_iEndMarker = -1;
			break;
		}
		case convert_to_delay:
		{
			float fMarkerStart = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker) );
			float fMarkerEnd = GetAppropriateTiming().GetElapsedTimeFromBeat( NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) );
			
			// The length of the delay segment we're going to create.  This includes time spent in any
			// stops in the selection, which will be deleted and subsumed into the new stop.
			float fStopLength = fMarkerEnd - fMarkerStart;
			
			NoteDataUtil::DeleteRows( m_NoteDataEdit, 
						 m_NoteFieldEdit.m_iBeginMarker,
						 m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker
						 );
			GetAppropriateTimingForUpdate().DeleteRows( m_NoteFieldEdit.m_iBeginMarker,
							  m_NoteFieldEdit.m_iEndMarker-m_NoteFieldEdit.m_iBeginMarker );
			GetAppropriateTimingForUpdate().SetDelayAtRow( m_NoteFieldEdit.m_iBeginMarker, fStopLength );
			m_NoteFieldEdit.m_iBeginMarker = -1;
			m_NoteFieldEdit.m_iEndMarker = -1;
			break;
		}
		case convert_to_warp:
		{
			float startBeat = NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker);
			float lengthBeat = NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) - startBeat;
			GetAppropriateTimingForUpdate().SetWarpAtBeat(startBeat,lengthBeat);
			SetDirty(true);
			break;
		}
		case convert_to_attack:
		{
			float startBeat = NoteRowToBeat(m_NoteFieldEdit.m_iBeginMarker);
			float endBeat = NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker);
			const TimingData &timing = GetAppropriateTiming();
			float &start = g_fLastInsertAttackPositionSeconds;
			float &length = g_fLastInsertAttackDurationSeconds;
			start = timing.GetElapsedTimeFromBeat(startBeat);
			length = timing.GetElapsedTimeFromBeat(endBeat) - start;
			
			AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ?
				m_pSteps->m_Attacks : m_pSong->m_Attacks;
			int iAttack = FindAttackAtTime(attacks, start);
			
			ModsGroup<PlayerOptions> &toEdit = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions;
			this->originalPlayerOptions.Assign(ModsLevel_Preferred, toEdit.GetPreferred());
			PlayerOptions po;
			if (iAttack >= 0)
				po.FromString(attacks[iAttack].sModifiers);
				
			toEdit.Assign( ModsLevel_Preferred, po );
			SCREENMAN->AddNewScreenToTop( "ScreenPlayerOptions", SM_BackFromInsertStepAttackPlayerOptions );
			SetDirty(true);
			break;
		}
		case convert_to_fake:
		{
			int startRow = m_NoteFieldEdit.m_iBeginMarker;
			float lengthBeat = NoteRowToBeat(m_NoteFieldEdit.m_iEndMarker) - NoteRowToBeat(startRow);
			GetAppropriateTimingForUpdate().AddSegment( FakeSegment(startRow,lengthBeat) );
			SetDirty(true);
			break;
		}
		case routine_invert_notes:
		{
			NoteData &nd = this->m_NoteDataEdit;
			NoteField &nf = this->m_NoteFieldEdit;
			FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE(nd, r,
							      nf.m_iBeginMarker,
							      nf.m_iEndMarker)
			{
				for (int t = 0; t < nd.GetNumTracks(); t++)
				{
					const TapNote &tn = nd.GetTapNote(t, r);
					if (tn.type != TapNote::empty)
					{
						TapNote nTap = tn;
						nTap.pn = (tn.pn == PLAYER_1 ?
							   PLAYER_2 : PLAYER_1);
						m_NoteDataEdit.SetTapNote(t, r, nTap);
					}
				}
			}
			break;
		}
		case routine_mirror_1_to_2:
		case routine_mirror_2_to_1:
		{
			PlayerNumber oPN = (c == routine_mirror_1_to_2 ?
					    PLAYER_1 : PLAYER_2);
			PlayerNumber nPN = (c == routine_mirror_1_to_2 ?
					    PLAYER_2 : PLAYER_1);
			int nTrack = -1;
			NoteData &nd = this->m_NoteDataEdit;
			NoteField &nf = this->m_NoteFieldEdit;
			int tracks = nd.GetNumTracks();
			FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE(nd, r,
							      nf.m_iBeginMarker,
							      nf.m_iEndMarker)
			{
				for (int t = 0; t < tracks; t++)
				{
					const TapNote &tn = nd.GetTapNote(t, r);
					if (tn.type != TapNote::empty && tn.pn == oPN)
					{
						TapNote nTap = tn;
						nTap.pn = nPN;
						StepsType curType = GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType;
						// TODO: Find a better way to do this.
						if (curType == StepsType_dance_routine)
						{
							nTrack = tracks - t - 1;
						}
						else if (curType == StepsType_pump_routine)
						{
							switch (t)
							{
								case 0: nTrack = 8; break;
								case 1: nTrack = 9; break;
								case 2: nTrack = 7; break;
								case 3: nTrack = 5; break;
								case 4: nTrack = 6; break;
								case 5: nTrack = 3; break;
								case 6: nTrack = 4; break;
								case 7: nTrack = 2; break;
								case 8: nTrack = 0; break;
								case 9: nTrack = 1; break;
								default: FAIL_M(ssprintf("Invalid column %d for pump-routine", t)); break;
							}
						}
						m_NoteDataEdit.SetTapNote(nTrack, r, nTap);
					}
				}
			}
			break;
		}
		default: break;
	}
	
}

void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, const vector<int> &iAnswers, bool bAllowUndo )
{
	bool bSaveUndo = true;
	switch( c )
	{
		case clear_clipboard:
		case undo:
			bSaveUndo = false;
			break;
		default:
			break;
	}

	if( bSaveUndo )
		SetDirty( true );

	/* We call HandleAreaMenuChoice recursively. Only the outermost
	 * HandleAreaMenuChoice should allow Undo so that the inner calls don't
	 * also save Undo and mess up the outermost */
	if( !bAllowUndo )
		bSaveUndo = false;

	if( bSaveUndo )
		SaveUndo();

	switch( c )
	{
		DEFAULT_FAIL( c );

		case paste_at_current_beat:
		case paste_at_begin_marker:
			{
				int iDestFirstRow = -1;
				switch( c )
				{
					DEFAULT_FAIL( c );
					case paste_at_current_beat:
						iDestFirstRow = BeatToNoteRow( GetAppropriatePosition().m_fSongBeat );
						break;
					case paste_at_begin_marker:
						ASSERT( m_NoteFieldEdit.m_iBeginMarker!=-1 );
						iDestFirstRow = m_NoteFieldEdit.m_iBeginMarker;
						break;
				}

				int iRowsToCopy = m_Clipboard.GetLastRow()+1;
				m_NoteDataEdit.CopyRange( m_Clipboard, 0, iRowsToCopy, iDestFirstRow );
			}
			break;
			
		case insert_and_shift:
			NoteDataUtil::InsertRows( m_NoteDataEdit, GetRow(),
				iAnswers.size() > 0 ? 
				NoteTypeToRow((NoteType)iAnswers[c]) : 48);
			break;
		case delete_and_shift:
			NoteDataUtil::DeleteRows( m_NoteDataEdit, GetRow(),
				iAnswers.size() > 0 ? 
				NoteTypeToRow((NoteType)iAnswers[c]) : 48);
			break;
		case shift_pauses_forward:
			GetAppropriateTimingForUpdate().InsertRows( GetRow(),
				iAnswers.size() > 0 ? 
				NoteTypeToRow((NoteType)iAnswers[c]) : 48);
			break;
		case shift_pauses_backward:
			GetAppropriateTimingForUpdate().DeleteRows( GetRow() + 1,
			  iAnswers.size() > 0 ? 
			  NoteTypeToRow((NoteType)iAnswers[c]) : 48);
			break;

		case convert_pause_to_beat:
		{
			float fStopSeconds = GetAppropriateTiming().GetStopAtRow(GetRow());
			GetAppropriateTimingForUpdate().SetStopAtBeat( GetBeat() , 0 );

			float fStopBeats = fStopSeconds * GetAppropriateTiming().GetBPMAtBeat( GetBeat() ) / 60;

			// don't move the step from where it is, just move everything later
			NoteDataUtil::InsertRows( m_NoteDataEdit, GetRow() + 1, BeatToNoteRow(fStopBeats) );
			GetAppropriateTimingForUpdate().InsertRows( GetRow() + 1, BeatToNoteRow(fStopBeats) );
		}
		break;
		case convert_delay_to_beat:
		{
			TimingData &timing = GetAppropriateTimingForUpdate();
			float pause = timing.GetDelayAtRow(GetRow());
			timing.SetDelayAtRow(GetRow(), 0);

			float pauseBeats = pause * timing.GetBPMAtBeat(GetBeat()) / 60;

			NoteDataUtil::InsertRows(m_NoteDataEdit, GetRow(), BeatToNoteRow(pauseBeats));
			timing.InsertRows(GetRow(), BeatToNoteRow(pauseBeats));
			break;
		}
		case last_second_at_beat:
		{
			const TimingData &timing = GetAppropriateTiming();
			Song &s = *GAMESTATE->m_pCurSong;
			s.SetSpecifiedLastSecond(timing.GetElapsedTimeFromBeat(GetBeat()));
			break;
		}
		case undo:
			Undo();
			break;
		case clear_clipboard:
		{
			m_Clipboard.ClearAll();
			break;
		}
		case modify_attacks_at_row:
		{
			this->DoStepAttackMenu();
			break;
		}
		case modify_keysounds_at_row:
		{
			this->DoKeyboardTrackMenu();
			break;
		}
	};

	if( bSaveUndo )
		CheckNumberOfNotesAndUndo();
}

void ScreenEdit::HandleStepsDataChoice( StepsDataChoice c, const vector<int> &iAnswers )
{
	return; // nothing is done with the choices. Yet.
}

static LocalizedString ENTER_NEW_DESCRIPTION( "ScreenEdit", "Enter a new description." );
static LocalizedString ENTER_NEW_CHART_NAME("ScreenEdit", "Enter a new chart name.");
static LocalizedString ENTER_NEW_CHART_STYLE( "ScreenEdit", "Enter a new chart style." );
static LocalizedString ENTER_NEW_STEP_AUTHOR( "ScreenEdit", "Enter the author who made this step pattern." );
static LocalizedString ENTER_NEW_METER( "ScreenEdit", "Enter a new meter." );
static LocalizedString ENTER_MIN_BPM			("ScreenEdit","Enter a new min BPM.");
static LocalizedString ENTER_MAX_BPM			("ScreenEdit","Enter a new max BPM.");
void ScreenEdit::HandleStepsInformationChoice( StepsInformationChoice c, const vector<int> &iAnswers )
{
	Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1];
	Difficulty dc = (Difficulty)iAnswers[difficulty];
	pSteps->SetDifficulty( dc );
	pSteps->SetDisplayBPM(static_cast<DisplayBPM>(iAnswers[step_display_bpm]));
	
	switch( c )
	{
		case chartname:
		{
			ScreenTextEntry::TextEntry(SM_None,
									   ENTER_NEW_CHART_NAME,
									   m_pSteps->GetChartName(),
									   MAX_STEPS_DESCRIPTION_LENGTH,
									   SongUtil::ValidateCurrentStepsChartName,
									   ChangeChartName,
									   NULL);
			break;
		}
		case description:
		{
			ScreenTextEntry::TextEntry(SM_None,
									   ENTER_NEW_DESCRIPTION,
									   m_pSteps->GetDescription(),
									   MAX_STEPS_DESCRIPTION_LENGTH,
									   SongUtil::ValidateCurrentStepsDescription,
									   ChangeDescription,
									   NULL);
			break;
		}
		case chartstyle:
		{
			ScreenTextEntry::TextEntry(SM_None, 
									   ENTER_NEW_CHART_STYLE,
									   m_pSteps->GetChartStyle(), 
									   255,
									   NULL,
									   ChangeChartStyle, 
									   NULL);
			break;
		}
		case step_credit:
		{
			ScreenTextEntry::TextEntry(SM_None,
									   ENTER_NEW_STEP_AUTHOR,
									   m_pSteps->GetCredit(),
									   255,
									   SongUtil::ValidateCurrentStepsCredit,
									   ChangeStepCredit,
									   NULL);
			break;
		}
		case meter:
		{
			ScreenTextEntry::TextEntry(SM_BackFromDifficultyMeterChange,
									   ENTER_NEW_METER,
									   ssprintf("%d", m_pSteps->GetMeter()),
									   4,
									   NULL,
									   ChangeStepMeter,
									   NULL);
			break;
		}
		case step_min_bpm:
		{
			ScreenTextEntry::TextEntry(SM_None, ENTER_MIN_BPM,
									   FloatToString(pSteps->GetMinBPM()), 20,
									   ScreenTextEntry::FloatValidate,
									   ChangeStepsMinBPM, NULL);
			break;
		}
		case step_max_bpm:
		{
			ScreenTextEntry::TextEntry(SM_None, ENTER_MAX_BPM,
									   FloatToString(pSteps->GetMaxBPM()), 20,
									   ScreenTextEntry::FloatValidate,
									   ChangeStepsMaxBPM, NULL);
			break;
		}
	default:
		break;
	}
	SetDirty(true);
}

static LocalizedString ENTER_MAIN_TITLE			("ScreenEdit","Enter a new main title.");
static LocalizedString ENTER_SUB_TITLE			("ScreenEdit","Enter a new sub title.");
static LocalizedString ENTER_ARTIST			("ScreenEdit","Enter a new artist.");
static LocalizedString ENTER_GENRE			("ScreenEdit","Enter a new genre.");
static LocalizedString ENTER_CREDIT			("ScreenEdit","Enter a new credit.");
static LocalizedString ENTER_MAIN_TITLE_TRANSLIT	("ScreenEdit","Enter a new main title transliteration.");
static LocalizedString ENTER_SUB_TITLE_TRANSLIT		("ScreenEdit","Enter a new sub title transliteration.");
static LocalizedString ENTER_ARTIST_TRANSLIT		("ScreenEdit","Enter a new artist transliteration.");
static LocalizedString ENTER_LAST_SECOND_HINT		("ScreenEdit","Enter a new last second hint.");
static LocalizedString ENTER_PREVIEW_START		("ScreenEdit","Enter a new preview start.");
static LocalizedString ENTER_PREVIEW_LENGTH		("ScreenEdit","Enter a new preview length.");
void ScreenEdit::HandleSongInformationChoice( SongInformationChoice c, const vector<int> &iAnswers )
{
	Song* pSong = GAMESTATE->m_pCurSong;
	pSong->m_DisplayBPMType = static_cast<DisplayBPM>(iAnswers[display_bpm]);

	switch( c )
	{
	case main_title:
		ScreenTextEntry::TextEntry( SM_None, ENTER_MAIN_TITLE, pSong->m_sMainTitle, 100, NULL, ChangeMainTitle, NULL );
		break;
	case sub_title:
		ScreenTextEntry::TextEntry( SM_None, ENTER_SUB_TITLE, pSong->m_sSubTitle, 100, NULL, ChangeSubTitle, NULL );
		break;
	case artist:
		ScreenTextEntry::TextEntry( SM_None, ENTER_ARTIST, pSong->m_sArtist, 100, NULL, ChangeArtist, NULL );
		break;
	case genre:
		ScreenTextEntry::TextEntry( SM_None, ENTER_GENRE, pSong->m_sGenre, 100, NULL, ChangeGenre, NULL );
		break;
	case credit:
		ScreenTextEntry::TextEntry( SM_None, ENTER_CREDIT, pSong->m_sCredit, 100, NULL, ChangeCredit, NULL );
		break;
	case main_title_transliteration:
		ScreenTextEntry::TextEntry( SM_None, ENTER_MAIN_TITLE_TRANSLIT, pSong->m_sMainTitleTranslit, 100, NULL, ChangeMainTitleTranslit, NULL );
		break;
	case sub_title_transliteration:
		ScreenTextEntry::TextEntry( SM_None, ENTER_SUB_TITLE_TRANSLIT, pSong->m_sSubTitleTranslit, 100, NULL, ChangeSubTitleTranslit, NULL );
		break;
	case artist_transliteration:
		ScreenTextEntry::TextEntry( SM_None, ENTER_ARTIST_TRANSLIT, pSong->m_sArtistTranslit, 100, NULL, ChangeArtistTranslit, NULL );
		break;
	case last_second_hint:
		ScreenTextEntry::TextEntry( SM_None, ENTER_LAST_SECOND_HINT, 
					   FloatToString(pSong->GetSpecifiedLastSecond()), 20, 
					   ScreenTextEntry::FloatValidate, ChangeLastSecondHint, NULL );
		break;
	case preview_start:
		ScreenTextEntry::TextEntry( SM_None, ENTER_PREVIEW_START,
					   FloatToString(pSong->m_fMusicSampleStartSeconds), 20,
					   ScreenTextEntry::FloatValidate, ChangePreviewStart, NULL );
		break;
	case preview_length:
		ScreenTextEntry::TextEntry( SM_None, ENTER_PREVIEW_LENGTH,
					   FloatToString(pSong->m_fMusicSampleLengthSeconds), 20,
					   ScreenTextEntry::FloatValidate, ChangePreviewLength, NULL );
		break;
	case min_bpm:
		ScreenTextEntry::TextEntry( SM_None, ENTER_MIN_BPM,
					   FloatToString(pSong->m_fSpecifiedBPMMin), 20,
					   ScreenTextEntry::FloatValidate, ChangeMinBPM, NULL );
		break;
	case max_bpm:
		ScreenTextEntry::TextEntry( SM_None, ENTER_MAX_BPM,
					   FloatToString(pSong->m_fSpecifiedBPMMax), 20,
					   ScreenTextEntry::FloatValidate, ChangeMaxBPM, NULL );
		break;
	default: break;
	};
	SetDirty(true);
}


static LocalizedString ENTER_BEAT_0_OFFSET			( "ScreenEdit", "Enter the offset for the song.");
static LocalizedString ENTER_BPM_VALUE				( "ScreenEdit", "Enter a new BPM value." );
static LocalizedString ENTER_STOP_VALUE				( "ScreenEdit", "Enter a new Stop value." );
static LocalizedString ENTER_DELAY_VALUE			( "ScreenEdit", "Enter a new Delay value." );
static LocalizedString ENTER_TIME_SIGNATURE_VALUE	( "ScreenEdit", "Enter a new Time Signature." );
static LocalizedString ENTER_TICKCOUNT_VALUE			( "ScreenEdit", "Enter a new Tickcount value." );
static LocalizedString ENTER_COMBO_VALUE			( "ScreenEdit", "Enter a new Combo value." );
static LocalizedString ENTER_LABEL_VALUE			( "ScreenEdit", "Enter a new Label value." );
static LocalizedString ENTER_WARP_VALUE				( "ScreenEdit", "Enter a new Warp value." );
static LocalizedString ENTER_SPEED_PERCENT_VALUE		( "ScreenEdit", "Enter a new Speed percent value." );
static LocalizedString ENTER_SPEED_WAIT_VALUE			( "ScreenEdit", "Enter a new Speed wait value." );
static LocalizedString ENTER_SPEED_MODE_VALUE			( "ScreenEdit", "Enter a new Speed mode value." );
static LocalizedString ENTER_SCROLL_VALUE		( "ScreenEdit", "Enter a new Scroll value." );
static LocalizedString ENTER_FAKE_VALUE				( "ScreenEdit", "Enter a new Fake value." );
static LocalizedString CONFIRM_TIMING_ERASE			( "ScreenEdit", "Are you sure you want to erase this chart's timing data?" );
void ScreenEdit::HandleTimingDataInformationChoice( TimingDataInformationChoice c, const vector<int> &iAnswers )
{
	switch( c )
	{
	DEFAULT_FAIL( c );
	case beat_0_offset:
		ScreenTextEntry::TextEntry(
			SM_BackFromBeat0Change,
			ENTER_BEAT_0_OFFSET,
			FloatToString(GetAppropriateTiming().m_fBeat0OffsetInSeconds),
			20
			);
		break;
	case bpm:
		ScreenTextEntry::TextEntry( 
			SM_BackFromBPMChange, 
			ENTER_BPM_VALUE, 
			FloatToString( GetAppropriateTiming().GetBPMAtBeat( GetBeat() ) ),
			10
			);
		break;
	case stop:
		ScreenTextEntry::TextEntry( 
			SM_BackFromStopChange, 
			ENTER_STOP_VALUE, 
			FloatToString( GetAppropriateTiming().GetStopAtBeat( GetBeat() ) ),
			10
			);
		break;
	case delay:
		ScreenTextEntry::TextEntry( 
			SM_BackFromDelayChange, 
			ENTER_DELAY_VALUE, 
			FloatToString( GetAppropriateTiming().GetDelayAtBeat( GetBeat() ) ),
			10
		);
		break;
	case time_signature:
	{
		const TimeSignatureSegment *ts = GetAppropriateTiming().GetTimeSignatureSegmentAtBeat( GetBeat() );
		ScreenTextEntry::TextEntry(
			SM_BackFromTimeSignatureChange,
			ENTER_TIME_SIGNATURE_VALUE,
			ssprintf( "%d/%d", ts->GetNum(), ts->GetDen() ),
			8
			);
		break;
	}
	case tickcount:
		ScreenTextEntry::TextEntry(
			SM_BackFromTickcountChange,
			ENTER_TICKCOUNT_VALUE,
			ssprintf( "%d", GetAppropriateTiming().GetTickcountAtBeat( GetBeat() ) ),
			2
			);
		break;
	case combo:
	{
		const ComboSegment *cs = GetAppropriateTiming().GetComboSegmentAtBeat(GetBeat());
		ScreenTextEntry::TextEntry(SM_BackFromComboChange,
								   ENTER_COMBO_VALUE,
								   ssprintf( "%d/%d",
											cs->GetCombo(),
											cs->GetMissCombo()),
								   7);
		break;
	}
	case label:
		ScreenTextEntry::TextEntry(
		   SM_BackFromLabelChange,
		   ENTER_LABEL_VALUE,
		   ssprintf( "%s", GetAppropriateTiming().GetLabelAtBeat( GetBeat() ).c_str() ),
		   64
		   );
		break;
	case warp:
		ScreenTextEntry::TextEntry( 
		   SM_BackFromWarpChange, 
		   ENTER_WARP_VALUE, 
		   FloatToString( GetAppropriateTiming().GetWarpAtBeat( GetBeat() ) ),
		   10
		   );
		break;
	case speed_percent:
		ScreenTextEntry::TextEntry(
		   SM_BackFromSpeedPercentChange,
		   ENTER_SPEED_PERCENT_VALUE,
		   FloatToString( GetAppropriateTiming().GetSpeedSegmentAtBeat( GetBeat() )->GetRatio() ),
		   10
		   );
		break;
	case scroll:
		ScreenTextEntry::TextEntry(
		   SM_BackFromScrollChange,
		   ENTER_SCROLL_VALUE,
		   FloatToString( GetAppropriateTiming().GetScrollSegmentAtBeat( GetBeat() )->GetRatio() ),
		   10
		   );
		break;
	case speed_wait:
		ScreenTextEntry::TextEntry(
		   SM_BackFromSpeedWaitChange,
		   ENTER_SPEED_WAIT_VALUE,
		   FloatToString( GetAppropriateTiming().GetSpeedSegmentAtBeat( GetBeat() )->GetDelay() ),
		   10
		   );
		break;
	case speed_mode: 
		{
			ScreenTextEntry::TextEntry(
						SM_BackFromSpeedModeChange,
						   ENTER_SPEED_MODE_VALUE,
						   "",
						   3
			);
			
			break;
		}
	case fake:
		{
			ScreenTextEntry::TextEntry(
				SM_BackFromFakeChange,
				ENTER_FAKE_VALUE,
			        FloatToString(GetAppropriateTiming().GetFakeAtBeat( GetBeat() ) ),
				10
			);
			break;
		}
	case copy_full_timing:
	{
		clipboardFullTiming = GetAppropriateTiming();
		break;
	}
	case paste_full_timing:
	{
		if (GAMESTATE->m_bIsUsingStepTiming)
		{
			GAMESTATE->m_pCurSteps[PLAYER_1]->m_Timing = clipboardFullTiming;
		}
		else
		{
			GAMESTATE->m_pCurSong->m_SongTiming = clipboardFullTiming;
		}
		SetDirty(true);
		break;
	}
	case erase_step_timing:
		ScreenPrompt::Prompt( SM_DoEraseStepTiming, CONFIRM_TIMING_ERASE , PROMPT_YES_NO, ANSWER_NO );
	break;
		
	}
}

void ScreenEdit::HandleBGChangeChoice( BGChangeChoice c, const vector<int> &iAnswers )
{
	BackgroundChange newChange;

	FOREACH( BackgroundChange, m_pSong->GetBackgroundChanges(g_CurrentBGChangeLayer), iter )
	{
		if( iter->m_fStartBeat == GAMESTATE->m_Position.m_fSongBeat )
		{
			newChange = *iter;
			// delete the old change.  We'll add a new one below.
			m_pSong->GetBackgroundChanges(g_CurrentBGChangeLayer).erase( iter );
			break;
		}
	}

	newChange.m_fStartBeat    = GAMESTATE->m_Position.m_fSongBeat;
	newChange.m_fRate         = StringToFloat( g_BackgroundChange.rows[rate].choices[iAnswers[rate]] )/100.f;
	newChange.m_sTransition   = iAnswers[transition] ? g_BackgroundChange.rows[transition].choices[iAnswers[transition]] : RString();
	newChange.m_def.m_sEffect = iAnswers[effect]     ? g_BackgroundChange.rows[effect].choices[iAnswers[effect]]         : RString();
	newChange.m_def.m_sColor1 = iAnswers[color1]     ? g_BackgroundChange.rows[color1].choices[iAnswers[color1]]         : RString();
	newChange.m_def.m_sColor2 = iAnswers[color2]     ? g_BackgroundChange.rows[color2].choices[iAnswers[color2]]         : RString();
	switch( iAnswers[file1_type] )
	{
	DEFAULT_FAIL( iAnswers[file1_type] );
	case none:			newChange.m_def.m_sFile1 = "";					break;
	case dynamic_random:		newChange.m_def.m_sFile1 = RANDOM_BACKGROUND_FILE;		break;
	case baked_random:		newChange.m_def.m_sFile1 = GetOneBakedRandomFile( m_pSong );	break;
	case song_bganimation:
	case song_movie:
	case song_bitmap:
	case global_bganimation:
	case global_movie:
	case global_movie_song_group:
	case global_movie_song_group_and_genre:
		{
			BGChangeChoice row1 = (BGChangeChoice)(file1_song_bganimation + iAnswers[file1_type]);
			newChange.m_def.m_sFile1 = g_BackgroundChange.rows[row1].choices.empty() ? "" : g_BackgroundChange.rows[row1].choices[iAnswers[row1]];
		}
		break;
	}
	switch( iAnswers[file2_type] )
	{
	DEFAULT_FAIL(iAnswers[file2_type]);
	case none:				newChange.m_def.m_sFile2 = "";				break;
	case dynamic_random:		newChange.m_def.m_sFile2 = RANDOM_BACKGROUND_FILE;		break;
	case baked_random:		newChange.m_def.m_sFile2 = GetOneBakedRandomFile( m_pSong );	break;
	case song_bganimation:
	case song_movie:
	case song_bitmap:
	case global_bganimation:
	case global_movie:
	case global_movie_song_group:
	case global_movie_song_group_and_genre:
		{
			BGChangeChoice row2 = (BGChangeChoice)(file2_song_bganimation + iAnswers[file2_type]);
			newChange.m_def.m_sFile2 = g_BackgroundChange.rows[row2].choices.empty() ? "" : g_BackgroundChange.rows[row2].choices[iAnswers[row2]];
		}
		break;
	}


	if( c == delete_change || newChange.m_def.m_sFile1.empty() )
	{
		// don't add
	}
	else
	{
		m_pSong->AddBackgroundChange( g_CurrentBGChangeLayer, newChange );
	}
	g_CurrentBGChangeLayer = BACKGROUND_LAYER_Invalid;
}

void ScreenEdit::SetupCourseAttacks()
{
	/* This is the first beat that can be changed without it being visible.  Until
	 * we draw for the first time, any beat can be changed. */
	GAMESTATE->m_pPlayerState[PLAYER_1]->m_fLastDrawnBeat = -100;

	// Put course options into effect.
	GAMESTATE->m_pPlayerState[PLAYER_1]->m_ModsToApply.clear();
	GAMESTATE->m_pPlayerState[PLAYER_1]->RemoveActiveAttacks();


	if( GAMESTATE->m_pCurCourse )
	{
		AttackArray Attacks;
		
		if( EDIT_MODE == EditMode_CourseMods )
		{
			Attacks = GAMESTATE->m_pCurCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex].attacks;
		}
		else
		{
			GAMESTATE->m_pCurCourse->RevertFromDisk();	// Remove this and have a separate reload key?

			for( unsigned e = 0; e < GAMESTATE->m_pCurCourse->m_vEntries.size(); ++e )
			{
				if( GAMESTATE->m_pCurCourse->m_vEntries[e].songID.ToSong() != m_pSong )
					continue;

				Attacks = GAMESTATE->m_pCurCourse->m_vEntries[e].attacks;
				break;
			}
		}

		FOREACH( Attack, Attacks, attack )
			GAMESTATE->m_pPlayerState[PLAYER_1]->LaunchAttack( *attack );
	}
	else 
	{
		const PlayerOptions &p = GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent();
		if (GAMESTATE->m_pCurSong && p.m_fNoAttack == 0 && p.m_fRandAttack == 0 )
		{
			AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ?
				GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks :
				GAMESTATE->m_pCurSong->m_Attacks;
			
			if (attacks.size() > 0)
			{
				FOREACH(Attack, attacks, attack)
				{
					float fBeat = GetAppropriateTiming().GetBeatFromElapsedTime(attack->fStartSecond);
					if (fBeat >= GetBeat())
						GAMESTATE->m_pPlayerState[PLAYER_1]->LaunchAttack( *attack );
				}
			}
		}
	}
	
	GAMESTATE->m_pPlayerState[PLAYER_1]->RebuildPlayerOptionsFromActiveAttacks();
}

void ScreenEdit::CopyToLastSave()
{
	ASSERT( GAMESTATE->m_pCurSong != NULL );
	ASSERT( GAMESTATE->m_pCurSteps[PLAYER_1] != NULL );
	m_SongLastSave = *GAMESTATE->m_pCurSong;
	m_vStepsLastSave.clear();
	const vector<Steps*> &vSteps = GAMESTATE->m_pCurSong->GetStepsByStepsType( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType );
	FOREACH_CONST( Steps*, vSteps, it )
		m_vStepsLastSave.push_back( **it );
}

void ScreenEdit::CopyFromLastSave()
{
	// We are assuming two things here:
	// 1) No steps can be created by ScreenEdit
	// 2) No steps can be deleted by ScreenEdit (except possibly when we exit)
	*GAMESTATE->m_pCurSong = m_SongLastSave;
	const vector<Steps*> &vSteps = GAMESTATE->m_pCurSong->GetStepsByStepsType( GAMESTATE->m_pCurSteps[PLAYER_1]->m_StepsType );
	ASSERT_M( vSteps.size() == m_vStepsLastSave.size(), ssprintf("Step sizes don't match: %d, %d", int(vSteps.size()), int(m_vStepsLastSave.size())) );
	for( unsigned i = 0; i < vSteps.size(); ++i )
		*vSteps[i] = m_vStepsLastSave[i];
}

void ScreenEdit::RevertFromDisk()
{
	ASSERT( GAMESTATE->m_pCurSteps[PLAYER_1] != NULL );
	StepsID id;
	id.FromSteps( GAMESTATE->m_pCurSteps[PLAYER_1] );
	ASSERT( id.IsValid() );

	GAMESTATE->m_pCurSong->ReloadFromSongDir( GAMESTATE->m_pCurSong->GetSongDir() );

	Steps *pNewSteps = id.ToSteps( GAMESTATE->m_pCurSong, true );
	if( !pNewSteps )
	{
		// If the Steps we were currently editing vanished when we did the revert,
		// put a blank Steps in its place.  Note that this does not have to be the
		// work of someone maliciously changing the simfile; it could happen to 
		// someone editing a new stepchart and reverting from disk, for example.
		pNewSteps = GAMESTATE->m_pCurSong->CreateSteps();
		pNewSteps->CreateBlank( id.GetStepsType() );
		pNewSteps->SetDifficulty( id.GetDifficulty() );
		GAMESTATE->m_pCurSong->AddSteps( pNewSteps );
	}
	GAMESTATE->m_pCurSteps[PLAYER_1].Set( pNewSteps );
	m_pSteps = pNewSteps;

	CopyToLastSave();
	SetDirty( false );
	SONGMAN->Invalidate( GAMESTATE->m_pCurSong );
}

void ScreenEdit::SaveUndo()
{
	m_bHasUndo = true;
	m_Undo.CopyAll( m_NoteDataEdit );
}

static LocalizedString UNDO			("ScreenEdit", "Undo");
static LocalizedString CANT_UNDO		("ScreenEdit", "Can't undo - no undo data.");
void ScreenEdit::Undo()
{
	if( m_bHasUndo )
	{
		swap( m_Undo, m_NoteDataEdit );
		SCREENMAN->SystemMessage( UNDO );
	}
	else
	{
		SCREENMAN->SystemMessage( CANT_UNDO );
		SCREENMAN->PlayInvalidSound();
	}
}

void ScreenEdit::ClearUndo()
{
	m_bHasUndo = false;
	m_Undo.ClearAll();
}

static LocalizedString CREATES_MORE_THAN_NOTES	( "ScreenEdit", "This change creates more than %d notes in a measure." );
static LocalizedString CREATES_NOTES_PAST_END	( "ScreenEdit", "This change creates notes past the end of the music and is not allowed." );
static LocalizedString CHANGE_REVERTED		( "ScreenEdit", "The change has been reverted." );
void ScreenEdit::CheckNumberOfNotesAndUndo()
{
	if( EDIT_MODE.GetValue() != EditMode_Home )
		return;

	const float fBeat = GAMESTATE->m_pPlayerState[PLAYER_1]->m_Position.m_fSongBeat;
	const TimeSignatureSegment * curTime = GAMESTATE->m_pCurSong->m_SongTiming.GetTimeSignatureSegmentAtBeat( fBeat );
	int rowsPerMeasure = curTime->GetDen() * curTime->GetNum();

	for( int row=0; row<=m_NoteDataEdit.GetLastRow(); row+=rowsPerMeasure )
	{
		int iNumNotesThisMeasure = 0;
		FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( m_NoteDataEdit, r, row, row+rowsPerMeasure )
			iNumNotesThisMeasure += m_NoteDataEdit.GetNumTapNonEmptyTracks( r );
	}

	if( GAMESTATE->m_pCurSteps[0]->IsAnEdit() )
	{
		/* Check that the action didn't push notes any farther past the last
		 * measure. This blocks Insert Beat from pushing past the end, but allows
		 * Delete Beat to pull back the notes that are already past the end.
		 */
		float fNewLastBeat = m_NoteDataEdit.GetLastBeat();
		bool bLastBeatIncreased = fNewLastBeat > m_Undo.GetLastBeat();
		bool bPassedTheEnd = fNewLastBeat > GetMaximumBeatForNewNote();
		if( bLastBeatIncreased && bPassedTheEnd )
		{
			Undo();
			m_bHasUndo = false;
			RString sError = CREATES_NOTES_PAST_END.GetValue() + "\n\n" + CHANGE_REVERTED.GetValue();
			ScreenPrompt::Prompt( SM_None, sError );
			return;
		}
	}
}

float ScreenEdit::GetMaximumBeatForNewNote() const
{
	switch( EDIT_MODE.GetValue() )
	{
	DEFAULT_FAIL( EDIT_MODE.GetValue() );
	case EditMode_Practice:
	case EditMode_CourseMods:
	case EditMode_Home:
		{
			Song &s = *GAMESTATE->m_pCurSong;
			float fEndBeat = s.GetLastBeat();

			/* Round up to the next measure end.  Some songs end on weird beats 
			 * mid-measure, and it's odd to have movement capped to these weird
			 * beats. */
			TimingData &timing = s.m_SongTiming;
			float playerBeat = GetAppropriatePosition().m_fSongBeat;
			int beatsPerMeasure = timing.GetTimeSignatureSegmentAtBeat( playerBeat )->GetNum();
			fEndBeat += beatsPerMeasure;
			fEndBeat = ftruncf( fEndBeat, (float)beatsPerMeasure );

			return fEndBeat;
		}
	case EditMode_Full:
		return NoteRowToBeat(MAX_NOTE_ROW);
	}
}

float ScreenEdit::GetMaximumBeatForMoving() const
{
	float fEndBeat = GetMaximumBeatForNewNote();

	/* Jump to GetLastBeat even if it's past the song's last beat
	 * so that users can delete garbage steps past then end that they have 
	 * have inserted in a text editor.  Once they delete all steps on 
	 * GetLastBeat() and move off of that beat, they won't be able to return. */
	fEndBeat = max( fEndBeat, m_NoteDataEdit.GetLastBeat() );

	return fEndBeat;
}

struct EditHelpLine
{
	const char *szEnglishDescription;
	vector<EditButton> veb;

	EditHelpLine( 
		const char *_szEnglishDescription,
		EditButton eb0, 
		EditButton eb1 = EditButton_Invalid, 
		EditButton eb2 = EditButton_Invalid, 
		EditButton eb3 = EditButton_Invalid, 
		EditButton eb4 = EditButton_Invalid, 
		EditButton eb5 = EditButton_Invalid, 
		EditButton eb6 = EditButton_Invalid, 
		EditButton eb7 = EditButton_Invalid, 
		EditButton eb8 = EditButton_Invalid, 
		EditButton eb9 = EditButton_Invalid )
	{
		szEnglishDescription = _szEnglishDescription;
#define PUSH_IF_VALID( x ) if( x != EditButton_Invalid ) veb.push_back( x );
		PUSH_IF_VALID( eb0 );
		PUSH_IF_VALID( eb1 );
		PUSH_IF_VALID( eb2 ); 
		PUSH_IF_VALID( eb3 ); 
		PUSH_IF_VALID( eb4 ); 
		PUSH_IF_VALID( eb5 ); 
		PUSH_IF_VALID( eb6 ); 
		PUSH_IF_VALID( eb7 ); 
		PUSH_IF_VALID( eb8 ); 
		PUSH_IF_VALID( eb9 ); 
#undef PUSH_IF_VALID
	}
};
// TODO: Identify which of these can be removed and sent to a readme.
static const EditHelpLine g_EditHelpLines[] =
{
	EditHelpLine( "Move cursor",					EDIT_BUTTON_SCROLL_UP_LINE,		EDIT_BUTTON_SCROLL_DOWN_LINE ),
	EditHelpLine( "Jump measure",					EDIT_BUTTON_SCROLL_UP_PAGE,		EDIT_BUTTON_SCROLL_DOWN_PAGE ),
	EditHelpLine( "Jump measure",					EDIT_BUTTON_SCROLL_PREV_MEASURE,	EDIT_BUTTON_SCROLL_NEXT_MEASURE ),
	EditHelpLine( "Select region",					EDIT_BUTTON_SCROLL_SELECT ),
	EditHelpLine( "Jump to first/last beat",			EDIT_BUTTON_SCROLL_HOME,		EDIT_BUTTON_SCROLL_END ),
	EditHelpLine( "Change zoom",					EDIT_BUTTON_SCROLL_SPEED_UP,		EDIT_BUTTON_SCROLL_SPEED_DOWN ),
	EditHelpLine( "Play",						EDIT_BUTTON_PLAY_SELECTION ),
	EditHelpLine( "Play current beat to end",			EDIT_BUTTON_PLAY_FROM_CURSOR ),
	EditHelpLine( "Play whole song",				EDIT_BUTTON_PLAY_FROM_START ),
	EditHelpLine( "Record",						EDIT_BUTTON_RECORD_SELECTION ),
	EditHelpLine( "Set selection",					EDIT_BUTTON_LAY_SELECT ),
	EditHelpLine( "Next/prev steps of same StepsType",		EDIT_BUTTON_OPEN_NEXT_STEPS,		EDIT_BUTTON_OPEN_PREV_STEPS ),
	EditHelpLine( "Decrease/increase BPM at cur beat",		EDIT_BUTTON_BPM_DOWN,			EDIT_BUTTON_BPM_UP ),
	EditHelpLine( "Decrease/increase stop at cur beat",		EDIT_BUTTON_STOP_DOWN,			EDIT_BUTTON_STOP_UP ),
	EditHelpLine( "Decrease/increase delay at cur beat",		EDIT_BUTTON_DELAY_DOWN,			EDIT_BUTTON_DELAY_UP ),
	EditHelpLine( "Decrease/increase music offset",			EDIT_BUTTON_OFFSET_DOWN,		EDIT_BUTTON_OFFSET_UP ),
	EditHelpLine( "Decrease/increase sample music start",		EDIT_BUTTON_SAMPLE_START_DOWN,		EDIT_BUTTON_SAMPLE_START_UP ),
	EditHelpLine( "Decrease/increase sample music length",		EDIT_BUTTON_SAMPLE_LENGTH_DOWN,		EDIT_BUTTON_SAMPLE_LENGTH_UP ),
	EditHelpLine( "Play sample music",				EDIT_BUTTON_PLAY_SAMPLE_MUSIC ),
	EditHelpLine( "Add/Edit Background Change",			EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU ),
	EditHelpLine( "Insert beat and shift down",			EDIT_BUTTON_INSERT ),
	EditHelpLine( "Shift BPM changes and stops down one beat",	EDIT_BUTTON_INSERT_SHIFT_PAUSES ),
	EditHelpLine( "Delete beat and shift up",			EDIT_BUTTON_DELETE ),
	EditHelpLine( "Shift BPM changes and stops up one beat",	EDIT_BUTTON_DELETE_SHIFT_PAUSES ),
	EditHelpLine( "Cycle between tap notes",			EDIT_BUTTON_CYCLE_TAP_LEFT,		EDIT_BUTTON_CYCLE_TAP_RIGHT ),
	EditHelpLine( "Add to/remove from right half",			EDIT_BUTTON_RIGHT_SIDE ),
	EditHelpLine( "Switch Timing",					EDIT_BUTTON_SWITCH_TIMINGS ),
	EditHelpLine( "Switch player (Routine only)",			EDIT_BUTTON_SWITCH_PLAYERS ),
};

static bool IsMapped( EditButton eb, const MapEditToDI &editmap )
{
	for( int s=0; s<NUM_EDIT_TO_DEVICE_SLOTS; s++ )
	{
		DeviceInput diPress = editmap.button[eb][s];
		if( diPress.IsValid() )
			return true;
	}
	return false;
}

static void ProcessKeyName( RString &s )
{
	s.Replace( "Key_", "" );
}

static void ProcessKeyNames( vector<RString> &vs )
{
	FOREACH( RString, vs, s )
		ProcessKeyName( *s );

	sort( vs.begin(), vs.end() );
	vector<RString>::iterator toDelete = unique( vs.begin(), vs.end() );
	vs.erase(toDelete, vs.end());
}

static RString GetDeviceButtonsLocalized( const vector<EditButton> &veb, const MapEditToDI &editmap )
{
	vector<RString> vsPress;
	vector<RString> vsHold;
	FOREACH_CONST( EditButton, veb, eb )
	{
		if( !IsMapped( *eb, editmap ) )
			continue;

		for( int s=0; s<NUM_EDIT_TO_DEVICE_SLOTS; s++ )
		{
			DeviceInput diPress = editmap.button[*eb][s];
			DeviceInput diHold = editmap.hold[*eb][s];
			if( diPress.IsValid() )
				vsPress.push_back( Capitalize(INPUTMAN->GetLocalizedInputString(diPress)) );
			if( diHold.IsValid() )
				vsHold.push_back( Capitalize(INPUTMAN->GetLocalizedInputString(diHold)) );
		}
	}

	ProcessKeyNames( vsPress );
	ProcessKeyNames( vsHold );

	RString s = join("/",vsPress);
	if( !vsHold.empty() )
		s = join("/",vsHold) + " + " + s;
	return s;
}

void ScreenEdit::DoStepAttackMenu()
{
	const TimingData &timing = GetAppropriateTiming();
	float startTime = timing.GetElapsedTimeFromBeat(GetBeat());
	AttackArray &attacks = 
		(GAMESTATE->m_bIsUsingStepTiming ? m_pSteps->m_Attacks : m_pSong->m_Attacks);
	vector<int> points = FindAllAttacksAtTime(attacks, startTime);
		
	g_AttackAtTimeMenu.rows.clear();
	unsigned index = 0;
		
	FOREACH(int, points, i)
	{
		const Attack &attack = attacks[*i];
		RString desc = ssprintf("%g -> %g (%d mod[s])",
			startTime, startTime + attack.fSecsRemaining,
			attack.GetNumAttacks());
			
		g_AttackAtTimeMenu.rows.push_back(MenuRowDef(index++,
			desc,
			true,
			EditMode_CourseMods,
			false,
			false,
			0,
			"Modify",
			"Delete"));
	}
	g_AttackAtTimeMenu.rows.push_back(MenuRowDef(index,
		"Add Attack",
		true,
		EditMode_CourseMods,
		true,
		true,
		0,
		NULL));
		
	EditMiniMenu(&g_AttackAtTimeMenu, SM_BackFromAttackAtTime);
}

static LocalizedString TRACK_NUM("ScreenEdit", "Track %d");
static LocalizedString NO_KEYSND("ScreenEdit", "None");
static LocalizedString NEWKEYSND("ScreenEdit", "New Sound");

void ScreenEdit::DoKeyboardTrackMenu()
{
	g_KeysoundTrack.rows.clear();
	vector<RString> &kses = m_pSong->m_vsKeysoundFile;
	
	vector<RString> choices;
	FOREACH(RString, kses, ks)
	{
		choices.push_back(*ks);
	}
	choices.push_back(NEWKEYSND);
	choices.push_back(NO_KEYSND);
	int numKeysounds = kses.size();
	int foundKeysounds = 0;
	for (int i = 0; i < m_NoteDataEdit.GetNumTracks(); ++i)
	{
		const TapNote &tn = m_NoteDataEdit.GetTapNote(i, this->GetRow());
		int keyIndex = tn.iKeysoundIndex;
		if (keyIndex == -1)
		{
			keyIndex = numKeysounds;
		}
		else
		{
			++foundKeysounds;
		}
		
		g_KeysoundTrack.rows.push_back(MenuRowDef(i, ssprintf(TRACK_NUM.GetValue(), i + 1),
												  true, EditMode_Full, false, false, keyIndex, choices));
	}
	g_KeysoundTrack.rows.push_back(MenuRowDef(m_NoteDataEdit.GetNumTracks(), "Remove Keysound",
											  foundKeysounds > 0, EditMode_Full, false, false, 0, kses));
	
	EditMiniMenu(&g_KeysoundTrack, SM_BackFromKeysoundTrack);
}

void ScreenEdit::DoHelp()
{
	g_EditHelp.rows.clear();

	for( unsigned i=0; i<ARRAYLEN(g_EditHelpLines); ++i )
	{
		const EditHelpLine &hl = g_EditHelpLines[i];

		if( !IsMapped(hl.veb[0],m_EditMappingsDeviceInput) )
			continue;

		RString sButtons = GetDeviceButtonsLocalized( hl.veb, m_EditMappingsDeviceInput );
		RString sDescription = THEME->GetString( "EditHelpDescription", hl.szEnglishDescription );
		
		// TODO: Better way of hiding routine only key on non-routine.
		if( hl.veb[0] == EDIT_BUTTON_SWITCH_PLAYERS && m_InputPlayerNumber == PLAYER_INVALID )
		{
			continue;
		}
		
		g_EditHelp.rows.push_back( MenuRowDef( -1, sDescription, false, EditMode_Practice, false, false, 0, sButtons ) );
	}

	EditMiniMenu( &g_EditHelp );
}

// lua start
#include "LuaBinding.h"

/** @brief Allow Lua to have access to ScreenEdit. */ 
class LunaScreenEdit: public Luna<ScreenEdit>
{
public:
	DEFINE_METHOD( GetEditState, GetEditState() )
	LunaScreenEdit()
	{
		ADD_METHOD( GetEditState );
	}
};

LUA_REGISTER_DERIVED_CLASS( ScreenEdit, ScreenWithMenuElements )

/*
 * (c) 2001-2004 Chris Danford
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
