// demopajaDoc.cpp : implementation of the CDemopajaDoc class
//

#pragma warning( disable : 4786 )		// long names generated by STL

#include "stdafx.h"
#include "demopaja.h"
#include <mmsystem.h>
#include <algorithm>
#include "PajaTypes.h"
#include "demopajaDoc.h"
#include "demopajaView.h"
#include "ParamI.h"
#include "ControllerC.h"
#include "LayerC.h"
#include "SceneC.h"
#include "EffectI.h"
#include "GizmoI.h"
#include "SceneItemC.h"
#include "ImportableI.h"
#include "UndoC.h"
#include "FileListC.h"
#include "ChooseImporterDlg.h"
#include "FileIO.h"
#include "DemopajaVersion.h"
#include "MainFrm.h"
#include "DemoPropDlg.h"
#include "DemopropTiming.h"
#include "DemopropLayout.h"
#include "DemopropDevices.h"
#include "DemopropMusic.h"
#include "DemopropComment.h"
#include "DemoInterfaceC.h"
#include "DeviceContextC.h"
#include "DeviceInterfaceI.h"
#include "opengldriver\OpenGLDriver.h"
#include "ProgressWnd.h"
#include "SubSceneC.h"
#include "GraphicsBufferI.h"
#include "RecursionErrorDlg.h"
#include "ColorCommonDialogC.h"
#include "ChooseImporterCommonDialogC.h"
#include "TimesegmentTypeInDlg.h"
#include "AudioFilter.h"
#include "AudioSpectrumC.h"

// type-ins
#include "IntTypeInDlg.h"
#include "IntComboTypeInDlg.h"
#include "FloatTypeInDlg.h"
#include "Vector2TypeInDlg.h"
#include "Vector3TypeInDlg.h"
#include "ColorTypeInDlg.h"
#include "FileTypeInDlg.h"
#include "TextTypeInDlg.h"
#include "TimesegmentTypeInDlg.h"
#include "MarkerTypeInDlg.h"
#include "SingleLinkTypeIn.h"
#include "MultiLinkTypeIn.h"

#include "AVIWriterC.h"
#include "ExportAVIDlg.h"

#include "fmod.h"
#include "fmod_errors.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


using namespace PajaTypes;
using namespace Composition;
using namespace Edit;
using namespace FileIO;
using namespace Import;
using namespace PajaSystem;
using namespace PluginClass;


#define		MUSIC_DATA_FREQ	8000
#define		BAND_DATA_FREQ	60

const uint32	DEMOPAJA_DOC_VERSION_1 = (1 << 16);
const uint32	DEMOPAJA_DOC_VERSION = (1 << 16) | 1;

enum DocChunksE {
	CHUNK_FILE = 0x1000,
	CHUNK_SCENE = 0x2000,
	CHUNK_COMMENT = 0x3000,
	CHUNK_MUSIC = 0x4000,
	CHUNK_DEVICEDRIVER = 0x5000,
	CHUNK_PROPERTIES = 0x6000,
		CHUNK_PROP_SHOWMUSICWAVEFORM = 0x6001,
		CHUNK_PROP_GRID = 0x6002,
		CHUNK_PROP_RULERS = 0x6003,
		CHUNK_PROP_MARKERS = 0x6004,
};


CLIPFORMAT CDemopajaDoc::m_rClipboardFormat = 0;


CDemopajaDoc*
GetDoc()
{
	POSITION		rPos;
	CDocTemplate*	pDocTemp;
	CWinApp*		pApp = AfxGetApp();

	rPos = pApp->GetFirstDocTemplatePosition();
	pDocTemp = pApp->GetNextDocTemplate( rPos );

	rPos = pDocTemp->GetFirstDocPosition();
	return (CDemopajaDoc*)pDocTemp->GetNextDoc( rPos );
}



/////////////////////////////////////////////////////////////////////////////
// CDemopajaDoc

IMPLEMENT_DYNCREATE(CDemopajaDoc, CDocument)

BEGIN_MESSAGE_MAP(CDemopajaDoc, CDocument)
	//{{AFX_MSG_MAP(CDemopajaDoc)
	ON_COMMAND(ID_PLAY_LOOP, OnPlayLoop)
	ON_UPDATE_COMMAND_UI(ID_PLAY_LOOP, OnUpdatePlayLoop)
	ON_COMMAND(ID_PLAY_PLAYMUSIC, OnPlayPlaymusic)
	ON_UPDATE_COMMAND_UI(ID_PLAY_PLAYMUSIC, OnUpdatePlayPlaymusic)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDemopajaDoc construction/destruction

CDemopajaDoc::CDemopajaDoc()
{
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();

	m_bDeferParamUpdateNotify = false;
	m_ui32DeferParamUpdateNotifyMsg = 0;

//	m_i32SceneListItemsHeight = 0;
//	m_i32ItemHeight = 16;
//	m_bListUpdated = false;

	m_pMainScene = (SceneC*)SceneC::create_new();
	m_pMainScene->set_name( "Main Scene" );
	m_pCurrentScene = m_pMainScene;
	m_pFileList = (FileListC*)FileListC::create_new();
	m_pKeySelector = KeySelectorC::create_new();
	m_pDeviceContext = new DeviceContextC;

	m_pTimeContext = new TimeContextC( m_pMainScene->get_beats_per_min(), m_pMainScene->get_qnotes_per_beat(), m_pMainScene->get_edit_accuracy() );

	m_pDemoInterface = new DemoInterfaceC( m_pDeviceContext, m_pTimeContext, m_pFileList, pApp->GetFactory() );
	SetupDemoInterface( m_pDemoInterface );

	m_rDeviceFeedback.set_scene( m_pMainScene );
	m_rDeviceFeedback.set_demo_interface( m_pDemoInterface );

	m_sComment = "Untitled Demo";

	m_bFirstNewDoc = true;

	m_sComment = "Made in Moppi Demopaja";

	m_bDrawGrid = false;
	m_bSnapToGrid = false;
	m_i32GridSize = 10;

	m_bDrawRulers = false;
	m_bShowMarkers = true;

	m_i32EditFocus = FOCUS_VIEW;

	m_rClipboard.m_eType = CLIPBOARD_NONE;
	m_rClipboard.m_pLayer = 0;

	m_eTool = TOOL_ARROW;

	m_pStream = 0;
//	for( uint32 i = 0; i < MUSIC_BAND_COUNT; i++ )
//		m_pBandData[i] = 0;
//	m_i32BandDataLength = 0;
	m_pMusicData = 0;
	m_i32MusicDataLength = 0;
	m_i32MusicDataView = MUSIC_VIEW_MONO;
	m_bShowWaveform = true;
	m_i32Channel = -1;

	memset( &m_rDefaultLogfont, 0, sizeof( m_rDefaultLogfont ) );
	m_rDefaultLogfont.lfHeight			= 12;
	m_rDefaultLogfont.lfWeight			= 1;
	m_rDefaultLogfont.lfCharSet			= ANSI_CHARSET;
	m_rDefaultLogfont.lfOutPrecision	= OUT_TT_PRECIS;
	m_rDefaultLogfont.lfClipPrecision	= CLIP_DEFAULT_PRECIS;
	m_rDefaultLogfont.lfQuality			= PROOF_QUALITY;
	m_rDefaultLogfont.lfPitchAndFamily	= FF_SWISS | VARIABLE_PITCH;
	strcpy( m_rDefaultLogfont.lfFaceName, "Arial" );
	m_rDefaultFont.CreateFontIndirect( &m_rDefaultLogfont );

	m_bPlayLoop = false;
	m_bPlayMusic = true;

	m_bStreamTimeFirstUpdate = true;

	m_pCurrentUndo = 0;

	//
	// Init color swatches
	//
	char	szCurDir[_MAX_PATH];
	::GetCurrentDirectory( _MAX_PATH, szCurDir );
	CString	sICMPath = szCurDir;
	sICMPath += "\\icm\\";
	CString	sCMYKProfile = sICMPath + "tr01_d50.icm";

	if( !m_rColorSwatches.init_color_management( sCMYKProfile ) ) {
		::MessageBox( NULL, "Demopaja is unable to load the color profiles need for swatch conversion.\nSome swatch colors may appear black.", "Error Initialising Color Profiles", MB_OK | MB_ICONWARNING );
	}

	m_rColorSwatches.load_swatches( pApp->GetCurrentDir( CURDIR_COLORSWATCH ) );

	// get project path
	m_bSaveProjectPath = true;
	m_sProjectPath = (const char*)pApp->GetProfileString( "ProjectPath", "Path" );
}

CDemopajaDoc::~CDemopajaDoc()
{
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();

	uint32	i;

	m_pKeySelector->release();

	// delete clipboard
	if( m_rClipboard.m_pLayer )
		m_rClipboard.m_pLayer->release();
	for( i = 0; i < m_rClipboard.m_rEffects.size(); i++ )
		if( m_rClipboard.m_rEffects[i] )
			m_rClipboard.m_rEffects[i]->release();
	for( i = 0; i < m_rClipboard.m_rKeys.size(); i++ )
		if( m_rClipboard.m_rKeys[i] )
			m_rClipboard.m_rKeys[i]->release();

	m_pMainScene->release();
	m_pFileList->release();

	ReleaseMusic();

	if( m_pCurrentUndo )
		delete m_pCurrentUndo;

	delete m_pDeviceContext;
	delete m_pTimeContext;
	delete m_pDemoInterface;

	delete [] m_pMusicData;
//	for( uint32 i = 0; i < MUSIC_BAND_COUNT; i++ )
//		delete [] m_pBandData[i];

	// save project path
	if( m_bSaveProjectPath )
		pApp->WriteProfileString( "ProjectPath", "Path", m_sProjectPath.c_str() );
}


CLIPFORMAT
CDemopajaDoc::GetPrivateClipboardFormat()
{
	if( !m_rClipboardFormat )
		m_rClipboardFormat = (CLIPFORMAT)::RegisterClipboardFormat( _T("Demopaja Internal Format") );
	return m_rClipboardFormat;
}


BOOL CDemopajaDoc::OnNewDocument()
{
	if (!CDocument::OnNewDocument())
		return FALSE;

	DeleteContents();

	// update everything
	if( !m_bFirstNewDoc ) {
		NotifyViews( NOTIFY_RESET_VIEWS );
	}

	if( !m_bFirstNewDoc )
		AskDemoProperties();

	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	FactoryC*			pFactory = pApp->GetFactory();

	// Create graphics device
	if( !m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE ) ) {
		GraphicsDeviceI*	pDev = (GraphicsDeviceI*)pFactory->create( pApp->GetDefaultGraphicsDeviceId() );
		
		if( pDev ) {
			// Find view
			POSITION		rPos = GetFirstViewPosition();
			CDemopajaView*	pView = 0;
			if( rPos )
				pView = (CDemopajaView*)GetNextView( rPos );

			if( pView ) {

				CRect	rRect;
				pView->GetClientRect( &rRect );
				if( rRect.Width() == 0 || rRect.Height() == 0 )
				{
					rRect.left = 0;
					rRect.right = 1;
					rRect.bottom = 1;
					rRect.top = 0;
				}

				if( !pDev->init( AfxGetInstanceHandle(), pView->GetSafeHwnd(), IDW_GRAPHICSDEVICE, GRAPHICSDEVICE_CREATE_EDITOR_CHILD,
					rRect.Width(), rRect.Height(), 0, &m_rDeviceFeedback ) ) {
					::MessageBox( NULL, "Could not initialise graphics device (init).", "Error", MB_OK | MB_ICONERROR );
					pDev->release();
					return FALSE;
				}

				// Set font
				GUIDrawInterfaceI*	pGUI = (GUIDrawInterfaceI*)pDev->query_interface( GRAPHICSDEVICE_GUIDRAW_INTERFACE );
				pGUI->use_font( m_rDefaultFont );

				// Set temp buffer size.
				pDev->set_temp_graphicsbuffer_size( GetLayoutWidth(), GetLayoutHeight() );

				// Calculate correct mapping
				GraphicsViewportI*	pViewport = (GraphicsViewportI*)pDev->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
				BBox2C	rViewport( Vector2C( -20, -20 ), Vector2C( 680, 520 ) );
				pViewport->set_viewport( rViewport );
				pViewport->set_layout( BBox2C( Vector2C( 0, 0 ), Vector2C( (float32)GetLayoutWidth(), (float32)GetLayoutHeight() ) ) );

/*				CRect	rRect;
				pView->GetClientRect( &rRect );
				if( rRect.Width() != 0 && rRect.Height() != 0 )
					pDev->set_size( 0, 0, rRect.Width(), rRect.Height() );*/

				m_pDeviceContext->register_interface( pDev );
			}
			else {
				::MessageBox( NULL, "Could not initialise graphics device (view).", "Error", MB_OK | MB_ICONERROR );
				pDev->release();
				return FALSE;
			}

		}
		else
			::MessageBox( NULL, "Could not initialise graphics device (create).", "Error", MB_OK | MB_ICONERROR );
	}

	// Create audio spectrum.
	AudioSpectrumC*	pAudioSpec = (AudioSpectrumC*)pFactory->create( CLASS_AUDIO_SPECTRUM );
	if( pAudioSpec )
		m_pDeviceContext->register_interface( pAudioSpec );

	// update everything
	if( !m_bFirstNewDoc ) {
		NotifyViews( NOTIFY_RESET_VIEWS );
	}

	m_bFirstNewDoc = false;

	return TRUE;
}


void CDemopajaDoc::AskDemoProperties()
{
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();

	// dialog
	CDemoPropDlg	rDlg;

	// pages
	CDemopropTiming		rTiming;
	CDemopropLayout		rLayout;
	CDemopropDevices	rDevices;
	CDemopropMusic		rMusic;
	CDemopropComment	rComment;

	rDlg.AddPage( rTiming, "Time and Measures" );
	rDlg.AddPage( rLayout, "Layout" );
	rDlg.AddPage( rDevices, "Devices" );
	rDlg.AddPage( rMusic, "Music" );
	rDlg.AddPage( rComment, "Comments" );

/*
			// Find view
			POSITION		rPos = GetFirstViewPosition();
			CDemopajaView*	pView = 0;
			if( rPos )
				pView = (CDemopajaView*)GetNextView( rPos );

			if( pView )
			{
				CRect	rRect;
				pView->GetClientRect( &rRect );

				if( !pGDev->init( AfxGetInstanceHandle(), pView->GetSafeHwnd(), IDW_GRAPHICSDEVICE, GRAPHICSDEVICE_CREATE_EDITOR_CHILD,
					rRect.Width(), rRect.Height(), 0, &m_rDeviceFeedback ) ) {
					pLoad->add_error_message( "Load Doc: Could not initialise graphics device (init)." );
				}

				// Set font
				GUIDrawInterfaceI*	pGUI = (GUIDrawInterfaceI*)pGDev->query_interface( GRAPHICSDEVICE_GUIDRAW_INTERFACE );
				pGUI->use_font( m_rDefaultFont );

				// Set temp buffer size.
				pGDev->set_temp_graphicsbuffer_size( GetLayoutWidth(), GetLayoutHeight() );

				// Calculate correct mapping
				GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGDev->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
				BBox2C	rViewport( Vector2C( -20, -20 ), Vector2C( 680, 520 ) );
				pViewport->set_viewport( rViewport );
				pViewport->set_layout( BBox2C( Vector2C( 0, 0 ), Vector2C( (float32)GetLayoutWidth(), (float32)GetLayoutHeight() ) ) );
			}
*/
	//
	// Devices page
	//
	ClassIdC	OldGraphDeviceID = NULL_CLASSID;
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );
	if( pGraphDevice )
	{
		OldGraphDeviceID = pGraphDevice->get_class_id();
	}
	else
	{
		TRACE( "Warning: Could not find graphics device, using default!\n" );
		OldGraphDeviceID = pApp->GetDefaultGraphicsDeviceId();
	}

	rDevices.m_rGraphicsDeviceClassId = OldGraphDeviceID;

	//
	// Music page
	//
	std::string		sOldMusicFile = m_sMusicFile;
	rMusic.m_sMusicFile = m_sMusicFile.c_str();
	rMusic.m_bShowWave = m_bShowWaveform ? TRUE : FALSE;

	int32	i32Driver, i32Device, i32Mixer, i32OutputRate;
	pApp->GetMusicSystemConfig( i32Driver, i32Device, i32Mixer, i32OutputRate );
	// driver
	if( i32Driver == FSOUND_OUTPUT_DSOUND) rMusic.m_i32Driver = 0;
	if( i32Driver == FSOUND_OUTPUT_WINMM ) rMusic.m_i32Driver = 1;
	if( i32Driver == FSOUND_OUTPUT_A3D ) rMusic.m_i32Driver = 2;
	// device
	rMusic.m_i32Device = i32Device;
	// mixer
	if( i32Mixer == FSOUND_MIXER_QUALITY_AUTODETECT ) rMusic.m_i32Mixer = 0;
	if( i32Mixer == FSOUND_MIXER_BLENDMODE ) rMusic.m_i32Mixer = 1;
	if( i32Mixer == FSOUND_MIXER_MMXP5 ) rMusic.m_i32Mixer = 2;
	if( i32Mixer == FSOUND_MIXER_MMXP6 ) rMusic.m_i32Mixer = 3;
	if( i32Mixer == FSOUND_MIXER_QUALITY_FPU ) rMusic.m_i32Mixer = 4;
	if( i32Mixer == FSOUND_MIXER_QUALITY_MMXP5 ) rMusic.m_i32Mixer = 5;
	if( i32Mixer == FSOUND_MIXER_QUALITY_MMXP6 ) rMusic.m_i32Mixer = 6;
	// rate
	if( i32OutputRate == 8000 ) rMusic.m_i32Rate = 0;
	if( i32OutputRate == 11025 ) rMusic.m_i32Rate = 1;
	if( i32OutputRate == 22050 ) rMusic.m_i32Rate = 2;
	if( i32OutputRate == 44100 ) rMusic.m_i32Rate = 3;
	if( i32OutputRate == 48000 ) rMusic.m_i32Rate = 4;


	//
	// Comment page
	//
	rComment.m_sComment = m_sComment.c_str();

	//
	// Layout page
	//
	rLayout.m_i32Width = m_pMainScene->get_layout_width();
	rLayout.m_i32Height = m_pMainScene->get_layout_height();
	rLayout.m_rBGColor = m_pMainScene->get_layout_color();

	//
	// Timing page
	//
	rTiming.m_i32BPM = m_pMainScene->get_beats_per_min();
	rTiming.m_i32BeatSize = m_pMainScene->get_qnotes_per_beat() - 1;
	rTiming.m_i32MeasureSize = m_pMainScene->get_beats_per_measure() - 1;

	switch( m_pMainScene->get_edit_accuracy() ) {
	case 1:		rTiming.m_i32Accuracy = 0; break;
	case 2:		rTiming.m_i32Accuracy = 1; break;
	case 4:		rTiming.m_i32Accuracy = 2; break;
	case 8:		rTiming.m_i32Accuracy = 3; break;
	case 16:	rTiming.m_i32Accuracy = 4; break;
	case 32:	rTiming.m_i32Accuracy = 5; break;
	default:	rTiming.m_i32Accuracy = 1; break;
	}

	CString	sDuration;
	int32	i32Hours, i32Mins, i32Secs, i32Duration;
	i32Duration = m_pMainScene->get_duration();
	int32	i32TimeScale = m_pMainScene->get_beats_per_min() * m_pMainScene->get_qnotes_per_beat() * 256;
	i32Secs = (i32Duration * 60 / i32TimeScale) % 60;
	i32Mins = (i32Duration / i32TimeScale) % 60;
	i32Hours = i32Duration / 60 / i32TimeScale;
	sDuration.Format( "%d:%02d:%02d", i32Hours, i32Mins, i32Secs );
	rTiming.m_sDuration = sDuration;


	if( rDlg.DoModal() == IDOK ) {

		m_pMainScene->set_layout_width( rLayout.m_i32Width );
		m_pMainScene->set_layout_height( rLayout.m_i32Height );
		m_pMainScene->set_layout_color( rLayout.m_rBGColor );

		int32	i32BPM = rTiming.m_i32BPM;
		int32	i32QNotesPerBeat = rTiming.m_i32BeatSize + 1;
		int32	i32BeatsPerMeasure = rTiming.m_i32MeasureSize + 1;
		int32	i32EditAccuracy = 1;

		switch( rTiming.m_i32Accuracy ) {
		case 0: i32EditAccuracy = 1; break;
		case 1: i32EditAccuracy = 2; break;
		case 2: i32EditAccuracy = 4; break;
		case 3: i32EditAccuracy = 8; break;
		case 4: i32EditAccuracy = 16; break;
		case 5: i32EditAccuracy = 32; break;
		case 6: i32EditAccuracy = 64; break;
		}

		UpdateSceneTimeSettigs( i32BPM, i32QNotesPerBeat, i32BeatsPerMeasure, i32EditAccuracy );

		m_sMusicFile = (const char*)rMusic.m_sMusicFile;
		m_bShowWaveform = rMusic.m_bShowWave ? true : false;

		m_sComment = (const char*)rComment.m_sComment;

		m_pMainScene->set_duration( GetTimeFromString( rTiming.m_sDuration, TIME_FORMAT_HOUR_MIN_SEC ) );

		//
		// set music system
		//

		FSOUND_Close();

		// driver
		if( rMusic.m_i32Driver == 0 ) i32Driver = FSOUND_OUTPUT_DSOUND;
		if( rMusic.m_i32Driver == 1 ) i32Driver = FSOUND_OUTPUT_WINMM;
		if( rMusic.m_i32Driver == 2 ) i32Driver = FSOUND_OUTPUT_A3D;
		// device
		i32Device = rMusic.m_i32Device;
		// mixer
		if( rMusic.m_i32Mixer == 0 ) i32Mixer = FSOUND_MIXER_QUALITY_AUTODETECT;
		if( rMusic.m_i32Mixer == 1 ) i32Mixer = FSOUND_MIXER_BLENDMODE;
		if( rMusic.m_i32Mixer == 2 ) i32Mixer = FSOUND_MIXER_MMXP5;
		if( rMusic.m_i32Mixer == 3 ) i32Mixer = FSOUND_MIXER_MMXP6;
		if( rMusic.m_i32Mixer == 4 ) i32Mixer = FSOUND_MIXER_QUALITY_FPU;
		if( rMusic.m_i32Mixer == 5 ) i32Mixer = FSOUND_MIXER_QUALITY_MMXP5;
		if( rMusic.m_i32Mixer == 6 ) i32Mixer = FSOUND_MIXER_QUALITY_MMXP6;
		// rate
		if( rMusic.m_i32Rate == 0 ) i32OutputRate = 8000;
		if( rMusic.m_i32Rate == 1 ) i32OutputRate = 11025;
		if( rMusic.m_i32Rate == 2 ) i32OutputRate = 22050;
		if( rMusic.m_i32Rate == 3 ) i32OutputRate = 44100;
		if( rMusic.m_i32Rate == 4 ) i32OutputRate = 48000;

		pApp->SetMusicSystemConfig( i32Driver, i32Device, i32Mixer, i32OutputRate );


		//
		// reload the music
		//
		if( m_sMusicFile.empty() ) {
			ReleaseMusic();
		}
		else if( sOldMusicFile.compare( m_sMusicFile ) != 0 )
		{
			if( !LoadMusic( m_sMusicFile.c_str() ) ) {
				::MessageBox( NULL, "The music file specified in the file could not be loaded.", "Error loading music", MB_OK | MB_ICONERROR );
			}
		}

	
		//
		// Change graphics device
		//
		if( rDevices.m_rGraphicsDeviceClassId != OldGraphDeviceID ) {

			FactoryC*		pFactory = pApp->GetFactory();

			ClassDescC*	pDesc = pFactory->get_classdesc( rDevices.m_rGraphicsDeviceClassId );

			if( pDesc ) {
				// Set new
				pApp->SetDefaultGraphicsDeviceName( pDesc->get_name() );
				pApp->SetDefaultGraphicsDeviceId( rDevices.m_rGraphicsDeviceClassId );
				// Restart

				GraphicsDeviceI*	pGDev = (GraphicsDeviceI*)m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );
				if( pGDev )
				{
					// Shut down old device
					pGDev->set_state( DEVICE_STATE_SHUTTINGDOWN );
					InitialiseData( INIT_DEVICE_CHANGED );
					m_pDeviceContext->unregister_interface( pGDev->get_class_id() );
					pGDev = 0;

					// Create new device
					pGDev = (GraphicsDeviceI*)pFactory->create( rDevices.m_rGraphicsDeviceClassId );
					
					if( pGDev )
					{
						m_pDeviceContext->register_interface( pGDev );

						// Find view
						POSITION		rPos = GetFirstViewPosition();
						CDemopajaView*	pView = 0;
						if( rPos )
							pView = (CDemopajaView*)GetNextView( rPos );

						if( pView )
						{
							CRect	rRect;
							pView->GetClientRect( &rRect );

							if( !pGDev->init( AfxGetInstanceHandle(), pView->GetSafeHwnd(), IDW_GRAPHICSDEVICE, GRAPHICSDEVICE_CREATE_EDITOR_CHILD,
								rRect.Width(), rRect.Height(), 0, &m_rDeviceFeedback ) )
							{
								::MessageBox( NULL, "Could not initialise graphics device (init).", "Error", MB_OK | MB_ICONERROR );
							}

							// Set font
							GUIDrawInterfaceI*	pGUI = (GUIDrawInterfaceI*)pGDev->query_interface( GRAPHICSDEVICE_GUIDRAW_INTERFACE );
							pGUI->use_font( m_rDefaultFont );

							// Set temp buffer size.
							pGDev->set_temp_graphicsbuffer_size( GetLayoutWidth(), GetLayoutHeight() );

							// Calculate correct mapping
							GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGDev->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
							BBox2C	rViewport( Vector2C( -20, -20 ), Vector2C( 680, 520 ) );
							pViewport->set_viewport( rViewport );
							pViewport->set_layout( BBox2C( Vector2C( 0, 0 ), Vector2C( (float32)GetLayoutWidth(), (float32)GetLayoutHeight() ) ) );
						}

/*
						// Find view
						POSITION		rPos = GetFirstViewPosition();
						CDemopajaView*	pView = 0;
						if( rPos )
							pView = (CDemopajaView*)GetNextView( rPos );

						if( pView )
						{
							CRect	rRect;
							pView->GetClientRect( &rRect );

							if( !pDev->init( AfxGetInstanceHandle(), pView->GetSafeHwnd(), IDW_GRAPHICSDEVICE, GRAPHICSDEVICE_CREATE_CHILD,
								rRect.Width(), rRect.Height(), 0, &m_rDeviceFeedback ) ) {
								::MessageBox( NULL, "Could not initialise graphics device (init).", "Error", MB_OK | MB_ICONERROR );
							}

							// Set font
							GUIDrawInterfaceI*	pGUI = (GUIDrawInterfaceI*)pDev->query_interface( GRAPHICSDEVICE_GUIDRAW_INTERFACE );
							pGUI->use_font( m_rDefaultFont );

							// Set temp buffer size.
							pDev->set_temp_graphicsbuffer_size( GetLayoutWidth(), GetLayoutHeight() );

							// Calculate correct mapping
							GraphicsViewportI*	pViewport = (GraphicsViewportI*)pDev->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
							BBox2C	rViewport( Vector2C( -20, -20 ), Vector2C( 680, 520 ) );
							pViewport->set_viewport( rViewport );
							pViewport->set_layout( BBox2C( Vector2C( 0, 0 ), Vector2C( (float32)GetLayoutWidth(), (float32)GetLayoutHeight() ) ) );

							InitialiseData( INIT_DEVICE_VALIDATE );
						}
						else {
							::MessageBox( NULL, "Could not initialise graphics device (view).", "Error", MB_OK | MB_ICONERROR );
						}
*/
					}
					else
						::MessageBox( NULL, "Could not initialise graphics device (create).", "Error", MB_OK | MB_ICONERROR );
				}

			}
			else {
				::MessageBox( NULL, "Could not found selected new graphics device driver.", "Error", MB_OK | MB_ICONERROR );
			}
		}
		else {
//			TRACE_LOG( "No need to change device\n" );
		}
	}
	else {
		// restore settings
		pApp->SetMusicSystemConfig( i32Driver, i32Device, i32Mixer, i32OutputRate );

		// driver
		if( i32Driver == FSOUND_OUTPUT_DSOUND) rMusic.m_i32Driver = 0;
		if( i32Driver == FSOUND_OUTPUT_WINMM ) rMusic.m_i32Driver = 1;
		if( i32Driver == FSOUND_OUTPUT_A3D ) rMusic.m_i32Driver = 2;
		// device
		rMusic.m_i32Device = i32Device;
		// mixer
		if( i32Mixer == FSOUND_MIXER_QUALITY_AUTODETECT ) rMusic.m_i32Mixer = 0;
		if( i32Mixer == FSOUND_MIXER_BLENDMODE ) rMusic.m_i32Mixer = 1;
		if( i32Mixer == FSOUND_MIXER_MMXP5 ) rMusic.m_i32Mixer = 2;
		if( i32Mixer == FSOUND_MIXER_MMXP6 ) rMusic.m_i32Mixer = 3;
		if( i32Mixer == FSOUND_MIXER_QUALITY_FPU ) rMusic.m_i32Mixer = 4;
		if( i32Mixer == FSOUND_MIXER_QUALITY_MMXP5 ) rMusic.m_i32Mixer = 5;
		if( i32Mixer == FSOUND_MIXER_QUALITY_MMXP6 ) rMusic.m_i32Mixer = 6;
		// rate
		if( i32OutputRate == 8000 ) rMusic.m_i32Rate = 0;
		if( i32OutputRate == 11025 ) rMusic.m_i32Rate = 1;
		if( i32OutputRate == 22050 ) rMusic.m_i32Rate = 2;
		if( i32OutputRate == 44100 ) rMusic.m_i32Rate = 3;
		if( i32OutputRate == 48000 ) rMusic.m_i32Rate = 4;
	}

	// Update the temp buffer size.
	GraphicsDeviceI*	pDev = (GraphicsDeviceI*)m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );
	if( pDev )
		pDev->set_temp_graphicsbuffer_size( GetLayoutWidth(), GetLayoutHeight() );

	NotifyViews( NOTIFY_RESET_VIEWS );
}


void CDemopajaDoc::UpdateSceneTimeSettigs( int32 i32BPM, int32 i32QNotesPerBeat, int32 i32BeatsPerMeasure, int32 i32EditAccuracy )
{
	m_pMainScene->set_beats_per_min( i32BPM );
	m_pMainScene->set_qnotes_per_beat( i32QNotesPerBeat );
	m_pMainScene->set_beats_per_measure( i32BeatsPerMeasure );
	m_pMainScene->set_edit_accuracy( i32EditAccuracy );

	for( uint32 i = 0; i < m_pFileList->get_file_count(); i++ ) {
		ImportableI*	pImp = m_pFileList->get_file( i )->get_importable();
		if( pImp && pImp->get_class_id() == CLASS_SUBSCENE_IMPORTABLE ) {
			SceneImportC*	pSceneImp = (SceneImportC*)pImp;
			SceneC*	pScene = pSceneImp->get_scene();
			pScene->set_beats_per_min( i32BPM );
			pScene->set_qnotes_per_beat( i32QNotesPerBeat );
			pScene->set_beats_per_measure( i32BeatsPerMeasure );
			pScene->set_edit_accuracy( i32EditAccuracy );
		}
	}

	m_pTimeContext->set_time_settings( i32BPM, i32QNotesPerBeat, i32EditAccuracy );
}


/////////////////////////////////////////////////////////////////////////////
// CDemopajaDoc diagnostics

#ifdef _DEBUG
void CDemopajaDoc::AssertValid() const
{
	CDocument::AssertValid();
}

void CDemopajaDoc::Dump(CDumpContext& dc) const
{
	CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CDemopajaDoc commands





UndoManagerC*
CDemopajaDoc::GetUndoManager()
{
	return &m_rUndoManager;
}

FileListC*
CDemopajaDoc::GetFileList()
{
	return m_pFileList;
}

DemoInterfaceC*
CDemopajaDoc::GetDemoInterface()
{
	return m_pDemoInterface;
}


void
CDemopajaDoc::UndoNotify()
{
	// rebuild the list
	UpdateFileReferences();
	UpdateSceneViewList();
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}


int32
CDemopajaDoc::GetFrameSizeInTicks()
{
	if( GetCurrentScene() )
		return 256 / GetCurrentScene()->get_edit_accuracy();
	return 0;
}

int32
CDemopajaDoc::GetEditAccuracy()
{
	if( GetCurrentScene() )
		return GetCurrentScene()->get_edit_accuracy();
	return 0;
}

int32
CDemopajaDoc::GetBeatsPerMin()
{
	if( GetCurrentScene() )
		return GetCurrentScene()->get_beats_per_min();
	return 0;
}

int32
CDemopajaDoc::GetQNotesPerBeat()
{
	if( GetCurrentScene() )
		return GetCurrentScene()->get_qnotes_per_beat();
	return 0;
}

int32
CDemopajaDoc::GetBeatsPerMeasure()
{
	if( GetCurrentScene() )
		return GetCurrentScene()->get_beats_per_measure();
	return 0;
}


int32
CDemopajaDoc::GetLastFrameTime()
{
	if( GetCurrentScene() )
		return GetCurrentScene()->get_duration();
	return 0;
}

int32
CDemopajaDoc::TimeToFrame( int32 i32Time )
{
	return i32Time / GetFrameSizeInTicks();
}

int32
CDemopajaDoc::FrameToTime( int32 i32Frame )
{
	return i32Frame * GetFrameSizeInTicks();
}



void WriteTGA( const char* szName, int iWidth, int iHeight, unsigned char* pData )
{
	FILE*			pStream;
	unsigned char	ucHeader[18];
	int				i, j;

	memset( ucHeader, 0, 18 );

	ucHeader[2] = 2;
	ucHeader[16] = 0x18;
	ucHeader[17] = 0x20;

	ucHeader[12] = iWidth;
	ucHeader[13] = iWidth >> 8;
	ucHeader[14] = iHeight;
	ucHeader[15] = iHeight >> 8;

	if( (pStream = fopen( szName, "wb" )) == 0 ) {
		return;
	}

	fwrite( ucHeader, 1, 18, pStream );

	for( i = 0; i < iHeight; i++ ) {
		for( j = 0; j < iWidth; j++ ) {
			fputc( pData[(j + i * iWidth) * 4], pStream );
			fputc( pData[(j + i * iWidth) * 4 + 1], pStream );
			fputc( pData[(j + i * iWidth) * 4 + 2], pStream );
		}
	}
	
	fclose( pStream );
}


/*
//	TRACE( "load music\n" );

	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	if( !pApp->MusicSystemPresent() )
	{
//		TRACE( "no music sys\n" );
		return true;
	}

	// is here something loaded already?
	if( m_pStream )
		FSOUND_Stream_Close( m_pStream );


	char szFname[_MAX_FNAME];

	m_pStream = FSOUND_Stream_OpenFile( szFilename, FSOUND_NORMAL | FSOUND_LOOP_NORMAL | FSOUND_MPEGACCURATE, 0 );
	if( !m_pStream ) {

		// try to laod it from current dir
		char szExt[_MAX_EXT];
		_splitpath( szFilename, 0, 0, szFname, szExt );

		strcat( szFname, szExt );

		m_pStream = FSOUND_Stream_OpenFile( szFname, FSOUND_NORMAL | FSOUND_LOOP_NORMAL | FSOUND_MPEGACCURATE, 0 );
		
		if( !m_pStream )
			return false;
	}
	else {
		strcpy( szFname, szFilename );
	}

	pApp->DoWaitCursor( 1 );

	// delete old data
	delete [] m_pMusicData;
	m_pMusicData = 0;
	m_i32MusicDataLength = 0;


	// Create waveform display data
	FSOUND_SAMPLE*	pSample = FSOUND_Sample_Load( FSOUND_UNMANAGED, szFname, FSOUND_HW2D | FSOUND_NORMAL, 0 );

	if( pSample ) {

//		TRACE( "Sample:\n" );

		int32	i32MusicFreq;
		FSOUND_Sample_GetDefaults( pSample, &i32MusicFreq, 0, 0, 0 );
//		TRACE( " freq: %d\n", i32MusicFreq );

		uint32	ui32Mode = FSOUND_Sample_GetMode( pSample );
//		TRACE( " mode: " );
//		IF_PRINT( ui32Mode, FSOUND_8BITS );
//		IF_PRINT( ui32Mode, FSOUND_16BITS );
//		IF_PRINT( ui32Mode, FSOUND_MONO );
//		IF_PRINT( ui32Mode, FSOUND_STEREO );
//		TRACE( "\n" );

		uint32	ui32Samples = FSOUND_Sample_GetLength( pSample );

		int32	i32LockSize = (int32)ui32Samples;
		int8*	pData;
		int8*	pData2;
		uint32	ui32Length, ui32Length2;

		if( ui32Mode & FSOUND_16BITS )
			i32LockSize *= 2;
		if( ui32Mode & FSOUND_STEREO )
			i32LockSize *= 2;

		if( FSOUND_Sample_Lock( pSample, 0, i32LockSize, (void**)&pData, (void**)&pData2, &ui32Length, &ui32Length2 ) ) {

			if( pData && ui32Length ) {

				// downsample to 8kHz and 8-bit
				m_i32MusicDataLength = (int32)((float64)ui32Samples * 8000.0 / (float64)i32MusicFreq) * 2;
				if( ui32Mode & FSOUND_STEREO )
					m_i32MusicDataLength *= 2;
				m_pMusicData = new uint8[m_i32MusicDataLength];

				float32	f32Delta = (float32)i32MusicFreq / 8000.0f;
				float32	f32Index = 0;
				float32	f32MaxSize = (float32)ui32Samples;
				uint32	i;
				uint8*	pDest = m_pMusicData;
				int32	i32DstCount = 0;
				int32	i32Sample;
				uint32	ui32PrevIndex = 0;
				int32	i32MaxL, i32MinL;
				int32	i32MaxR, i32MinR;
				uint32	j;

				if( ui32Mode & FSOUND_8BITS ) {
					// 8 bit
					int8*	pSrc = (int8*)pData;

					if( ui32Mode & FSOUND_STEREO ) {
						// stereo

						ui32PrevIndex = 0;

						for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32MusicDataLength; f32Index += f32Delta ) {
							i = (uint32)f32Index;

							i32MaxL = 0; //128;
							i32MinL = 255;//128;
							i32MaxR = 0;//128;
							i32MinR = 255;//128;
							for( j = ui32PrevIndex; j < i; j++ ) {
								i32Sample = (int32)pSrc[j * 2] + 0x80;
								i32MinL = __min( i32MinL, i32Sample );
								i32MaxL = __max( i32MaxL, i32Sample );
								i32Sample = (int32)pSrc[j * 2 + 1] + 0x80;
								i32MinR = __min( i32MinR, i32Sample );
								i32MaxR = __max( i32MaxR, i32Sample );
							}
							ui32PrevIndex = i;

							pDest[i32DstCount] = (uint8)i32MinL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MinR;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxR;
							i32DstCount++;
						}
						m_bMusicDataStereo = true;
					}
					else {
						// mono
						ui32PrevIndex = 0;
						for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32MusicDataLength; f32Index += f32Delta ) {
							i = (uint32)f32Index;
							i32MaxL = 0; //128;
							i32MinL = 255; //128;
							for( j = ui32PrevIndex; j < i; j++ ) {
								i32Sample = (int32)pSrc[j] + 0x80;
								i32MinL = __min( i32MinL, i32Sample );
								i32MaxL = __max( i32MaxL, i32Sample );
							}
							ui32PrevIndex = i;

							pDest[i32DstCount] = (uint8)i32MinL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxL;
							i32DstCount++;
						}
						m_bMusicDataStereo = false;
					}
				}
				else {
					// 16bit
					int16*	pSrc = (int16*)pData;

					if( ui32Mode & FSOUND_STEREO ) {
						// stereo
						ui32PrevIndex = 0;
						for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32MusicDataLength; f32Index += f32Delta ) {
							i = (uint32)f32Index;

							i32MaxL = 0; //128;
							i32MinL = 255; //128;
							i32MaxR = 0; //128;
							i32MinR = 255; //128;
							for( j = ui32PrevIndex; j < i; j++ ) {
								i32Sample = (((int32)pSrc[j * 2] + 0x8000) >> 8);
								i32MinL = __min( i32MinL, i32Sample );
								i32MaxL = __max( i32MaxL, i32Sample );
								i32Sample = (((int32)pSrc[j * 2 + 1] + 0x8000) >> 8);
								i32MinR = __min( i32MinR, i32Sample );
								i32MaxR = __max( i32MaxR, i32Sample );
							}
							ui32PrevIndex = i;

							pDest[i32DstCount] = (uint8)i32MinL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MinR;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxR;
							i32DstCount++;
						}
						m_bMusicDataStereo = true;
					}
					else {
						// mono
						ui32PrevIndex = 0;
						for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32MusicDataLength; f32Index += f32Delta ) {
							i = (uint32)f32Index;

							i32MaxL = 0; //128;
							i32MinL = 255; //128;
							for( j = ui32PrevIndex; j < i; j++ ) {
								i32Sample = (((int32)pSrc[j] + 0x8000) >> 8);
								i32MinL = __min( i32MinL, i32Sample );
								i32MaxL = __max( i32MaxL, i32Sample );
							}
							ui32PrevIndex = i;

							pDest[i32DstCount] = (uint8)i32MinL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxL;
							i32DstCount++;
						}
						m_bMusicDataStereo = false;
					}
				}
			}

			FSOUND_Sample_Unlock( pSample, (void*)pData, (void*)pData2, ui32Length, ui32Length2 );

			if( m_bMusicDataStereo )
				m_i32MusicDataView = MUSIC_VIEW_STEREO;
			else
				m_i32MusicDataView = MUSIC_VIEW_MONO;
		}
		else {
			TRACE( "Could not lock sample\n" );
		}

		FSOUND_Sample_Free( pSample );
	}
	else {
		TRACE( "Failed to load sample: " );
		TRACE( "%s\n", FMOD_ErrorString( FSOUND_GetError() ) );
	}

	pApp->DoWaitCursor( -1 );

	return true;
}
*/

bool
CDemopajaDoc::ExportAVI( const char* szName )
{

	CExportAVIDlg	rDlg;

	CProgressWnd	rProgress( AfxGetMainWnd(), "Rendering", TRUE );
	rProgress.Hide();


	//
	// Check the music
	//

	uint8*		pAudioData = 0;
	uint8*		pAudioData2 = 0;
	uint32	ui32AudioLength, ui32AudioLength2;
	int32		i32AudioLockSize = 0;
	int32		i32AudioChannels = 0;
	int32		i32AudioFreq = 0;
	FSOUND_SAMPLE*	pSample = 0;

	// Open music file.
	if( !m_sMusicFile.empty() )
	{
		// Create waveform display data
//		pSample = FSOUND_Stream_GetSample( m_pStream );

		char szFname[_MAX_FNAME];

		// Create waveform display data
		pSample = FSOUND_Sample_Load( FSOUND_UNMANAGED, m_sMusicFile.c_str(), FSOUND_HW2D | FSOUND_NORMAL, 0, 0 );
		if( !pSample ) {

			// try to laod it from current dir
			char szExt[_MAX_EXT];
			_splitpath( m_sMusicFile.c_str(), 0, 0, szFname, szExt );

			strcat( szFname, szExt );

			pSample = FSOUND_Sample_Load( FSOUND_UNMANAGED, szFname, FSOUND_HW2D | FSOUND_NORMAL, 0, 0 );
			
			if( !pSample )
				return false;
		}

		if( pSample ) {

			TRACE( "Sample:\n" );

			FSOUND_Sample_GetDefaults( pSample, &i32AudioFreq, 0, 0, 0 );
			TRACE( " freq: %d\n", i32AudioFreq );

			uint32	ui32Mode = FSOUND_Sample_GetMode( pSample );
			TRACE( " mode: " );
			if( ui32Mode & FSOUND_8BITS )
				TRACE( " - 8 bits\n" );
			if( ui32Mode & FSOUND_16BITS )
				TRACE( " - 16 bits\n" );

			if( ui32Mode & FSOUND_MONO )
			{
				i32AudioChannels = 1;
				TRACE( " - mono\n" );
			}
			if( ui32Mode & FSOUND_STEREO )
			{
				i32AudioChannels = 2;
				TRACE( " - stereo\n" );
			}

			uint32	ui32Samples = FSOUND_Sample_GetLength( pSample );

			i32AudioLockSize = (int32)ui32Samples;

			if( ui32Mode & FSOUND_16BITS )
				i32AudioLockSize *= 2;
			if( ui32Mode & FSOUND_STEREO )
				i32AudioLockSize *= 2;

			if( FSOUND_Sample_Lock( pSample, 0, i32AudioLockSize, (void**)&pAudioData, (void**)&pAudioData2, &ui32AudioLength, &ui32AudioLength2 ) ) {

				if( pAudioData && ui32AudioLength ) {

					TRACE( "sample lock success %d / %d bytes\n",  ui32AudioLength, i32AudioLockSize );

				}

			}

		}
	}


	COMPVARS					CompVars;
	memset( &CompVars,0, sizeof( COMPVARS ) );

	rDlg.m_pCompVars = &CompVars;


	rDlg.m_iFPS = 30;
	rDlg.m_iWidth = GetLayoutWidth();
	rDlg.m_iHeight = GetLayoutHeight();
	rDlg.m_i32BeatsPerMin = GetBeatsPerMin();
	rDlg.m_i32EditAccuracy = GetEditAccuracy();
	rDlg.m_i32QNotesPerBeat = GetQNotesPerBeat();
	rDlg.m_i32StartTime = 0;
	rDlg.m_i32EndTime = GetLastFrameTime();



	WAVEFORMATEX*	pAudioOutputFormat;
	HACMDRIVERID	hAudioDriverId = 0;

	if( i32AudioChannels > 0 )
	{
		WAVEFORMATEX	SrcAudioFormat;
		memset( &SrcAudioFormat, 0, sizeof( WAVEFORMATEX ) );
		SrcAudioFormat.wFormatTag = WAVE_FORMAT_PCM;
		SrcAudioFormat.nChannels = i32AudioChannels;
		SrcAudioFormat.nSamplesPerSec = i32AudioFreq;
		SrcAudioFormat.nAvgBytesPerSec = i32AudioFreq * 2 * i32AudioChannels;
		SrcAudioFormat.nBlockAlign = 2 * i32AudioChannels;
		SrcAudioFormat.wBitsPerSample = 16;
		SrcAudioFormat.cbSize = 0;

		rDlg.m_pWaveFormat = &SrcAudioFormat;
	}

	if( rDlg.DoModal() != IDOK )
		return false;

	pAudioOutputFormat = rDlg.m_pWaveFormatOut;
	hAudioDriverId = rDlg.m_hAudioDriverId;

	GraphicsDeviceI*	pDev = (GraphicsDeviceI*)m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );
	GraphicsBufferI*	pOldGBuf = 0;
	GraphicsBufferI*	pGBuf = pDev->create_graphicsbuffer();

	if( !pGBuf ) {
		AfxMessageBox( "Unable to create buffer for rendering." );
		return false;
	}

	if( !pGBuf->init( GRAPHICSBUFFER_INIT_OFFSCREEN, rDlg.m_iWidth, rDlg.m_iHeight ) ) {
		AfxMessageBox( "Unable to initialize buffer for rendering." );
		pGBuf->release();
		return false;
	}

	// Calculate correct mapping
	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGBuf->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	BBox2C	rLayout( Vector2C( 0, 0 ), Vector2C( (float32)GetLayoutWidth(), (float32)GetLayoutHeight() ) );
	pViewport->set_viewport( rLayout );
	pViewport->set_layout( rLayout );


	AVIWriterC	rWriter;

	if( !rWriter.open( szName, pViewport->get_width(), pViewport->get_height(), rDlg.m_iFPS, &CompVars, i32AudioChannels, i32AudioFreq, hAudioDriverId, pAudioOutputFormat ) ) {
		pDev->set_render_target( pOldGBuf );
		pGBuf->release();
		return false;
	}

	uint32	ui32BufferSize = pViewport->get_width() * pViewport->get_height() * 3;
	uint8*	pBuffer = new uint8[ui32BufferSize];

	int32			i32FrameSizeInTicks = GetFrameSizeInTicks();
	int32			i32RenderTime = rDlg.m_i32StartTime;
	int32			i32MaxRenderTime = rDlg.m_i32EndTime;
	float64			f64TimeScale = (float64)GetBeatsPerMin() * (float64)GetQNotesPerBeat() * 256.0 / 60.0;

	float64			f64Time = 0;
	float64			f64DeltaTime = 1000.0f / (float64)rDlg.m_iFPS;


	rProgress.Show();
	rProgress.GoModal();

	float64		f64MaxTime = (float64)i32MaxRenderTime / f64TimeScale * 1000.0;
	rProgress.SetRange( 0, (int32)(f64MaxTime / f64DeltaTime) );
	rProgress.SetText( "Rendering..." );

	SceneC*	pScene = GetCurrentScene();


	int32		i32AudioPos = 0;


	while( 1 ) {

		i32RenderTime = (int32)(f64Time / 1000.0 * f64TimeScale);
		if( i32RenderTime > i32MaxRenderTime )
			break;
		f64Time += f64DeltaTime;

		pOldGBuf = pDev->set_render_target( pGBuf );

		pDev->begin_draw();

		// clear device
		pDev->clear_device( GRAPHICSDEVICE_ALLBUFFERS, pScene->get_layout_color() );


		//
		// draw effects
		//

		pDev->begin_effects();

		for( int32 i = pScene->get_layer_count() - 1; i >= 0 ; i-- ) {

			LayerC*	pLayer = pScene->get_layer( i );

			if( !pLayer )
				continue;

			if( !pLayer->get_timesegment()->is_visible( i32RenderTime ) )
				continue;

			int32	i32TimeOffset;
			i32TimeOffset = pLayer->get_timesegment()->get_segment_start();

			for( int32 j = pLayer->get_effect_count() - 1; j >= 0; j-- ) {
				EffectI*	pEffect = pLayer->get_effect( j );

				if( !pEffect )
					continue;

				if( pEffect->get_timesegment()->is_visible( i32RenderTime - i32TimeOffset ) ) {
					pEffect->eval_state( i32RenderTime - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start()) );
				}
			}
		}

		pDev->end_effects();

		pDev->end_draw();

		// flush device (swap buffers)
		pDev->flush();



		pGBuf->read_pixels( GRAPHICSBUFFER_GET_RGB, pBuffer );

		pDev->set_render_target( pOldGBuf );

		// change RGB to BGR
		for( i = 0; i < ui32BufferSize; i += 3 ) {
			uint8	ui8Tmp = pBuffer[i];
			pBuffer[i] = pBuffer[i + 2];
			pBuffer[i + 2] = ui8Tmp;
		}


		uint8*	pAudioDataSrc = 0;
		int32		i32AudioWriteSize = 0;
		if( pAudioData )
		{
			if( i32AudioPos < i32AudioLockSize )
			{
				pAudioDataSrc = &pAudioData[i32AudioPos];
				i32AudioWriteSize = rWriter.get_audio_bytes_per_frame();

				if( (i32AudioPos + i32AudioWriteSize) > i32AudioLockSize )
					i32AudioWriteSize = i32AudioLockSize - i32AudioPos;

				i32AudioPos += rWriter.get_audio_bytes_per_frame();
			}
		}

		if( !rWriter.write_frame( pBuffer, pAudioDataSrc, i32AudioWriteSize ) )
			break;

		rProgress.StepIt();
		rProgress.PeekAndPump();

		IncFrameID();

		if( rProgress.Cancelled() )
			break;
	}



	if( pSample )
	{
		if( pAudioData )
			FSOUND_Sample_Unlock( pSample, (void*)pAudioData, (void*)pAudioData2, ui32AudioLength, ui32AudioLength2 );
		FSOUND_Sample_Free( pSample );
	}

	if( pAudioOutputFormat )
		delete [] (uint8*)pAudioOutputFormat;

	delete [] pBuffer;

	if( CompVars.hic )
		ICClose( CompVars.hic );

	rWriter.close();


	pGBuf->release();

	return true;
}

bool
CDemopajaDoc::ExportScript( const char* szName )
{
/*	FILE*	pStream;

	pStream = fopen( szName, "w" );
	if( !pStream )
		return false;

	fprintf( pStream, "// Moppi Demopaja ASCII Script Export\n" );
	fprintf( pStream, "\n" );

	fprintf( pStream, "MUSIC \"%s\"\n", m_sMusicFile.c_str() );

	fprintf( pStream, "TIMING %d %d %d %d\n", GetCurrentScene()->get_beats_per_min(), m_pScene->get_beats_per_measure(),
		m_pScene->get_qnotes_per_beat(), m_pScene->get_edit_accuracy() );

	fprintf( pStream, "DURATION %d\n", m_pScene->get_duration() );

	fprintf( pStream, "LAYOUT_SIZE %d %d\n", m_pScene->get_layout_width(), m_pScene->get_layout_height() );


	// give files IDs (files can also refer to files)

	fprintf( pStream, "\n" );
	fprintf( pStream, "FILE_COUNT %d\n", m_pFileList->get_file_count() );

	for( uint32 i = 0; i < m_pFileList->get_file_count(); i++ ) {
		// set file id
		FileHandleC*	pHandle = m_pFileList->get_file( i );
		pHandle->set_id( i );

		ImportableI*	pImp = pHandle->get_importable();
		if( pImp ) {
			fprintf( pStream, "FILE %d \"%s\" CLASS \"%s\" 0x%x 0x%x\n", i, pImp->get_filename(), pImp->get_class_name(),
				pImp->get_class_id().get_class_a(), pImp->get_class_id().get_class_b() );
		}
		else {
			fprintf( pStream, "FILE %d \"\" CLASS 0x0 0x0\n", i );
		}
	}

	fprintf( pStream, "\n" );
	fprintf( pStream, "LAYER_COUNT %d\n", m_pScene->get_layer_count() );

	for( i = 0; i < m_pScene->get_layer_count(); i++ ) {
		LayerC*	pLayer = m_pScene->get_layer( i );
		if( !pLayer )
			continue;

		fprintf( pStream, "LAYER {\n" );
		fprintf( pStream, "\tNAME \"%s\"\n", pLayer->get_name() );

		TimeSegmentC*	pSeg = pLayer->get_timesegment();
		if( pSeg ) {
			fprintf( pStream, "\tTIMESEGMENT {\n" );
			fprintf( pStream, "\t\tORIGO %d\n", pSeg->get_segment_start() );
			fprintf( pStream, "\t\tKEY_COUNT %d\n", pSeg->get_key_count() );
			for( uint32 j = 0; j < pSeg->get_key_count(); j++ )
				fprintf( pStream, "\t\tKEY %d\n", pSeg->get_key_time( j ) );
			fprintf( pStream, "\t}\n" );
		}

		fprintf( pStream, "\tEFFECT_COUNT %d\n", pLayer->get_effect_count() );

		for( uint32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pEffect = pLayer->get_effect( j );
			if( !pEffect )
				continue;

			fprintf( pStream, "\tEFFECT CLASS \"%s\" 0x%x 0x%x {\n", pEffect->get_class_name(),
				pEffect->get_class_id().get_class_a(), pEffect->get_class_id().get_class_b() );
			fprintf( pStream, "\t\tNAME \"%s\"\n", pEffect->get_name() );

			TimeSegmentC*	pSeg = pEffect->get_timesegment();
			if( pSeg ) {
				fprintf( pStream, "\t\tTIMESEGMENT {\n" );
				fprintf( pStream, "\t\t\tORIGO %d\n", pSeg->get_segment_start() );
				fprintf( pStream, "\t\t\tKEY_COUNT %d\n", pSeg->get_key_count() );
				for( uint32 j = 0; j < pSeg->get_key_count(); j++ )
					fprintf( pStream, "\t\t\tKEY %d\n", pSeg->get_key_time( j ) );
				fprintf( pStream, "\t\t}\n" );
			}

			fprintf( pStream, "\t\tGIZMO_COUNT %d\n", pEffect->get_gizmo_count() );

			for( uint32 k = 0; k < pEffect->get_gizmo_count(); k++ ) {
				GizmoI*	pGizmo = pEffect->get_gizmo( k );
				if( !pGizmo )
					continue;

				fprintf( pStream, "\t\tGIZMO {\n" );
				fprintf( pStream, "\t\t\tNAME \"%s\"\n", pGizmo->get_name() );

				fprintf( pStream, "\t\t\tPARAMETER_COUNT %d\n", pGizmo->get_parameter_count() );

				for( uint32 p = 0; p < pGizmo->get_parameter_count(); p++ ) {
					ParamI*	pParam = pGizmo->get_parameter( p );
					if( !pParam )
						continue;

					float32	f32Vals[KEY_MAXCHANNELS];

					if( pParam->get_type() == PARAM_TYPE_FILE ) {
						ParamFileC*		pFileParam = (ParamFileC*)pParam;
						FileHandleC*	pHandle = pFileParam->get_file();

						fprintf( pStream, "\t\t\tPARAMETER FILE {\n" );
						fprintf( pStream, "\t\t\t\tNAME \"%s\"\n", pParam->get_name() );
						fprintf( pStream, "\t\t\t\tFILE_REF %d\n", pHandle ? pHandle->get_id() : -1 );
						fprintf( pStream, "\t\t\t}\n" );
					}
					else if( pParam->get_type() == PARAM_TYPE_INT ) {
						ParamIntC*		pIntParam = (ParamIntC*)pParam;
						int32			i32Val;

						pIntParam->get_val( 0, i32Val );

						fprintf( pStream, "\t\t\tPARAMETER INTEGER {\n" );
						fprintf( pStream, "\t\t\t\tNAME \"%s\"\n", pParam->get_name() );
						fprintf( pStream, "\t\t\t\tVALUE %d\n", i32Val );

						ControllerC*	pCont = pIntParam->get_controller();
						if( pCont && pCont->get_key_count() ) {
							fprintf( pStream, "\t\t\t\tCONTROLLER {\n", pCont->get_key_count() );
							fprintf( pStream, "\t\t\t\t\tOUT_OF_RANGE 0x%x 0x%x\n", pCont->get_start_ort(), pCont->get_end_ort() );
							fprintf( pStream, "\t\t\t\t\tKEY_COUNT %d\n", pCont->get_key_count() );

							for( uint32 r = 0; r < pCont->get_key_count(); r++ ) {
								KeyC*	pKey = pCont->get_key( r );
								pKey->get_value( f32Vals );

								if( pKey->get_flags() & KEY_HOLD ) {
									fprintf( pStream, "\t\t\t\t\tKEY HOLD %d %d %d\n", r, pKey->get_time(), (int32)f32Vals[0] );
								}
								else if( pKey->get_flags() & KEY_SMOOTH ) {
									fprintf( pStream, "\t\t\t\t\tKEY SMOOTH %d %d %d %f %f %f %f %f\n", r, pKey->get_time(), (int32)f32Vals[0],
										pKey->get_tens(), pKey->get_cont(), pKey->get_bias(), pKey->get_ease_in(), pKey->get_ease_out() );
								}
								else {
									fprintf( pStream, "\t\t\t\t\tKEY LINEAR %d %d %d %f %f\n", r, pKey->get_time(), (int32)f32Vals[0],
										pKey->get_ease_in(), pKey->get_ease_out() );
								}
							}
							fprintf( pStream, "\t\t\t\t}\n" );
						}
						
						fprintf( pStream, "\t\t\t}\n" );
					}
					else if( pParam->get_type() == PARAM_TYPE_FLOAT ) {
						ParamFloatC*		pFloatParam = (ParamFloatC*)pParam;
						float32				f32Val;

						pFloatParam->get_val( 0, f32Val );

						fprintf( pStream, "\t\t\tPARAMETER FLOAT {\n" );
						fprintf( pStream, "\t\t\t\tNAME \"%s\"\n", pParam->get_name() );
						fprintf( pStream, "\t\t\t\tVALUE %f\n", f32Val );

						ControllerC*	pCont = pFloatParam->get_controller();
						if( pCont && pCont->get_key_count() ) {
							fprintf( pStream, "\t\t\t\tCONTROLLER {\n", pCont->get_key_count() );
							fprintf( pStream, "\t\t\t\t\tOUT_OF_RANGE 0x%x 0x%x\n", pCont->get_start_ort(), pCont->get_end_ort() );
							fprintf( pStream, "\t\t\t\t\tKEY_COUNT %d\n", pCont->get_key_count() );

							for( uint32 r = 0; r < pCont->get_key_count(); r++ ) {
								KeyC*	pKey = pCont->get_key( r );
								pKey->get_value( f32Vals );

								if( pKey->get_flags() & KEY_HOLD ) {
									fprintf( pStream, "\t\t\t\t\tKEY HOLD %d %d %f\n", r, pKey->get_time(), f32Vals[0] );
								}
								else if( pKey->get_flags() & KEY_SMOOTH ) {
									fprintf( pStream, "\t\t\t\t\tKEY SMOOTH %d %d %f %f %f %f %f %f\n", r, pKey->get_time(), f32Vals[0],
										pKey->get_tens(), pKey->get_cont(), pKey->get_bias(), pKey->get_ease_in(), pKey->get_ease_out() );
								}
								else {
									fprintf( pStream, "\t\t\t\t\tKEY LINEAR %d %d %f %f %f\n", r, pKey->get_time(), f32Vals[0],
										pKey->get_ease_in(), pKey->get_ease_out() );
								}
							}
							fprintf( pStream, "\t\t\t\t}\n" );
						}

						fprintf( pStream, "\t\t\t}\n" );
					}
					else if( pParam->get_type() == PARAM_TYPE_VECTOR2 ) {
						ParamVector2C*		pVecParam = (ParamVector2C*)pParam;
						Vector2C			rVal;

						pVecParam->get_val( 0, rVal );

						fprintf( pStream, "\t\t\tPARAMETER VECTOR2 {\n" );
						fprintf( pStream, "\t\t\t\tNAME \"%s\"\n", pParam->get_name() );
						fprintf( pStream, "\t\t\t\tVALUE %f %f\n", rVal[0], rVal[1] );

						ControllerC*	pCont = pVecParam->get_controller();
						if( pCont && pCont->get_key_count() ) {
							fprintf( pStream, "\t\t\t\tCONTROLLER {\n", pCont->get_key_count() );
							fprintf( pStream, "\t\t\t\t\tOUT_OF_RANGE 0x%x 0x%x\n", pCont->get_start_ort(), pCont->get_end_ort() );
							fprintf( pStream, "\t\t\t\t\tKEY_COUNT %d\n", pCont->get_key_count() );

							for( uint32 r = 0; r < pCont->get_key_count(); r++ ) {
								KeyC*	pKey = pCont->get_key( r );
								pKey->get_value( f32Vals );

								if( pKey->get_flags() & KEY_HOLD ) {
									fprintf( pStream, "\t\t\t\t\tKEY HOLD %d %d %f %f\n", r, pKey->get_time(), f32Vals[0], f32Vals[1] );
								}
								else if( pKey->get_flags() & KEY_SMOOTH ) {
									fprintf( pStream, "\t\t\t\t\tKEY SMOOTH %d %d %f %f %f %f %f %f %f\n", r, pKey->get_time(), f32Vals[0], f32Vals[1],
										pKey->get_tens(), pKey->get_cont(), pKey->get_bias(), pKey->get_ease_in(), pKey->get_ease_out() );
								}
								else {
									fprintf( pStream, "\t\t\t\t\tKEY LINEAR %d %d %f %f %f %f\n", r, pKey->get_time(), f32Vals[0], f32Vals[1],
										pKey->get_ease_in(), pKey->get_ease_out() );
								}
							}
							fprintf( pStream, "\t\t\t\t}\n" );
						}
						
						fprintf( pStream, "\t\t\t}\n" );
					}
					else if( pParam->get_type() == PARAM_TYPE_VECTOR3 ) {
						ParamVector3C*		pVecParam = (ParamVector3C*)pParam;
						Vector3C			rVal;

						pVecParam->get_val( 0, rVal );
						
						fprintf( pStream, "\t\t\tPARAMETER VECTOR3 {\n" );
						fprintf( pStream, "\t\t\t\tNAME \"%s\"\n", pParam->get_name() );
						fprintf( pStream, "\t\t\t\tVALUE %f %f\n", rVal[0], rVal[1], rVal[2] );

						ControllerC*	pCont = pVecParam->get_controller();
						if( pCont && pCont->get_key_count() ) {
							fprintf( pStream, "\t\t\t\tCONTROLLER {\n", pCont->get_key_count() );
							fprintf( pStream, "\t\t\t\t\tOUT_OF_RANGE 0x%x 0x%x\n", pCont->get_start_ort(), pCont->get_end_ort() );
							fprintf( pStream, "\t\t\t\t\tKEY_COUNT %d\n", pCont->get_key_count() );

							for( uint32 r = 0; r < pCont->get_key_count(); r++ ) {
								KeyC*	pKey = pCont->get_key( r );
								pKey->get_value( f32Vals );

								if( pKey->get_flags() & KEY_HOLD ) {
									fprintf( pStream, "\t\t\t\t\tKEY HOLD %d %d %f %f %f\n", r, pKey->get_time(), f32Vals[0], f32Vals[1], f32Vals[2] );
								}
								else if( pKey->get_flags() & KEY_SMOOTH ) {
									fprintf( pStream, "\t\t\t\t\tKEY SMOOTH %d %d %f %f %f %f %f %f %f %f\n", r, pKey->get_time(),
										f32Vals[0], f32Vals[1], f32Vals[2], pKey->get_tens(), pKey->get_cont(),
										pKey->get_bias(), pKey->get_ease_in(), pKey->get_ease_out() );
								}
								else {
									fprintf( pStream, "\t\t\t\t\tKEY LINEAR %d %d %f %f %f %f %f\n", r, pKey->get_time(), f32Vals[0], f32Vals[1], f32Vals[2],
										pKey->get_ease_in(), pKey->get_ease_out() );
								}
							}
							fprintf( pStream, "\t\t\t\t}\n" );
						}
						
						
						fprintf( pStream, "\t\t\t}\n" );
					}
					else if( pParam->get_type() == PARAM_TYPE_COLOR ) {
						ParamColorC*		pColParam = (ParamColorC*)pParam;
						ColorC				rCol;

						pColParam->get_val( 0, rCol );
						
						fprintf( pStream, "\t\t\tPARAMETER COLOR {\n" );
						fprintf( pStream, "\t\t\t\tNAME \"%s\"\n", pParam->get_name() );
						fprintf( pStream, "\t\t\t\tVALUE %d %d %d %d\n", (int32)(rCol[0] * 255.0f), (int32)(rCol[1] * 255.0f), (int32)(rCol[2] * 255.0f), (int32)(rCol[3] * 255.0f) );

						ControllerC*	pCont = pColParam->get_controller();
						if( pCont && pCont->get_key_count() ) {
							fprintf( pStream, "\t\t\t\tCONTROLLER {\n", pCont->get_key_count() );
							fprintf( pStream, "\t\t\t\t\tOUT_OF_RANGE 0x%x 0x%x\n", pCont->get_start_ort(), pCont->get_end_ort() );
							fprintf( pStream, "\t\t\t\t\tKEY_COUNT %d\n", pCont->get_key_count() );

							for( uint32 r = 0; r < pCont->get_key_count(); r++ ) {
								KeyC*	pKey = pCont->get_key( r );
								pKey->get_value( f32Vals );

								if( pKey->get_flags() & KEY_HOLD ) {
									fprintf( pStream, "\t\t\t\t\tKEY HOLD %d %d %d %d %d %d\n", r, pKey->get_time(), (int32)(f32Vals[0] * 255.0f), (int32)(f32Vals[1] * 255.0f), (int32)(f32Vals[2] * 255.0f), (int32)(f32Vals[3] * 255.0f) );
								}
								else if( pKey->get_flags() & KEY_SMOOTH ) {
									fprintf( pStream, "\t\t\t\t\tKEY SMOOTH %d %d %d %d %d %d %f %f %f %f %f\n", r, pKey->get_time(),
										(int32)(f32Vals[0] * 255.0f), (int32)(f32Vals[1] * 255.0f), (int32)(f32Vals[2] * 255.0f), (int32)(f32Vals[3] * 255.0f), pKey->get_tens(), pKey->get_cont(),
										pKey->get_bias(), pKey->get_ease_in(), pKey->get_ease_out() );
								}
								else {
									fprintf( pStream, "\t\t\t\t\tKEY LINEAR %d %d %d %d %d %d %f %f\n", r, pKey->get_time(), (int32)(f32Vals[0] * 255.0f), (int32)(f32Vals[1] * 255.0f), (int32)(f32Vals[2] * 255.0f), (int32)(f32Vals[3] * 255.0f),
										pKey->get_ease_in(), pKey->get_ease_out() );
								}
							}
							fprintf( pStream, "\t\t\t\t}\n" );
						}
						
						fprintf( pStream, "\t\t\t}\n" );
					}
					else if( pParam->get_type() == PARAM_TYPE_TEXT ) {
						ParamTextC*		pTextParam = (ParamTextC*)pParam;
						std::string		sText;
						
						sText = pTextParam->get_val( 0 );

						fprintf( pStream, "\t\t\tPARAMETER TEXT {\n" );
						fprintf( pStream, "\t\t\t\tNAME \"%s\"\n", pParam->get_name() );
						fprintf( pStream, "\t\t\t\tTEXT \"%s\"\n", sText.c_str() );
						fprintf( pStream, "\t\t\t}\n" );
					}

				}
				fprintf( pStream, "\t\t}\n" );
			}
			fprintf( pStream, "\t}\n" );
		}

		fprintf( pStream, "}\n" );
	}

	fclose( pStream );
*/

	return true;
}


//
//
// File list methods
//
//

void
CDemopajaDoc::ImportFile( Import::FileHandleC* pFolder ) 
{
	uint32	i;

	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();
	FactoryC*		pFactory = pApp->GetFactory();

	CString	sFileTypes;
	CString sKnownFormats;
	bool	bFirstKnown = true;

	sKnownFormats = "All Formats|";

	std::vector<int32>	rImporters;

	for( i = 0; i < pFactory->get_classdesc_count(); i++ ) {
		ClassDescC*	pDesc = pFactory->get_classdesc( i );
		if( pDesc->get_classtype() == CLASS_TYPE_FILEIMPORT ) {

			CString	sExt;
			for( uint32 j = 0; j < pDesc->get_ext_count(); j++ ) {
				if( j )
					sExt += ";";
				sExt += "*.";
				sExt += pDesc->get_ext( j );

				if( bFirstKnown )
					bFirstKnown = false;
				else
					sKnownFormats += ";";
				sKnownFormats += "*.";
				sKnownFormats += pDesc->get_ext( j );
			}

			sFileTypes += pDesc->get_name();
			sFileTypes += "(";
			sFileTypes += sExt;
			sFileTypes += ")|";
			sFileTypes += sExt;
			sFileTypes += "|";

			rImporters.push_back( i );
		}
	}

	// add all known formats
	sFileTypes += sKnownFormats;
	sFileTypes += "|";

	sFileTypes += "All Files (*.*)|*.*||";

	int32	i32LastExt = pApp->GetProfileInt( "Settings", "LastImportExt", 1 );

	CFileDialog		rDlg( TRUE, NULL, NULL, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ALLOWMULTISELECT, sFileTypes );
	rDlg.m_ofn.lpstrTitle = __TEXT( "Import" );
	rDlg.m_ofn.nFilterIndex = i32LastExt;
	rDlg.m_ofn.lpstrInitialDir = pApp->GetCurrentDir( CURDIR_IMPORT );

	if( rDlg.DoModal() == IDOK ) {

		int32	i32Idx = rDlg.m_ofn.nFilterIndex - 1;
		// Remember the last extension
		pApp->WriteProfileInt( "Settings", "LastImportExt", i32Idx + 1 );


		uint32	ui32Session = m_pDemoInterface->begin_session();
		m_pDemoInterface->set_parent_folder( pFolder );

		POSITION	rPos = rDlg.GetStartPosition();

		while( rPos ) {

			CString	sFilename = rDlg.GetNextPathName( rPos );

			// Remember path.
			pApp->SetCurrentDir( CURDIR_IMPORT, sFilename );

			// Make file name relative
			sFilename = m_pDemoInterface->get_relative_path( sFilename );

			TRACE( "--rel: '%s'\n", sFilename );

			// Do undo
			UndoC*	pUndo = new UndoC( "Add New File" );
			UndoC*	pOldUndo = m_pFileList->begin_editing( pUndo );

			FileHandleC* pNewHandle = m_pDemoInterface->request_import( sFilename, NULL_SUPERCLASS, NULL_CLASSID );

			m_pFileList->end_editing( pOldUndo );
			m_rUndoManager.push( pUndo );

			// Check if load failed
			if( !pNewHandle ) {
				m_rUndoManager.undo();			// undo the add operation
				m_rUndoManager.purge_redo();	// avoid redo operation
			}
			else {
				ImportableI*	pImp = pNewHandle->get_importable();
				if( pImp && pImp->has_properties() ) {
					pImp->prompt_properties();
				}
			}
		}

		SetModifiedFlag();
		UpdateFileReferences();
		NotifyViews( NOTIFY_FILELIST_CHANGED );

		m_pDemoInterface->end_session( ui32Session );
	}
}

FileHandleC*
CDemopajaDoc::CreateFile( const PluginClass::ClassIdC& rClassId, Import::FileHandleC* pFolder )
{
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	FactoryC*			pFactory = pApp->GetFactory();

	ImportableI*	pImp = (ImportableI*)pFactory->create( rClassId );


	if( pImp ) {
		UndoC*	pUndo = new UndoC( "Create File" );
		UndoC*	pOldUndo = m_pFileList->begin_editing( pUndo );

		FileHandleC*	pCaller = m_pFileList->add_file( pImp, pFolder );
		m_pFileList->end_editing( pOldUndo );
		m_rUndoManager.push( pUndo );

		uint32	ui32Session = m_pDemoInterface->begin_session();
		m_pDemoInterface->set_parent_folder( pCaller->get_parent_handle() );

		if( !pImp->create_file( m_pDemoInterface ) ) {
			::MessageBox( NULL, "An error occured while creating the file.", "Import Error", MB_ICONERROR | MB_OK );
			m_rUndoManager.undo();			// undo the add operation
			m_rUndoManager.purge_redo();	// avouid redo operation
			m_pDemoInterface->end_session( ui32Session );
			return 0;
		}

		if( pImp->has_properties() ) {
			if( !pImp->prompt_properties() ) {
				m_rUndoManager.undo();			// undo the add operation
				m_rUndoManager.purge_redo();	// avouid redo operation
				m_pDemoInterface->end_session( ui32Session );
				return 0;
			}
		}

		pImp->initialize( INIT_INITIAL_UPDATE, m_pDemoInterface );

		m_pDemoInterface->end_session( ui32Session );

		SetModifiedFlag();

		UpdateFileReferences();
		UpdateSceneViewList();

		return pCaller;
	}

	return 0;
}

FileHandleC*
CDemopajaDoc::CreateFolder( const char* szName, FileHandleC* pFolder, PajaTypes::uint32 ui32ColorFlags )
{
	UndoC*	pUndo = new UndoC( "Create Folder" );
	UndoC*	pOldUndo = m_pFileList->begin_editing( pUndo );

	FileHandleC*	pCaller = m_pFileList->add_folder( szName, pFolder );

	pCaller->set_flags( (pCaller->get_flags() & ~FILEHANDLE_FOLDERCOLOR_MASK) | (ui32ColorFlags & FILEHANDLE_FOLDERCOLOR_MASK) );

	m_pFileList->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();

	return pCaller;
}


void
CDemopajaDoc::RenameFolder( uint32 ui32Index, const char* szName, PajaTypes::uint32 ui32ColorFlags )
{
	FileHandleC*	pHandle = m_pFileList->get_file( ui32Index );

	if( !pHandle )
		return;

	UndoC*	pUndo = new UndoC( "Change Folder Name" );
	UndoC*	pOldUndo;

	pOldUndo = pHandle->begin_editing( pUndo );

	pHandle->set_folder_name( szName );
	pHandle->set_flags( (pHandle->get_flags() & ~FILEHANDLE_FOLDERCOLOR_MASK) | (ui32ColorFlags & FILEHANDLE_FOLDERCOLOR_MASK) );

	pHandle->end_editing( pOldUndo );

	m_rUndoManager.push( pUndo );
	SetModifiedFlag();
}

void
CDemopajaDoc::MoveFileOrder( uint32 ui32Index, FileHandleC* pNewParent )
{
	FileHandleC*	pHandle = m_pFileList->get_file( ui32Index );

	if( !pHandle )
		return;

	UndoC*	pUndo = new UndoC( "Move File" );
	UndoC*	pOldUndo;

	pOldUndo = pHandle->begin_editing( pUndo );

	pHandle->set_parent_handle( pNewParent );

	pHandle->end_editing( pOldUndo );

	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
}

void
CDemopajaDoc::DeleteFile( uint32 ui32Index )
{
	FileHandleC*	pHandle = m_pFileList->get_file( ui32Index );

	if( !pHandle )
		return;

	bool	bUpdateSceneList = false;

	// make sure the fodler is empty before deleting it.
	if( pHandle->get_flags() & FILEHANDLE_FOLDER ) {
		bool	bHasFiles = false;
		for( uint32 i = 0; i < m_pFileList->get_file_count(); i++ ) {
			if( m_pFileList->get_file( i )->get_parent_handle() == pHandle ) {
				bHasFiles = true;
				break;
			}
		}
		if( bHasFiles ) {
			::MessageBox( NULL, "The folder is not empty.\nRemove all files from the folder and retry.", "Folder Not Empty", MB_ICONWARNING | MB_OK );
			return;
		}
	}

	// Change the current scene to main scene if the deleted file is scen and
	// the scene is currently being edited.
	ImportableI*	pImp = pHandle->get_importable();
	if( pImp && pImp->get_class_id() == CLASS_SUBSCENE_IMPORTABLE ) {
		SceneImportC*	pSceneImp = (SceneImportC*)pImp;
		SceneC*	pScene = pSceneImp->get_scene();
		if( pScene == m_pCurrentScene ) {
			m_pCurrentScene = m_pMainScene;
		}
		bUpdateSceneList = true;
	}

	if( pHandle->get_reference_count() == 0 ) {
		UndoC*	pUndo = new UndoC( "Delete File" );
		UndoC*	pOldUndo = m_pFileList->begin_editing( pUndo );

		m_pFileList->del_file( ui32Index );

		m_pFileList->end_editing( pOldUndo );
		m_rUndoManager.push( pUndo );

		SetModifiedFlag();

		// clear clipboard because there can be effect in clipboard which references the deleted file
		ClearClipboard();
	}
	else {
		::MessageBox( NULL, "This file is still in use.\nRemove all references to this file and retry.", "File Still in Use", MB_ICONWARNING | MB_OK );
	}

	if( bUpdateSceneList ) {
		UpdateFileReferences();
		UpdateSceneViewList();
		// update everything
		NotifyViews( NOTIFY_RESET_VIEWS );
	}
}

void
CDemopajaDoc::ReloadFile( uint32 ui32Index )
{
	UndoC*	pUndo = new UndoC( "Reload File" );
	UndoC*	pOldUndo = m_pFileList->begin_editing( pUndo );

	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	FactoryC*			pFactory = pApp->GetFactory();

	FileHandleC*	pOldHandle = m_pFileList->get_file( ui32Index );
	ImportableI*	pImp = pOldHandle->get_importable();

//	PajaChooseImporterDialogC	rChooseImpDlg;
//	DemoInterfaceC	rInterface( m_pDeviceContext, m_pTimeContext, m_pFileList, pFactory, 0, &rChooseImpDlg );
//	SetupDemoInterfaceC( &rInterface );

	uint32	ui32Session = m_pDemoInterface->begin_session();
	m_pDemoInterface->set_parent_folder( pOldHandle->get_parent_handle() );

	if( pImp ) {
		FILE*	pFile = fopen( m_pDemoInterface->get_absolute_path( pImp->get_filename() ), "rb" );
		if( !pFile ) {
			::MessageBox( NULL, "The original file cannot be found.\n Try command \"Replace with\" instead.", "File Reload Error", MB_ICONERROR | MB_OK );
			m_pDemoInterface->end_session( ui32Session );
			return;
		}
		else
			fclose( pFile );
	}
	else {
		::MessageBox( NULL, "Error reloading file.", "File Reload Error", MB_ICONERROR | MB_OK );
		m_pDemoInterface->end_session( ui32Session );
		return;
	}

	m_pFileList->reload_file( ui32Index, m_pDemoInterface );

	m_pFileList->end_editing( pOldUndo );

	// update parameters
	// file handle will stay the same, only the stuff inside it changes
	FileHandleC*	pNewHandle = m_pFileList->get_file( ui32Index );
	if( pNewHandle ) {
		// main scene
		UpdateFileHandles( pNewHandle, m_pMainScene, pUndo );
		// scene files
		for( uint32 i = 0; i < m_pFileList->get_file_count(); i++ ) {
			ImportableI*	pImp = m_pFileList->get_file( i )->get_importable();
			if( pImp && pImp->get_class_id() == CLASS_SUBSCENE_IMPORTABLE ) {
				SceneImportC*	pSceneImp = (SceneImportC*)pImp;
				UpdateFileHandles( pNewHandle, pSceneImp->get_scene(), pUndo );
			}
		}
	}

	FileUpdateNotify( pNewHandle );

	m_rUndoManager.push( pUndo );

	m_pDemoInterface->end_session( ui32Session );

	SetModifiedFlag();
}


void CDemopajaDoc::ReplaceFile( uint32 ui32Index )
{
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	FactoryC*			pFactory = pApp->GetFactory();
	CString				sFileTypes;
	FileHandleC*		pHandle = m_pFileList->get_file( ui32Index );
//	PajaChooseImporterDialogC	rChooseImpDlg;
//	DemoInterfaceC	rInterface( m_pDeviceContext, m_pTimeContext, m_pFileList, pFactory, 0, &rChooseImpDlg );
	ImportableI*		pImp = pHandle->get_importable();
	uint32				i;

	if( !pImp )
		return;

	uint32	ui32Session = m_pDemoInterface->begin_session();
	m_pDemoInterface->set_parent_folder( pHandle->get_parent_handle() );

//	SetupDemoInterfaceC( &rInterface );

	for( i = 0; i < pFactory->get_classdesc_count(); i++ ) {
		ClassDescC*	pDesc = pFactory->get_classdesc( i );
		if( pDesc->get_class_id() == pImp->get_class_id() ) {

			CString	sExt;
			for( uint32 j = 0; j < pDesc->get_ext_count(); j++ ) {
				if( j )
					sExt += ";";
				sExt += "*.";
				sExt += pDesc->get_ext( j );
			}

			sFileTypes += pDesc->get_name();
			sFileTypes += "(";
			sFileTypes += sExt;
			sFileTypes += ")|";
			sFileTypes += sExt;
			sFileTypes += "|";
		}
	}

	CFileDialog	rDlg( TRUE, NULL, NULL, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER, sFileTypes );
	rDlg.m_ofn.lpstrTitle = "Replace With";
	rDlg.m_ofn.lpstrInitialDir = pApp->GetCurrentDir( CURDIR_IMPORT );

	if( rDlg.DoModal() == IDOK ) {

		pApp->SetCurrentDir( CURDIR_IMPORT, rDlg.GetPathName() );

		UndoC*	pUndo = new UndoC( "Replace With" );
		UndoC*	pOldUndo = m_pFileList->begin_editing( pUndo );

		m_pFileList->replace_file( ui32Index, rDlg.GetPathName(), m_pDemoInterface );

		m_pFileList->end_editing( pOldUndo );

		// update parameters
		// file handle will stay the same, only the stuff inside it changes
		FileHandleC*	pNewHandle = m_pFileList->get_file( ui32Index );
		if( pNewHandle ) {
			// main scene
			UpdateFileHandles( pNewHandle, m_pMainScene, pUndo );
			// scene files
			for( i = 0; i < m_pFileList->get_file_count(); i++ ) {
				ImportableI*	pImp = m_pFileList->get_file( i )->get_importable();
				if( pImp && pImp->get_class_id() == CLASS_SUBSCENE_IMPORTABLE ) {
					SceneImportC*	pSceneImp = (SceneImportC*)pImp;
					UpdateFileHandles( pNewHandle, pSceneImp->get_scene(), pUndo );
				}
			}
		}

		FileUpdateNotify( pNewHandle );

		m_rUndoManager.push( pUndo );
		SetModifiedFlag();
	}

	m_pDemoInterface->end_session( ui32Session );
}

void CDemopajaDoc::PromptFileProperties( uint32 ui32Index )
{
	bool				bRes;
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	FactoryC*			pFactory = pApp->GetFactory();
//	PajaChooseImporterDialogC	rChooseImpDlg;
//	DemoInterfaceC	rInterface( m_pDeviceContext, m_pTimeContext, m_pFileList, pFactory, 0, &rChooseImpDlg );
	UndoC*				pUndo = new UndoC( "Change Properties" );
	UndoC*				pOldUndo = m_pFileList->begin_editing( pUndo );

//	SetupDemoInterfaceC( &rInterface );
	uint32	ui32Session = m_pDemoInterface->begin_session();

	bRes = m_pFileList->prompt_properties( ui32Index );
	m_pFileList->end_editing( pOldUndo );

	// update parameters
	// file handle will stay the same, only the stuff inside it changes
	FileHandleC*	pNewHandle = m_pFileList->get_file( ui32Index );
	if( pNewHandle ) {
		// main scene
		UpdateFileHandles( pNewHandle, m_pMainScene, pUndo );
		// scene files
		for( uint32 i = 0; i < m_pFileList->get_file_count(); i++ ) {
			ImportableI*	pImp = m_pFileList->get_file( i )->get_importable();
			if( pImp && pImp->get_class_id() == CLASS_SUBSCENE_IMPORTABLE ) {
				SceneImportC*	pSceneImp = (SceneImportC*)pImp;
				UpdateFileHandles( pNewHandle, pSceneImp->get_scene(), pUndo );
			}
		}

		FileUpdateNotify( pNewHandle );
	}

	m_rUndoManager.push( pUndo );

	if( !bRes )
		m_rUndoManager.undo();
	UndoNotify();

	SetModifiedFlag();

	m_pDemoInterface->end_session( ui32Session );
}

void CDemopajaDoc::CreateScene( Import::FileHandleC* pFolder )
{
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	FactoryC*			pFactory = pApp->GetFactory();

	SceneImportC*	pImp = (SceneImportC*)pFactory->create( CLASS_SUBSCENE_IMPORTABLE );

	if( pImp ) {
		UndoC*	pUndo = new UndoC( "Create Scene" );
		UndoC*	pOldUndo = m_pFileList->begin_editing( pUndo );

		FileHandleC*	pCaller = m_pFileList->add_file( pImp, pFolder );
		m_pFileList->end_editing( pOldUndo );
		m_rUndoManager.push( pUndo );

//		PajaChooseImporterDialogC	rChooseImpDlg;
//		DemoInterfaceC	rInterface( m_pDeviceContext, m_pTimeContext, m_pFileList, pFactory, pCaller, &rChooseImpDlg );
//		SetupDemoInterfaceC( &rInterface );

		uint32	ui32Session = m_pDemoInterface->begin_session();
		m_pDemoInterface->set_parent_folder( pCaller->get_parent_handle() );

		if( !pImp->create_file( m_pDemoInterface ) ) {
			::MessageBox( NULL, "An error occured while creating the scene.", "Import Error", MB_ICONERROR | MB_OK );
//			pImp->release();
//			m_pFileList->end_editing( pOldUndo );
//			delete pUndo;
			m_rUndoManager.undo();			// undo the add operation
			m_rUndoManager.purge_redo();	// avouid redo operation
			m_pDemoInterface->end_session( ui32Session );
			return;
		}

		if( pImp->has_properties() ) {
			if( !pImp->prompt_properties() ) {
//				pImp->release();
//				m_pFileList->end_editing( pOldUndo );
//				delete pUndo;
				m_rUndoManager.undo();			// undo the add operation
				m_rUndoManager.purge_redo();	// avouid redo operation
				m_pDemoInterface->end_session( ui32Session );
				return;
			}
		}

		pImp->initialize( INIT_INITIAL_UPDATE, m_pDemoInterface );

//		m_pFileList->add_file( pImp, pFolder );
//		m_pFileList->end_editing( pOldUndo );
//		m_rUndoManager.push( pUndo );

		SetModifiedFlag();

		UpdateFileReferences();
		UpdateSceneViewList();

		m_pDemoInterface->end_session( ui32Session );
	}

	// update everything
	NotifyViews( NOTIFY_RESET_VIEWS );
}

void CDemopajaDoc::UpdateFileHandles( FileHandleC* pNewHandle, SceneC* pScene, UndoC* pUndo )
{
	BeginDeferHandleParamNotify();

	for( uint32 i = 0; i < pScene->get_layer_count(); i++ ) {
		LayerC*	pLayer = pScene->get_layer( i );
		if( !pLayer )
			continue;
		for( uint32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pEffect = pLayer->get_effect( j );
			if( !pEffect )
				continue;
			for( uint32 k = 0; k < pEffect->get_gizmo_count(); k++ ) {
				GizmoI*	pGizmo = pEffect->get_gizmo( k );
				if( !pGizmo )
					continue;
				for( uint32 p = 0; p < pGizmo->get_parameter_count(); p++ ) {
					ParamI*	pParam = pGizmo->get_parameter( p );
					if( !pParam )
						continue;
					if( pParam->get_type() == PARAM_TYPE_FILE ) {
						ParamFileC*	pFileParam = (ParamFileC*)pParam;

						UndoC*	pOldUndo = pFileParam->begin_editing( pUndo );
//						pFileParam->set_file( pHandle );

						HandleParamNotify( pFileParam->update_file_handles( pNewHandle ) );

						pFileParam->end_editing( pOldUndo );

/*						FileHandleC*	pHandle = pFileParam->get_file();
						if( pHandle == pNewHandle ) {

							UndoC*	pFxOldUndo = pEffect->begin_editing( pUndo );

							UndoC*	pOldUndo = pFileParam->begin_editing( pUndo );
							pFileParam->set_file( pHandle );
							pFileParam->end_editing( pOldUndo );

							pEffect->end_editing( pFxOldUndo );
						}*/
					}
				}
			}
		}
	}

	EndDeferHandleParamNotify();
}


void CDemopajaDoc::FileUpdateNotify( FileHandleC* pHandle )
{
	m_pDemoInterface->file_update_notify( pHandle );
}



BOOL CDemopajaDoc::OnOpenDocument(LPCTSTR lpszPathName) 
{
	DeleteContents();

	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	LoadC	rLoad( pApp->GetFactory() );

	if( rLoad.open( lpszPathName, "MOPPI|DEMOPAJA", 14 ) != IO_OK ) {
		if( rLoad.get_error() == IO_ERROR_FORMAT )
			::MessageBox( 0, "File is in wrong format.", "Load Error", MB_ICONERROR | MB_OK );
		else
			::MessageBox( 0, "Cannot open file for input.", "Load Error", MB_ICONERROR | MB_OK );
		return FALSE;
	}

	if( Load( &rLoad, false ) != IO_OK ) {

//		TRACE_LOG( "** ERROR MESSAGES **\n" );
//		TRACE_LOG( "\n" );

		std::string	sErrorLog;

		sErrorLog = "Loading file failed.\n\n";

		if( rLoad.get_error_message_count() ) {
			sErrorLog += "Following error occured:\n";
			for( uint32 i = 0; i < rLoad.get_error_message_count(); i++ ) {
				sErrorLog += rLoad.get_error_message( i );
				sErrorLog += "\n";
			}
		}

		::MessageBox( 0, sErrorLog.c_str(), "Load Error", MB_ICONERROR | MB_OK );

		// update everything
		NotifyViews( NOTIFY_RESET_VIEWS );

		return FALSE;
	}

	// make sure the list gets updated
	UpdateSceneViewList();

	// update everything
	NotifyViews( NOTIFY_RESET_VIEWS );

	// no modifications yet
	SetModifiedFlag( FALSE );

	return TRUE;
}

bool CDemopajaDoc::OnMergeDocument( const char* szPathName, FileHandleC* pFolder, bool bMergeFiles, bool bCreateScene ) 
{
	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	// first purge undo, so that nothing bad happens.
	m_rUndoManager.purge();

	LoadC	rLoad( pApp->GetFactory() );

	if( rLoad.open( szPathName, "MOPPI|DEMOPAJA", 14 ) != IO_OK ) {
		if( rLoad.get_error() == IO_ERROR_FORMAT )
			::MessageBox( 0, "File is in wrong format.", "Load Error", MB_ICONERROR | MB_OK );
		else
			::MessageBox( 0, "Cannot open file for input.", "Load Error", MB_ICONERROR | MB_OK );
		return false;
	}


	char szFname[_MAX_FNAME];
	_splitpath( szPathName, 0, 0, szFname, 0 );
	m_sMergeName = szFname;
	m_pMergeFolder = pFolder;
	m_bMergeFiles = bMergeFiles;
	m_bMergeCreateScene = bCreateScene;

	if( Load( &rLoad, true ) != IO_OK ) {

//		TRACE_LOG( "** ERROR MESSAGES **\n" );
//		TRACE_LOG( "\n" );

		std::string	sErrorLog;

		sErrorLog = "Loading file failed.\n\n";

		if( rLoad.get_error_message_count() ) {
			sErrorLog += "Following error occured:\n";
			for( uint32 i = 0; i < rLoad.get_error_message_count(); i++ ) {
				sErrorLog += rLoad.get_error_message( i );
				sErrorLog += "\n";
			}
		}

		::MessageBox( 0, sErrorLog.c_str(), "Load Error", MB_ICONERROR | MB_OK );

		// update everything
		NotifyViews( NOTIFY_RESET_VIEWS );

		return false;
	}

	// make sure the list gets updated
	UpdateSceneViewList();

	// update everything
	NotifyViews( NOTIFY_RESET_VIEWS );

	// no modifications yet
	SetModifiedFlag( TRUE );

	return true;
}

BOOL CDemopajaDoc::OnSaveDocument(LPCTSTR lpszPathName) 
{
	SaveC	rSave;

	if( rSave.open( lpszPathName, "MOPPI|DEMOPAJA", 14 ) != IO_OK ) {
		::MessageBox( 0, "Cannot open file for output.", "Save Error", MB_ICONERROR | MB_OK );
		return FALSE;
	}

	if( Save( &rSave ) != IO_OK ) {
		::MessageBox( 0, "Saving file failed.", "Save Error", MB_ICONERROR | MB_OK );
		return FALSE;
	}

	// all modifications are saved
	SetModifiedFlag( FALSE );

	return TRUE;
}


void CDemopajaDoc::DeleteContents()
{
	//
	// delete old data
	//

	// purge undo
	m_rUndoManager.purge();

	if( m_pMainScene )
		m_pMainScene->release();

	if( m_pFileList )
		m_pFileList->release();

	delete m_pDeviceContext;
	delete m_pDemoInterface;
	delete m_pTimeContext;


	//
	// init new data
	//
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	FactoryC*			pFactory = pApp->GetFactory();

	m_pMainScene = (SceneC*)SceneC::create_new();
	m_pMainScene->set_name( "Main Scene" );
	m_pCurrentScene = m_pMainScene;
	m_pFileList = (FileListC*)FileListC::create_new();
	m_pDeviceContext = new DeviceContextC;

	m_pTimeContext = new TimeContextC( m_pMainScene->get_beats_per_min(), m_pMainScene->get_qnotes_per_beat(), m_pMainScene->get_edit_accuracy() );

	m_pDemoInterface = new DemoInterfaceC( m_pDeviceContext, m_pTimeContext, m_pFileList, pApp->GetFactory() );
	SetupDemoInterface( m_pDemoInterface );

	m_rDeviceFeedback.set_scene( m_pMainScene );
	m_rDeviceFeedback.set_demo_interface( m_pDemoInterface );

//	m_i32SceneListItemsHeight = 0;
//	m_i32ItemHeight = 16;
//	m_bListUpdated = false;

	m_sComment = "Made in Moppi Demopaja";
	m_sMusicFile = "";

	ReleaseMusic();

	m_rSceneViews.clear();
	UpdateSceneViewList();
}



uint32 CDemopajaDoc::LoadProperties( LoadC* pLoad )
{
	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	//
	// load chunks
	//

	m_bShowWaveform = false;
	m_bDrawRulers = false;
	m_bShowMarkers = false;


	uint32		ui32Error = IO_OK;

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {

		case CHUNK_PROP_SHOWMUSICWAVEFORM:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
					m_bShowWaveform = true;
				}
			}
			break;



		case CHUNK_PROP_RULERS:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
					m_bDrawRulers = true;
				}
			}
			break;

		case CHUNK_PROP_MARKERS:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
					m_bShowMarkers = true;
				}
			}
			break;

		case CHUNK_PROP_GRID:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
					uint8	ui8Temp;
					// draw flag
					ui32Error = pLoad->read( &ui8Temp, sizeof( ui8Temp ) );
					m_bDrawGrid = ui8Temp ? true : false;
					// snap flag
					ui32Error = pLoad->read( &ui8Temp, sizeof( ui8Temp ) );
					m_bSnapToGrid = ui8Temp ? true : false;
					// size
					ui32Error = pLoad->read( &m_i32GridSize, sizeof( m_i32GridSize ) );
				}
			}
			break;

		default:
			assert( 0 );
			break;
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END )
			return ui32Error;
	}

	if( ui32Error != IO_OK && ui32Error != IO_END )
		return ui32Error;

	return IO_OK;
}


// file io
uint32 CDemopajaDoc::Load( LoadC* pLoad, bool bMerge )
{
	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	//
	// load chunks
	//

	uint32		ui32Error = IO_OK;
	uint32		i;
	char		szStr[2049];
	FactoryC*	pFactory = pLoad->get_factory();

	int32		i32LayoutWidth = 0;
	int32		i32LayoutHeight = 0;
	int32		i32Duration = 0;
	int32		i32BeatsPerMin = 0;
	int32		i32QNotesPerBeat = 0;
	int32		i32BeatsPerMeasure = 0;
	int32		i32EditAccuracy = 0;

	// Reset settings
	m_pTimeContext->set_time_settings( 120, 4, 2 );

	//
	// Set old file format compatible graphics driver
	//
	pApp->SetDefaultGraphicsDeviceName( "OpenGL Device Driver" );
	pApp->SetDefaultGraphicsDeviceId( CLASS_OPENGL_DEVICEDRIVER );


	std::vector<FileHandleC*>	rHandles;

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {

		case CHUNK_COMMENT:
			{
				if( !bMerge ) {
					if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
						ui32Error = pLoad->read_str( szStr );
						m_sComment = szStr;
					}
				}
			}
			break;

		case CHUNK_PROPERTIES:
			{
				if( !bMerge ) {
					if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
						ui32Error = LoadProperties( pLoad );
					}
				}
			}
			break;

		case CHUNK_MUSIC:
			{
				if( !bMerge ) {
					if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
						ui32Error = pLoad->read_str( szStr );
						m_sMusicFile = szStr;
//						TRACE( "music: \"%s\"\n", m_sMusicFile.c_str() );
					}
				}
			}
			break;

		case CHUNK_DEVICEDRIVER:
			{
				if( !bMerge && pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
					// Read class id
					uint32	ui32ClassId[2];
					pLoad->read( &ui32ClassId, sizeof( ui32ClassId ) );
					// Read class name
					ui32Error = pLoad->read_str( szStr );

					ClassIdC	rClassId( ui32ClassId[0], ui32ClassId[1] );

					DeviceInterfaceI*	pDev = (DeviceInterfaceI*)pFactory->create( rClassId );

					if( pDev ) {
						if( pDev->get_super_class_id() == SUPERCLASS_GRAPHICSDEVICE &&
							m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE ) ) {
							// load only one device driver
							pDev->release();
						}
						else {
							ui32Error = pDev->load( pLoad );
							m_pDeviceContext->register_interface( pDev );
						}
					}
					else {
						char		szClassId[32];
						std::string	sMsg;

						_snprintf( szClassId, 31, " (0x%08x, 0x%08x)", ui32ClassId[0], ui32ClassId[1] );	// this isnt the way to go, but I hate c++ streams

						sMsg = "- Device driver: Device driver ";
						sMsg += szStr;
						sMsg += szClassId;
						sMsg += " cannot be created. Propably missing DLL.";

						pLoad->add_error_message( sMsg.c_str() );

						ui32Error = IO_ERROR_READ;
					}
				}
			}
			break;

		case CHUNK_SCENE:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {
					if( bMerge ) {
						if( m_bMergeCreateScene ) {
							// Create new scene for the main scene

							SceneImportC*	pImp = (SceneImportC*)pFactory->create( CLASS_SUBSCENE_IMPORTABLE );
							FileHandleC*	pCaller = m_pFileList->add_file( pImp, 0 );

//							DemoInterfaceC	rInterface( m_pDeviceContext, m_pTimeContext, m_pFileList, pFactory, pCaller );

							uint32	ui32Session = m_pDemoInterface->begin_session();
							m_pDemoInterface->set_parent_folder( pCaller->get_parent_handle() );

							if( pImp->create_file( m_pDemoInterface ) ) {
								SceneC*	pScene = pImp->get_scene();
								if( pScene ) {
									ui32Error = pScene->load( pLoad, false );
								}
							}
							else {
								// failed
								m_pFileList->del_file( pCaller );
							}

							m_pDemoInterface->end_session( ui32Session );

						}
						else {
							// merge to the current main scene
							ui32Error = m_pMainScene->load( pLoad, true );
						}
					}
					else {
						ui32Error = m_pMainScene->load( pLoad, false );
					}
				}
			}
			break;

		case CHUNK_FILE:
			{
				if( pLoad->get_chunk_version() <= DEMOPAJA_DOC_VERSION ) {

					uint32	ui32ClassId[2];
					int32	i32Flags = 0;
					int32	i32ParentId = FILEHANDLE_INVALID_ID;
					bool	bFolder = false;
					std::string	sFolderName;

					if( pLoad->get_chunk_version() == DEMOPAJA_DOC_VERSION_1 ) {
						// read class id
						ui32Error = pLoad->read( &ui32ClassId, sizeof( ui32ClassId ) );
						// read class name
						ui32Error = pLoad->read_str( szStr );
					}
					else {
						// read flags
						ui32Error = pLoad->read( &i32Flags, sizeof( i32Flags ) );
						// read parent ID
						ui32Error = pLoad->read( &i32ParentId, sizeof( i32ParentId ) );

						if( i32Flags & FILEHANDLE_FOLDER ) {
							// read folder data
							// fodler name
							ui32Error = pLoad->read_str( szStr );
							sFolderName = szStr;
							bFolder = true;
						}
						else {
							// read file data
							// read class id
							ui32Error = pLoad->read( &ui32ClassId, sizeof( ui32ClassId ) );
							// read class name
							ui32Error = pLoad->read_str( szStr );
						}
					}

					FileHandleC*	pHandle = 0;

					if( bFolder ) {
						// add to the root folder, patch parents later.
						pHandle = m_pFileList->add_folder( sFolderName.c_str(), 0 );
						pHandle->set_parent_id( i32ParentId );
						pHandle->set_flags( i32Flags );
					}
					else {
						ClassIdC	rClassId( ui32ClassId[0], ui32ClassId[1] );

						ImportableI*	pImportable = (ImportableI*)pFactory->create( rClassId );

						if( pImportable )  {
							ui32Error = pImportable->load( pLoad );
							// add importable to file list
							// but test for duplicates
							if( m_bMergeFiles ) {
								if( m_pFileList->get_equal_file( pImportable ) ) {
									pImportable->release();
								}
								else {
									// add to the root folder, patch parents later.
									pHandle = m_pFileList->add_file( pImportable, 0 );
									pHandle->set_parent_id( i32ParentId );
								}
							}
							else {
								// add to the root folder, patch parents later.
								pHandle = m_pFileList->add_file( pImportable, 0 );
								pHandle->set_parent_id( i32ParentId );
							}
						}
						else {
							char		szClassId[32];
							std::string	sMsg;

							_snprintf( szClassId, 31, " (0x%08x, 0x%08x)", ui32ClassId[0], ui32ClassId[1] );	// this isnt the way to go, but I hate c++ streams

							sMsg = "- Load File: Importable ";
							sMsg += szStr;
							sMsg += szClassId;
							sMsg += " cannot be created. Propably missing DLL.";

							pLoad->add_error_message( sMsg.c_str() );

							ui32Error = IO_ERROR_READ;
						}
					}
					rHandles.push_back( pHandle);
				}
			}
			break;

		default:
			assert( 0 );
			break;
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END ) {
			DeleteContents();
			return ui32Error;
		}
	}

	if( ui32Error != IO_OK && ui32Error != IO_END ) {
		DeleteContents();
		return ui32Error;
	}

	//
	// patch file parents
	//

	TRACE( "** %d handles\n", rHandles.size() );

	for( i = 0; i < rHandles.size(); i++ ) {
		FileHandleC*	pHandle = rHandles[i];
		if( pHandle ) {

			if( pHandle->get_parent_id() != FILEHANDLE_INVALID_ID ) {
				TRACE( " - %02d valid handle %d\n", i, pHandle->get_parent_id() );
				pHandle->set_parent_handle( rHandles[pHandle->get_parent_id()] );
			}
			else {
				TRACE( " - %02d rootvalid handle\n", i );
				if( bMerge )
					pHandle->set_parent_handle( m_pMergeFolder );
			}
		}
	}
	// make all parent IDs invalid
	for( i = 0; i < m_pFileList->get_file_count(); i++ ) {
		FileHandleC*	pHandle = m_pFileList->get_file( i );
		if( pHandle )
			pHandle->set_parent_id( FILEHANDLE_INVALID_ID );
	}

	//
	// patch file handles
	//
	for( i = 0; i < pLoad->get_file_handle_patch_count(); i++ ) {
		FileHandleC*	pHandle = rHandles[pLoad->get_file_handle_patch_id( i )];//m_pFileList->get_file( pLoad->get_file_handle_patch_id( i ) );
		if( pHandle ) {
			void**	pPointer = pLoad->get_file_handle_patch_pointer( i );
			(*pPointer) = (void*)pHandle;
		}
		else {
			pLoad->add_error_message( "Load Doc: FileHandle patching failed." );
		}
	}

	//
	// patch effects
	//
	TRACE( "%d effect patches\n", pLoad->get_effect_patch_count() ); 
	for( i = 0; i < pLoad->get_effect_patch_count(); i++ )
	{
		LayerC*	pLayer = (LayerC*)pLoad->get_effect_patch_layer( i );
		if( pLayer )
		{
			EffectI*	pEffect = pLayer->get_effect( pLoad->get_effect_patch_id( i ) );

			TRACE( "patching effect: Layer: '%s'\n", pLayer->get_name() );
			if( pEffect )
				TRACE( "  - effect: '%s'\n", pEffect->get_name() );

			void**	pPointer = pLoad->get_effect_patch_pointer( i );
			(*pPointer) = (void*)pEffect;
		}
	}

	// Set time settings
	if( m_pMainScene )
		m_pTimeContext->set_time_settings( m_pMainScene->get_beats_per_min(), m_pMainScene->get_qnotes_per_beat(), m_pMainScene->get_edit_accuracy() );

	// If there's no audio spectrum device, create one.
	if( !m_pDeviceContext->query_interface( CLASS_AUDIO_SPECTRUM ) )
	{
		AudioSpectrumC*	pAudioSpec = (AudioSpectrumC*)pFactory->create( CLASS_AUDIO_SPECTRUM );
		if( pAudioSpec )
			m_pDeviceContext->register_interface( pAudioSpec );
	}

	// Init devices
	if( !m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE ) ) {
		DeviceInterfaceI*	pDev = (DeviceInterfaceI*)pFactory->create( CLASS_OPENGL_DEVICEDRIVER );
		if( pDev )
			m_pDeviceContext->register_interface( pDev );
		else
			pLoad->add_error_message( "Load Doc: Could not initialise graphics device." );
	}

	for( i = 0; i < m_pDeviceContext->get_interface_count(); i++ ) {
		DeviceInterfaceI*	pDev = m_pDeviceContext->get_interface( i );

		if( pDev->get_super_class_id() == SUPERCLASS_GRAPHICSDEVICE ) {
			GraphicsDeviceI*	pGDev = (GraphicsDeviceI*)pDev;

			// Find view
			POSITION		rPos = GetFirstViewPosition();
			CDemopajaView*	pView = 0;
			if( rPos )
				pView = (CDemopajaView*)GetNextView( rPos );

			if( pView )
			{
				CRect	rRect;
				pView->GetClientRect( &rRect );

				if( !pGDev->init( AfxGetInstanceHandle(), pView->GetSafeHwnd(), IDW_GRAPHICSDEVICE, GRAPHICSDEVICE_CREATE_EDITOR_CHILD,
					rRect.Width(), rRect.Height(), 0, &m_rDeviceFeedback ) ) {
					pLoad->add_error_message( "Load Doc: Could not initialise graphics device (init)." );
				}

				// Set font
				GUIDrawInterfaceI*	pGUI = (GUIDrawInterfaceI*)pGDev->query_interface( GRAPHICSDEVICE_GUIDRAW_INTERFACE );
				pGUI->use_font( m_rDefaultFont );

				// Set temp buffer size.
				pGDev->set_temp_graphicsbuffer_size( GetLayoutWidth(), GetLayoutHeight() );

				// Calculate correct mapping
				GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGDev->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
				BBox2C	rViewport( Vector2C( -20, -20 ), Vector2C( 680, 520 ) );
				pViewport->set_viewport( rViewport );
				pViewport->set_layout( BBox2C( Vector2C( 0, 0 ), Vector2C( (float32)GetLayoutWidth(), (float32)GetLayoutHeight() ) ) );
			}
			else {
				pLoad->add_error_message( "Load Doc: Could not initialise graphics device (view)." );
			}
		}
	}

	//
	// Init effects
	//
	InitialiseData( INIT_INITIAL_UPDATE );

	UpdateFileReferences();

	//
	// load the music
	//

	if( !m_sMusicFile.empty() ) {
		if( !LoadMusic( m_sMusicFile.c_str() ) ) {
			pLoad->add_error_message( "Load Doc: Could not load music file." );
		}
	}
	else
	{
		TRACE( "no music\n" );
		ReleaseMusic();
	}

	return IO_OK;
}


uint32
CDemopajaDoc::Save( SaveC* pSave )
{
	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	uint32		i;
	uint32		ui32Error = IO_OK;
//	int32		i32Tmp;
	std::string	sStr;

	//
	// demo options
	//
	pSave->begin_chunk( CHUNK_COMMENT, DEMOPAJA_DOC_VERSION );
		// write the comment
		sStr = m_sComment;
		if( sStr.size() > 2048 )
			sStr.resize( 2048 );
		ui32Error = pSave->write_str( sStr.c_str() );
	pSave->end_chunk();

	//
	// demo propertie (UI)
	//
	pSave->begin_chunk( CHUNK_PROPERTIES, DEMOPAJA_DOC_VERSION );
		// show waveform
		if( m_bShowWaveform ) {
			pSave->begin_chunk( CHUNK_PROP_SHOWMUSICWAVEFORM, DEMOPAJA_DOC_VERSION );
			pSave->end_chunk();
		}
		// rulers
		if( m_bDrawRulers ) {
			pSave->begin_chunk( CHUNK_PROP_RULERS, DEMOPAJA_DOC_VERSION );
			pSave->end_chunk();
		}
		// markers
		if( m_bShowMarkers ) {
			pSave->begin_chunk( CHUNK_PROP_MARKERS, DEMOPAJA_DOC_VERSION );
			pSave->end_chunk();
		}
		// grid
		uint8	ui8Temp;
		pSave->begin_chunk( CHUNK_PROP_GRID, DEMOPAJA_DOC_VERSION );
			// draw flag
			ui8Temp = m_bDrawGrid ? 1 : 0;
			pSave->write( &ui8Temp, sizeof( ui8Temp ) );
			// snap flag
			ui8Temp = m_bSnapToGrid ? 1 : 0;
			pSave->write( &ui8Temp, sizeof( ui8Temp ) );
			// size
			pSave->write( &m_i32GridSize, sizeof( m_i32GridSize ) );
		pSave->end_chunk();
	pSave->end_chunk();

	//
	// music filename
	//
	pSave->begin_chunk( CHUNK_MUSIC, DEMOPAJA_DOC_VERSION );
		// write the comment
		sStr = m_sMusicFile;
		if( sStr.size() > 2048 )
			sStr.resize( 2048 );
		ui32Error = pSave->write_str( sStr.c_str() );
	pSave->end_chunk();

	//
	// save used device-drivers
	//

	for( i = 0; i < m_pDeviceContext->get_interface_count(); i++ ) {
		DeviceInterfaceI*	pDev = m_pDeviceContext->get_interface( i );
		pSave->begin_chunk( CHUNK_DEVICEDRIVER, DEMOPAJA_DOC_VERSION );
			// write class id
			uint32	ui32ClassId[2];
			ui32ClassId[0] = pDev->get_class_id().get_class_a();
			ui32ClassId[1] = pDev->get_class_id().get_class_b();
			ui32Error = pSave->write( &ui32ClassId, sizeof( ui32ClassId ) );
			// write class name
			ui32Error = pSave->write_str( pDev->get_class_name() );
			// write device configuration
			ui32Error = pDev->save( pSave );
		pSave->end_chunk();
	}

	//
	// save files
	//

	// give files IDs (files can also refer to files)
	for( i = 0; i < m_pFileList->get_file_count(); i++ ) {
		// set file id
		FileHandleC*	pHandle = m_pFileList->get_file( i );
		pHandle->set_id( i );
	}


	for( i = 0; i < m_pFileList->get_file_count(); i++ ) {
		pSave->begin_chunk( CHUNK_FILE, DEMOPAJA_DOC_VERSION );
			FileHandleC*	pHandle = m_pFileList->get_file( i );

			int32	i32Flags = pHandle->get_flags();
			int32	i32ParentId = FILEHANDLE_INVALID_ID;

			if( pHandle->get_parent_handle() )
				i32ParentId = pHandle->get_parent_handle()->get_id();

			// flags
			ui32Error = pSave->write( &i32Flags, sizeof( i32Flags ) );
			// parent ID
			ui32Error = pSave->write( &i32ParentId, sizeof( i32ParentId ) );

//			TRACE( "saving pid: %d\n", i32ParentId );

			if( i32Flags & FILEHANDLE_FOLDER ) {
				// save folder info
				sStr = pHandle->get_folder_name();
				if( sStr.size() > 256 )
					sStr.resize( 256 );
				ui32Error = pSave->write_str( sStr.c_str() );
			}
			else {
				ImportableI*	pImportable = pHandle->get_importable();
				// write class id
				uint32	ui32ClassId[2];
				ui32ClassId[0] = pImportable->get_class_id().get_class_a();
				ui32ClassId[1] = pImportable->get_class_id().get_class_b();
				ui32Error = pSave->write( &ui32ClassId, sizeof( ui32ClassId ) );
				// write class name
				ui32Error = pSave->write_str( pImportable->get_class_name() );
				// write importable
				ui32Error = pImportable->save( pSave );
			}

		pSave->end_chunk();
	}

	//
	// save scenes
	//
	pSave->begin_chunk( CHUNK_SCENE, DEMOPAJA_DOC_VERSION );
		ui32Error = m_pMainScene->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

int32
CDemopajaDoc::GetLayoutWidth()
{
	if( GetCurrentScene() )
		return GetCurrentScene()->get_layout_width();
	return 0;
}

int32
CDemopajaDoc::GetLayoutHeight()
{
	if( GetCurrentScene() )
		return GetCurrentScene()->get_layout_height();
	return 0;
}

SceneC*
CDemopajaDoc::GetCurrentScene()
{
	return m_pCurrentScene;
}


void
CDemopajaDoc::UpdateSceneViewList()
{
	int32							i;
	typedef std::vector<SceneC*>	TSceneVec;
	typedef TSceneVec::iterator		TSceneIter;
	TSceneVec						rScenes;

	// Collect all available scenes
	rScenes.push_back( m_pMainScene );
	for( i = 0; i < m_pFileList->get_file_count(); i++ ) {
		ImportableI*	pImp = m_pFileList->get_file( i )->get_importable();
		if( pImp && pImp->get_class_id() == CLASS_SUBSCENE_IMPORTABLE ) {
			SceneImportC*	pSceneImp = (SceneImportC*)pImp;
			rScenes.push_back( pSceneImp->get_scene() );
		}
	}


	for( i = (int32)m_rSceneViews.size() - 1; i >= 0; i-- ) {

		TSceneIter it = std::find( rScenes.begin(), rScenes.end(), m_rSceneViews[i].m_pScene );

		if( it == rScenes.end() ) {
			// The scene doesnt exists anymore.
			m_rSceneViews.erase( m_rSceneViews.begin() + i );
		}
		else {
			// Scene exists.
			rScenes.erase( it );
		}
	}

	// Get Graphics device window size
	int32				i32DevWidth = 640;
	int32				i32DevHeight = 480;
	GraphicsDeviceI*	pDev = (GraphicsDeviceI*)m_pDeviceContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );
	if( pDev ) {
		// Calculate correct mapping
		GraphicsViewportI*	pViewport = (GraphicsViewportI*)pDev->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
		i32DevWidth = pViewport->get_width();
		i32DevHeight = pViewport->get_height();
	}
				
	// All remaining views in rScenes have to be added.
	for( TSceneIter it = rScenes.begin(), end = rScenes.end(); it != end; ++it ) {
		SceneViewS	rView;
		SceneC*	pScene = (*it);
		rView.m_pScene = pScene;
		rView.m_i32LayerListPos = 0;

		float32	f32HalfWidth = (float32)pScene->get_layout_width() / 2;
		float32	f32HalfHeight = (float32)pScene->get_layout_height() / 2;

		rView.m_rViewport[0] = Vector2C( f32HalfWidth - i32DevWidth / 2, f32HalfHeight - i32DevHeight / 2 );
		rView.m_rViewport[1] = rView.m_rViewport[0] + Vector2C( (float32)i32DevWidth, (float32)i32DevHeight );

		m_rSceneViews.push_back( rView );
	}

	m_i32CurrentSceneIdx = -1;

	for( i = 0; i < m_rSceneViews.size(); i++ ) {
		if( m_rSceneViews[i].m_pScene == GetCurrentScene() ) {
			m_i32CurrentSceneIdx = i;
		}
	}
}

uint32
CDemopajaDoc::GetSceneCount()
{
	return m_rSceneViews.size();
}

SceneC*
CDemopajaDoc::GetScene( uint32 ui32Index )
{
	if( ui32Index >= m_rSceneViews.size() )
		return m_pMainScene;
	return m_rSceneViews[ui32Index].m_pScene;
}

SceneC*
CDemopajaDoc::GetMainScene()
{
	return m_pMainScene;
}

void
CDemopajaDoc::SetCurrentScene( uint32 ui32Index )
{
	// empty key selection, otherwise the selection will point to the old
	// scene and the app will crash!
	m_pKeySelector->deselect_all();

	if( ui32Index >= m_rSceneViews.size() ) {
		m_pCurrentScene = m_pMainScene;
		m_i32CurrentSceneIdx = -1;
	}
	else {
		m_pCurrentScene = m_rSceneViews[ui32Index].m_pScene;
		m_i32CurrentSceneIdx = ui32Index;
	}

	// force to update layerlist
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}


int32
CDemopajaDoc::GetSceneLayerListPos()
{
	if( m_i32CurrentSceneIdx >= 0 && m_i32CurrentSceneIdx < m_rSceneViews.size() )
		return m_rSceneViews[m_i32CurrentSceneIdx].m_i32LayerListPos;
	return 0;
}

void
CDemopajaDoc::SetSceneLayerListPos( int32 i32Pos )
{
	if( m_i32CurrentSceneIdx >= 0 && m_i32CurrentSceneIdx < m_rSceneViews.size() )
		m_rSceneViews[m_i32CurrentSceneIdx].m_i32LayerListPos = i32Pos;
}

BBox2C
CDemopajaDoc::GetSceneViewport() const
{
	if( m_i32CurrentSceneIdx >= 0 && m_i32CurrentSceneIdx < m_rSceneViews.size() )
		return m_rSceneViews[m_i32CurrentSceneIdx].m_rViewport;

	BBox2C	rViewport;
	rViewport[0][0] = 0;
	rViewport[0][1] = 0;
	rViewport[1][0] = 10;
	rViewport[1][0] = 10;
	return rViewport;
}

void
CDemopajaDoc::SetSceneViewport( const PajaTypes::BBox2C& rBox )
{
	if( m_i32CurrentSceneIdx >= 0 && m_i32CurrentSceneIdx < m_rSceneViews.size() )
		m_rSceneViews[m_i32CurrentSceneIdx].m_rViewport = rBox;
}

void
CDemopajaDoc::AdjustSceneViewports( const Vector2C& rDelta )
{
	for( uint32 i = 0; i < m_rSceneViews.size(); i++ ) {
		Vector2C	rDeltaLocal = rDelta;
		rDeltaLocal[0] *= m_rSceneViews[i].m_rViewport.width();
		rDeltaLocal[1] *= m_rSceneViews[i].m_rViewport.height();

		m_rSceneViews[i].m_rViewport[0] -= rDeltaLocal;
		m_rSceneViews[i].m_rViewport[1] += rDeltaLocal;
	}
}


void
CDemopajaDoc::SetTimecursor( int32 i32Time )
{
	if( GetCurrentScene() )
		GetCurrentScene()->set_time( i32Time );
}

int32
CDemopajaDoc::GetTimecursor()
{
	if( GetCurrentScene() )
		return GetCurrentScene()->get_time();
	return 0;
}

void
CDemopajaDoc::SetDrawRulers( bool bState )
{
	m_bDrawRulers = bState;
}

bool
CDemopajaDoc::GetDrawRulers()
{
	return m_bDrawRulers;
}

void
CDemopajaDoc::SetShowMarkers( bool bState )
{
	m_bShowMarkers = bState;
}

bool
CDemopajaDoc::GetShowMarkers()
{
	return m_bShowMarkers;
}

void
CDemopajaDoc::SetDrawGrid( bool bState )
{
	m_bDrawGrid = bState;
}

bool
CDemopajaDoc::GetDrawGrid()
{
	return m_bDrawGrid;
}

int32
CDemopajaDoc::GetGridSize()
{
	return m_i32GridSize;
}

void
CDemopajaDoc::SetGridSize( int32 i32GridSize )
{
	m_i32GridSize = i32GridSize;
}

void
CDemopajaDoc::SetSnapToGrid( bool bState )
{
	m_bSnapToGrid = bState;
}

bool
CDemopajaDoc::GetSnapToGrid()
{
	return m_bSnapToGrid;
}


void
CDemopajaDoc::SetEditFocus( int32 i32Focus )
{
//	TRACE( "edit focus\n" );
	m_i32EditFocus = i32Focus;
}

int32
CDemopajaDoc::GetEditFocus()
{
	return m_i32EditFocus;
}

/*
KeySelectorC*
CDemopajaDoc::GetKeySelector()
{
	return m_pKeySelector;
}
*/

void
CDemopajaDoc::OnCut()
{
	// copy data first
	OnCopy();

	if( m_i32EditFocus == FOCUS_LAYERLIST ) {
		LayerAndEffectDeleteSelected( true );
	}
	else if( m_i32EditFocus == FOCUS_VIEW ) {
		EffectDeleteSelected( true );
	}
	else if( m_i32EditFocus == FOCUS_TIMELINE ) {
		if( m_pKeySelector->selected_items_count() == 1 ) {
			SceneItemC*	pItem = m_pKeySelector->get_selected_item( 0 );
			if( pItem->get_type() == SCENEITEM_PARAMETER ) {
				UndoC*	pUndo = new UndoC( "Cut Keys" );
				UndoC*	pOldUndo = m_pKeySelector->begin_editing( pUndo );

				m_pKeySelector->delete_selected();

				m_pKeySelector->end_editing( pOldUndo );
				GetUndoManager()->push( pUndo );
			}
		}
	}

	NotifyViews( NOTIFY_REDRAW_ALL );
}


void
CDemopajaDoc::ClearClipboard()
{
	uint32	i;

	// delete old content of clipboard
	if( m_rClipboard.m_pLayer ) {
		m_rClipboard.m_pLayer->release();
		m_rClipboard.m_pLayer = 0;
	}

	for( i = 0; i < m_rClipboard.m_rEffects.size(); i++ )
		if( m_rClipboard.m_rEffects[i] )
			m_rClipboard.m_rEffects[i]->release();
	m_rClipboard.m_rEffects.clear();

	for( i = 0; i < m_rClipboard.m_rKeys.size(); i++ )
		if( m_rClipboard.m_rKeys[i] )
			m_rClipboard.m_rKeys[i]->release();
	m_rClipboard.m_rKeys.clear();

//	m_rClipboard.m_pToLayer = 0;

	m_rClipboard.m_eType = CLIPBOARD_NONE;

	m_rClipboard.m_i32ParamType = 0;

	m_rClipboard.m_pFromScene = 0;
}


void
CDemopajaDoc::OnCopy()
{
	uint32	i;

	if( !GetCurrentScene()->get_layer_count() ) {
		return;
	}

	ClearClipboard();

	m_rClipboard.m_pFromScene = GetCurrentScene();

	if( m_i32EditFocus == FOCUS_LAYERLIST ) {
		// copy stuff to clipboard before delete

		int32	i32EffectsFound = 0;

		bool	bFirst = true;
		int32	i32MinTime = 0;

		for( i = 0; i < GetCurrentScene()->get_layer_count(); i++ ) {

			LayerC*	pLayer = GetCurrentScene()->get_layer( i );

			if( pLayer ) {
				if( pLayer->get_flags() & ITEM_SELECTED && !i32EffectsFound ) {
					m_rClipboard.m_pLayer = (LayerC*)pLayer->duplicate();
					m_rClipboard.m_pLayer->add_flags( ITEM_SELECTED );
					m_rClipboard.m_eType = CLIPBOARD_LAYER;

					for( uint32 i = 0; i < m_rClipboard.m_pLayer->get_effect_count(); i++ ) {
						EffectI*	pEff = m_rClipboard.m_pLayer->get_effect( i );
						if( pEff )
							pEff->initialize( INIT_INITIAL_UPDATE, m_pDemoInterface );
					}

					break;
				}

				for( int32 j = (int32)pLayer->get_effect_count() - 1; j >= 0; j-- ) {
					EffectI*	pEffect = pLayer->get_effect( j );
					if( pEffect->get_flags() & ITEM_SELECTED ) {

						// Find minimum time
						int32	i32StartTime = pEffect->get_timesegment()->get_segment_start();
						if( bFirst )
							i32MinTime = i32StartTime;
						else {
							if( i32StartTime < i32MinTime )
								i32MinTime = i32StartTime;
						}

						EffectI*	pNewEffect = (EffectI*)pEffect->duplicate();

						pNewEffect->initialize( INIT_INITIAL_UPDATE, m_pDemoInterface );
						
						pNewEffect->add_flags( ITEM_SELECTED );
						m_rClipboard.m_rEffects.push_back( pNewEffect );
						m_rClipboard.m_eType = CLIPBOARD_EFFECT;
						// set default layer
//						if( !m_rClipboard.m_pToLayer )
//							m_rClipboard.m_pToLayer = pLayer;
						i32EffectsFound++;
					}
				}
			}
		}

		// offset the time of each effect by min time
		if( i32EffectsFound && m_rClipboard.m_eType == CLIPBOARD_EFFECT ) {
			for( i = 0; i < m_rClipboard.m_rEffects.size(); i++ ) {
				int32	i32StartTime = m_rClipboard.m_rEffects[i]->get_timesegment()->get_segment_start();
				i32StartTime -= i32MinTime;
				m_rClipboard.m_rEffects[i]->get_timesegment()->set_segment_start( i32StartTime );
			}
		}
	}
	else if( m_i32EditFocus == FOCUS_VIEW ) {
		// copy stuff to clipboard before delete

		bool	bFirst = true;
		int32	i32MinTime = 0;

		for( i = 0; i < GetCurrentScene()->get_layer_count(); i++ ) {

			LayerC*	pLayer = GetCurrentScene()->get_layer( i );

			if( pLayer ) {
				for( int32 j = (int32)pLayer->get_effect_count() - 1; j >= 0; j-- ) {
					EffectI*	pEffect = pLayer->get_effect( j );
					if( pEffect->get_flags() & ITEM_SELECTED ) {

						// Find minimum time
						int32	i32StartTime = pEffect->get_timesegment()->get_segment_start();
						if( bFirst )
							i32MinTime = i32StartTime;
						else {
							if( i32StartTime < i32MinTime )
								i32MinTime = i32StartTime;
						}

						EffectI*	pNewEffect = (EffectI*)pEffect->duplicate();

						pNewEffect->initialize( INIT_INITIAL_UPDATE, m_pDemoInterface );

						pNewEffect->add_flags( ITEM_SELECTED );
						m_rClipboard.m_rEffects.push_back( pNewEffect );
						m_rClipboard.m_eType = CLIPBOARD_EFFECT;
						// set default layer
//						if( !m_rClipboard.m_pToLayer )
//							m_rClipboard.m_pToLayer = pLayer;
					}
				}
			}
		}

		// offset the time of each effect by min time
		if( m_rClipboard.m_eType = CLIPBOARD_EFFECT ) {
			for( i = 0; i < m_rClipboard.m_rEffects.size(); i++ ) {
				int32	i32StartTime = m_rClipboard.m_rEffects[i]->get_timesegment()->get_segment_start();
				i32StartTime -= i32MinTime;
				m_rClipboard.m_rEffects[i]->get_timesegment()->set_segment_start( i32StartTime );
			}
		}
	}
	else if( m_i32EditFocus == FOCUS_TIMELINE ) {

		if( m_pKeySelector->selected_items_count() == 1 ) {
			SceneItemC*	pItem = m_pKeySelector->get_selected_item( 0 );
			if( pItem->get_type() == SCENEITEM_PARAMETER ) {

				bool			bFirstKey = true;
				int32			i32Time;
				ParamI*			pParam = pItem->get_parameter();
				ControllerC*	pCont = pParam->get_controller();

				for( uint32 i = 0; i < pCont->get_key_count(); i++ ) {
					KeyC*	pKey = pCont->get_key( i );
					if( pKey->get_flags() & KEY_SELECTED ) {
						if( bFirstKey ) {
							i32Time = pKey->get_time();
							bFirstKey = false;
						}
						KeyC*	pNewKey = (KeyC*)pKey->duplicate();
						// change the key time so that the first key is at time 0
						pNewKey->set_time( pKey->get_time() - i32Time );
						m_rClipboard.m_rKeys.push_back( pNewKey );
					}
				}

				m_rClipboard.m_eType = CLIPBOARD_KEYS;
				m_rClipboard.m_i32ParamType = pParam->get_type();

				// get class filters for file parameter
				if( pParam->get_type() == PARAM_TYPE_FILE ) {
					ParamFileC*	pParamFile = (ParamFileC*)pParam;
					m_rClipboard.m_rFilterClassId = pParamFile->get_class_filter();
					m_rClipboard.m_rFilterSuperClassId = pParamFile->get_super_class_filter();
				}

			}
			else if( pItem->get_type() == SCENEITEM_LAYER || pItem->get_type() == SCENEITEM_EFFECT ) {

				bool			bFirstKey = true;
				int32			i32Time;

				TimeSegmentC*	pSeg = 0;
				if( pItem->get_type() == SCENEITEM_LAYER )
					pSeg = pItem->get_layer()->get_timesegment();
				if( pItem->get_type() == SCENEITEM_EFFECT )
					pSeg = pItem->get_effect()->get_timesegment();

				if( !pSeg )
					return;

				for( uint32 i = 0; i < pSeg->get_key_count(); i++ ) {
					KeyC*	pKey = pSeg->get_key( i );
					if( pKey->get_flags() & KEY_SELECTED ) {
						if( bFirstKey ) {
							i32Time = pKey->get_time();
							bFirstKey = false;
						}
						KeyC*	pNewKey = (KeyC*)pKey->duplicate();
						pNewKey->set_time( pKey->get_time() - i32Time );
						m_rClipboard.m_rKeys.push_back( pNewKey );
					}
				}

				m_rClipboard.m_eType = CLIPBOARD_TIMESEG_KEYS;

			}
		}
		else
			::MessageBox( 0, "Cannot copy keys from multiple sources.\nSelect keys from only one parameter or time segment.", "Copy Warning", MB_ICONWARNING | MB_OK );

	}
}

void
CDemopajaDoc::OnPaste()
{
	SceneC*	pFromScene = m_rClipboard.m_pFromScene;

	if( !pFromScene )
		return;

	if( m_i32EditFocus == FOCUS_LAYERLIST || m_i32EditFocus == FOCUS_VIEW ) {

		m_pKeySelector->deselect_all();

		// we are pasting an effect
		if( m_rClipboard.m_eType == CLIPBOARD_EFFECT && m_rClipboard.m_rEffects.size() ) {

			if( !GetCurrentScene()->get_layer_count() ) {
				return;
			}

			LayerC*	pPasteToLayer = 0;

			for( uint32 i = 0; i < GetCurrentScene()->get_layer_count(); i++ ) {

				LayerC*	pLayer = GetCurrentScene()->get_layer( i );

				if( pLayer->get_flags() & ITEM_SELECTED ) {
					TRACE( "using layer %d\n", i );
					pPasteToLayer = pLayer;
					break;
				}
			}

			if( !pPasteToLayer /*&& m_rClipboard.m_pToLayer*/ ) {
				// we didnt found any selected layer.. paste to topmost layer
				pPasteToLayer = GetCurrentScene()->get_layer( 0 ); //m_rClipboard.m_pToLayer;
				TRACE( "using first layer\n" );
			}

			// Deselect everything after we have found the selected layer
			DelFlagsAll( ITEM_LAYER | ITEM_EFFECT | ITEM_GIZMO | ITEM_PARAMETER, ITEM_SELECTED );

			if( pPasteToLayer ) {

				UndoC*	pUndo = new UndoC( "Paste Effects" );
				UndoC*	pOldUndo = pPasteToLayer->begin_editing( pUndo );

				// find paste time
				int32			i32Time = GetTimecursor();

				// offset time to relative in side the paste-to-layer.
				int32			i32TimeOffset = 0;
				TimeSegmentC*	pLayerSegment = pPasteToLayer->get_timesegment();
				if( pLayerSegment )
					i32Time -= pLayerSegment->get_segment_start();

				// find the minimum time from the effects which are being pasted.


				for( uint32 i = 0; i < m_rClipboard.m_rEffects.size(); i++ ) {
					EffectI*	pEffect = (EffectI*)m_rClipboard.m_rEffects[i]->duplicate();
					std::string	sName = pEffect->get_name();
					sName += " Duplicate";
					pEffect->set_name( sName.c_str() );

					TimeSegmentC*	pSeg = pEffect->get_timesegment();
					pSeg->set_segment_start( pSeg->get_segment_start() + i32Time );

					pEffect->initialize( INIT_INITIAL_UPDATE, m_pDemoInterface );

					pPasteToLayer->add_effect( pEffect );

				}

				pPasteToLayer->end_editing( pOldUndo );
				m_rUndoManager.push( pUndo );
			}
		}
		// we are pasting a layer
		else if( m_rClipboard.m_eType == CLIPBOARD_LAYER && m_rClipboard.m_pLayer ) {

			// Deselect everything
			DelFlagsAll( ITEM_LAYER | ITEM_EFFECT | ITEM_GIZMO | ITEM_PARAMETER, ITEM_SELECTED );

			SceneC*	pScene = GetCurrentScene();

			UndoC*	pUndo = new UndoC( "Paste Layer" );
			UndoC*	pOldUndo = pScene->begin_editing( pUndo );

			LayerC*	pLayer = m_rClipboard.m_pLayer;
			LayerC*	pNewLayer = pScene->add_layer();

			std::string	sName = pLayer->get_name();
			sName += " Duplicate";

			pNewLayer->copy( pLayer );

			for( uint32 i = 0; i < pNewLayer->get_effect_count(); i++ ) {
				EffectI*	pEff = pNewLayer->get_effect( i );
				if( pEff )
					pEff->initialize( INIT_INITIAL_UPDATE, m_pDemoInterface );
			}

			// find paste time
			int32			i32Time = GetTimecursor();
			m_rClipboard.m_pLayer->get_timesegment()->set_segment_start( i32Time );


			pNewLayer->set_name( sName.c_str() );
			pNewLayer->set_parent( pScene );

			pScene->end_editing( pOldUndo );
			m_rUndoManager.push( pUndo );
		}

		SetModifiedFlag();
		NotifyViews( NOTIFY_LAYERLIST_CHANGED );
	}
	else if( m_i32EditFocus == FOCUS_TIMELINE ) {

		if( !GetCurrentScene()->get_layer_count() ) {
			return;
		}

		if( m_rClipboard.m_eType == CLIPBOARD_TIMESEG_KEYS ) {
			// paste time segment keys


			bool	bFound = false;

			TimeSegmentC*	pSeg = 0;
			int32			i32Time = GetTimecursor();

			SceneItemC		rItem;

			// find firstselected time segment
			for( uint32 i = 0; i < m_pMainScene->get_layer_count() && !bFound; i++ ) {
				LayerC*	pLayer = m_pMainScene->get_layer( i );
				if( !pLayer )
					continue;

				if( pLayer->get_flags() & ITEM_SELECTED ) {
					pSeg = pLayer->get_timesegment();
					i32Time -= pSeg->get_segment_start();
					rItem.init( pLayer );
					rItem.set_flags( pLayer->get_flags() );
					bFound = true;
					break;
				}

				for( uint32 j = 0; j < pLayer->get_effect_count() && !bFound; j++ ) {
					EffectI*	pEffect = pLayer->get_effect( j );
					if( !pEffect )
						continue;

					if( pEffect->get_flags() & ITEM_SELECTED ) {
						pSeg = pEffect->get_timesegment();
						i32Time -= pLayer->get_timesegment()->get_segment_start();
						i32Time -= pSeg->get_segment_start();
						rItem.init( pEffect );
						rItem.set_flags( pEffect->get_flags() );
						bFound = true;
						break;
					}

				}
			}


			if( !pSeg )
				return;

			m_pKeySelector->deselect_all();

			UndoC*	pUndo = new UndoC( "Paste Keys" );
			UndoC*	pOldUndo = pSeg->begin_editing( pUndo );

			for( uint32 j = 0; j < m_rClipboard.m_rKeys.size(); j++ ) {
				KeyC*	pKey = m_rClipboard.m_rKeys[j];
				KeyC*	pNewKey = pSeg->add_key_at_time( i32Time + pKey->get_time() );
				pNewKey->copy( pKey );								// this copies time too
				pNewKey->set_time( i32Time + pKey->get_time() );	// set it again
			}

			pSeg->end_editing( pOldUndo );

			m_rUndoManager.push( pUndo );

			m_pKeySelector->deselect_all();
			m_pKeySelector->update_selected_keys( &rItem );

			SetModifiedFlag();
		}
		else if( m_rClipboard.m_eType == CLIPBOARD_KEYS ) {
			// paste keyframes

			bool	bFound = false;

			// go thru selected parameters, and paster key frames
			for( uint32 i = 0; i < m_pMainScene->get_layer_count() && !bFound; i++ ) {
				LayerC*	pLayer = m_pMainScene->get_layer( i );
				if( !pLayer )
					continue;
				for( uint32 j = 0; j < pLayer->get_effect_count() && !bFound; j++ ) {
					EffectI*	pEffect = pLayer->get_effect( j );
					if( !pEffect )
						continue;
					for( uint32 k = 0; k < pEffect->get_gizmo_count() && !bFound; k++ ) {
						GizmoI*	pGizmo = pEffect->get_gizmo( k );
						if( !pGizmo )
							continue;
						for( uint32 p = 0; p < pGizmo->get_parameter_count() && !bFound; p++ ) {
							ParamI*	pParam = pGizmo->get_parameter( p );
							if( !pParam )
								continue;

							if( pParam->get_type() == m_rClipboard.m_i32ParamType && pParam->get_flags() & ITEM_SELECTED ) {

								if( pParam->get_type() == PARAM_TYPE_FILE ) {
									// check file filter compatibility for file parameters
									ParamFileC*	pParamFile = (ParamFileC*)pParam;
									if( m_rClipboard.m_rFilterClassId != NULL_CLASSID ) {
										// check class id filter
										if( pParamFile->get_class_filter() != m_rClipboard.m_rFilterClassId ) {
											::MessageBox( 0, "Cannot paste keyframes to file parameter\nwith dirrent file type requirements.", "Paste Error", MB_ICONERROR | MB_OK );
											break;
										}
									}
									else if( m_rClipboard.m_rFilterSuperClassId != NULL_SUPERCLASS ) {
										// check super class id filter
										if( pParamFile->get_super_class_filter() != m_rClipboard.m_rFilterSuperClassId ) {
											::MessageBox( 0, "Cannot paste keyframes to file parameter\nwith dirrent file type requirements.", "Paste Error", MB_ICONERROR | MB_OK );
											break;
										}
									}
								}

								ControllerC*	pCont = pParam->get_controller();
								int32			i32Time = GetTimecursor();

								int32			i32TimeOffset = 0;
								TimeSegmentC*	pLayerSegment = pLayer->get_timesegment();
								if( !pLayerSegment )
									return;
								i32Time -= pLayerSegment->get_segment_start();

								TimeSegmentC*	pEffectSegment = pEffect->get_timesegment();
								if( !pEffectSegment )
									return;
								i32Time -= pEffectSegment->get_segment_start();

								m_pKeySelector->deselect_all();

								if( !pCont )
									continue;

								if( !(pParam->get_flags() & ITEM_ANIMATED) ) {
									::MessageBox( 0, "Cannot paste keyframes to non-animated parameter.", "Paste Error", MB_ICONERROR | MB_OK );
									break;
								}

								UndoC*	pUndo = new UndoC( "Paste Keys" );
								UndoC*	pOldUndo = pCont->begin_editing( pUndo );

								for( uint32 j = 0; j < m_rClipboard.m_rKeys.size(); j++ ) {
									KeyC*	pKey = m_rClipboard.m_rKeys[j];
									KeyC*	pNewKey = pCont->add_key_at_time( i32Time + pKey->get_time() );
									pNewKey->copy( pKey );								// this copies time too
									pNewKey->set_time( i32Time + pKey->get_time() );	// set it again
								}

								pCont->prepare();

								pCont->end_editing( pOldUndo );

								m_rUndoManager.push( pUndo );

								SceneItemC	rItem;
								rItem.init( pParam );
								rItem.set_flags( pParam->get_flags() );

								m_pKeySelector->deselect_all();
								m_pKeySelector->update_selected_keys( &rItem );

								SetModifiedFlag();

								bFound = true;

								break;
							}
						}
					}
				}
			}
		}

	}

	UpdateFileReferences();

	NotifyViews( NOTIFY_REDRAW_ALL );
}

void
CDemopajaDoc::NotifyViews( uint32 ui32Notify )
{
	CMainFrame*	pMain = (CMainFrame*)AfxGetMainWnd();
	pMain->NotifyViews( ui32Notify );
}

bool
CDemopajaDoc::CanCopy()
{
	if( m_i32EditFocus == FOCUS_LAYERLIST || m_i32EditFocus == FOCUS_VIEW )
		return true;
	else if( m_i32EditFocus == FOCUS_TIMELINE ) {
		// make sure we have only one selected item and it is parameter
		if( m_pKeySelector->selected_items_count() == 1 ) {
			return true;
		}
		else
			return false;
	}
	return false;
}

bool
CDemopajaDoc::CanPaste()
{
	if( m_i32EditFocus == FOCUS_LAYERLIST && (m_rClipboard.m_eType == CLIPBOARD_EFFECT || m_rClipboard.m_eType == CLIPBOARD_LAYER ) )
		return true;
	else if( m_i32EditFocus == FOCUS_VIEW && m_rClipboard.m_eType == CLIPBOARD_EFFECT )
		return true;
	else if( m_i32EditFocus == FOCUS_TIMELINE ) {
		// a parameter must be selected
		if( m_rClipboard.m_eType == CLIPBOARD_KEYS ) {
			ParamI*	pSelParam = 0;

			bool	bFound = false;
			// go thru selected parameters, and paster key frames
			for( uint32 i = 0; i < m_pMainScene->get_layer_count() && !bFound; i++ ) {
				LayerC*	pLayer = m_pMainScene->get_layer( i );
				if( !pLayer )
					continue;
				for( uint32 j = 0; j < pLayer->get_effect_count() && !bFound; j++ ) {
					EffectI*	pEffect = pLayer->get_effect( j );
					if( !pEffect )
						continue;
					for( uint32 k = 0; k < pEffect->get_gizmo_count() && !bFound; k++ ) {
						GizmoI*	pGizmo = pEffect->get_gizmo( k );
						if( !pGizmo )
							continue;
						for( uint32 p = 0; p < pGizmo->get_parameter_count() && !bFound; p++ ) {
							ParamI*	pParam = pGizmo->get_parameter( p );
							if( !pParam )
								continue;
							if( pParam->get_flags() & ITEM_SELECTED ) {
								pSelParam = pParam;
								bFound = true;
								break;
							}
						}
					}
				}
			}

			if( pSelParam && pSelParam->get_type() == m_rClipboard.m_i32ParamType )
				return true;
		}
		else if( m_rClipboard.m_eType == CLIPBOARD_TIMESEG_KEYS ) {
			// a layer or effect must be selected

			TimeSegmentC*	pSeg = 0;
			int32			i32Time = GetTimecursor();

			SceneItemC		rItem;
			bool			bFound = false;

			// find firstselected time segment
			for( uint32 i = 0; i < m_pMainScene->get_layer_count() && !bFound; i++ ) {
				LayerC*	pLayer = m_pMainScene->get_layer( i );
				if( !pLayer )
					continue;
				if( pLayer->get_flags() & ITEM_SELECTED )
					return true;
				for( uint32 j = 0; j < pLayer->get_effect_count() && !bFound; j++ ) {
					EffectI*	pEffect = pLayer->get_effect( j );
					if( !pEffect )
						continue;
					if( pEffect->get_flags() & ITEM_SELECTED )
						return true;
				}
			}
		}
	}
	return false;
}

void
CDemopajaDoc::UpdateFileReferences()
{
	uint32	i;

	// reset reference count
	for( i = 0; i < m_pFileList->get_file_count(); i++ )
	{
		FileHandleC*	pHandle = m_pFileList->get_file( i );
		// Update file references
		pHandle->reset_references();
		// Update scene references
		ImportableI*	pImp = pHandle->get_importable();
		if( pImp && pImp->get_class_id() == CLASS_SUBSCENE_IMPORTABLE )
		{
			SceneImportC*	pSceneImp = (SceneImportC*)pImp;
			SceneC*	pScene = pSceneImp->get_scene();
			if( pScene )
				pScene->reset_references();
		}
	}

	m_pMainScene->reset_references();

	// update file references
	for( i = 0; i < m_pFileList->get_file_count(); i++ ) {
		ImportableI*	pImp = m_pFileList->get_file( i )->get_importable();
		if( !pImp )
			continue;
		pImp->update_references();
	}

	// update effect references
	m_pMainScene->update_references();
}

const char*
CDemopajaDoc::GetMusicName()
{
	return m_sMusicFile.c_str();
}

void
CDemopajaDoc::SetTool( ToolE eTool )
{
	m_eTool = eTool;
	NotifyViews( NOTIFY_REDRAW_GRAPHICS );
}

uint32
CDemopajaDoc::GetTool()
{
	return m_eTool;
}

void CDemopajaDoc::OnCloseDocument() 
{
	// Delete the contents before the windows are closed.
	DeleteContents();

	// save color swatches
	if( m_rColorSwatches.is_modified() ) {
		int	iRes = ::MessageBox( NULL, "The color swatch set has been changed.\n\nSave changes?", "Color Swatches not saved", MB_ICONQUESTION | MB_YESNO );

		if( iRes == IDYES ) {
			CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();
			char	szFileType[] = "Color Swatches (*.aco)|*.aco|All Files (*.*)|*.*||";
			CFileDialog	rDlg( FALSE, NULL, pApp->GetCurrentDir( CURDIR_COLORSWATCH ), OFN_HIDEREADONLY | OFN_EXPLORER | OFN_OVERWRITEPROMPT, szFileType );

			if( rDlg.DoModal() == IDOK ) {
				m_rColorSwatches.save_swatches( rDlg.GetPathName() );
				pApp->SetCurrentDir( CURDIR_COLORSWATCH, rDlg.GetPathName() );
			}
		}
	}


	CDocument::OnCloseDocument();
}


void CDemopajaDoc::ReleaseMusic()
{
//	TRACE( "release music\n" );

	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	if( !pApp->MusicSystemPresent() )
		return;

	// is here something loaded already?
	if( m_pStream ) {
		FSOUND_Stream_Stop( m_pStream );
		FSOUND_Stream_Close( m_pStream );
		m_pStream = 0;
	}

//	for( uint32 i = 0; i < MUSIC_BAND_COUNT; i++ )
//	{
//		delete [] m_pBandData[i];
//		m_pBandData[i] = 0;
//	}
//	m_i32BandDataLength = 0;

	delete [] m_pMusicData;
	m_pMusicData = 0;
	m_i32MusicDataLength = 0;
}


#define	IF_PRINT( x, y ) if( (x) & (y) ) TRACE( #y " " );


bool CDemopajaDoc::LoadMusic( const char* szFilename )
{
//	TRACE( "load music\n" );

	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	if( !pApp->MusicSystemPresent() )
	{
//		TRACE( "no music sys\n" );
		return true;
	}

	// is here something loaded already?
	if( m_pStream )
		FSOUND_Stream_Close( m_pStream );


	char szFname[_MAX_FNAME];

	m_pStream = FSOUND_Stream_Open( szFilename, FSOUND_NORMAL | FSOUND_LOOP_NORMAL | FSOUND_MPEGACCURATE, 0, 0 );
	if( !m_pStream ) {

		// try to laod it from current dir
		char szExt[_MAX_EXT];
		_splitpath( szFilename, 0, 0, szFname, szExt );

		strcat( szFname, szExt );

		m_pStream = FSOUND_Stream_Open( szFname, FSOUND_NORMAL | FSOUND_LOOP_NORMAL | FSOUND_MPEGACCURATE, 0, 0 );
		
		if( !m_pStream )
			return false;
	}
	else {
		strcpy( szFname, szFilename );
	}

	pApp->DoWaitCursor( 1 );

	// delete old data
	delete [] m_pMusicData;
	m_pMusicData = 0;
	m_i32MusicDataLength = 0;

//	for( uint32 i = 0; i < MUSIC_BAND_COUNT; i++ )
//	{
//		delete [] m_pBandData[i];
//		m_pBandData[i] = 0;
//	}
//	m_i32BandDataLength = 0;


	// Create waveform display data
	FSOUND_SAMPLE*	pSample = FSOUND_Sample_Load( FSOUND_UNMANAGED, szFname, FSOUND_HW2D | FSOUND_NORMAL, 0, 0 );

	if( pSample ) {

//		TRACE( "Sample:\n" );

		int32	i32MusicFreq;
		FSOUND_Sample_GetDefaults( pSample, &i32MusicFreq, 0, 0, 0 );
//		TRACE( " freq: %d\n", i32MusicFreq );

		uint32	ui32Mode = FSOUND_Sample_GetMode( pSample );
//		TRACE( " mode: " );
//		IF_PRINT( ui32Mode, FSOUND_8BITS );
//		IF_PRINT( ui32Mode, FSOUND_16BITS );
//		IF_PRINT( ui32Mode, FSOUND_MONO );
//		IF_PRINT( ui32Mode, FSOUND_STEREO );
//		TRACE( "\n" );

		uint32	ui32Samples = FSOUND_Sample_GetLength( pSample );

		int32	i32LockSize = (int32)ui32Samples;
		int8*	pData;
		int8*	pData2;
		uint32	ui32Length, ui32Length2;

		if( ui32Mode & FSOUND_16BITS )
			i32LockSize *= 2;
		if( ui32Mode & FSOUND_STEREO )
			i32LockSize *= 2;

		uint32	i, j, k;

		if( FSOUND_Sample_Lock( pSample, 0, i32LockSize, (void**)&pData, (void**)&pData2, &ui32Length, &ui32Length2 ) ) {

			if( pData && ui32Length ) {

				// downsample to 8kHz and 8-bit
				m_i32MusicDataLength = (int32)((float64)ui32Samples * MUSIC_DATA_FREQ / (float64)i32MusicFreq) * 2;
				if( ui32Mode & FSOUND_STEREO )
					m_i32MusicDataLength *= 2;
				m_pMusicData = new uint8[m_i32MusicDataLength];

				float32	f32Delta = (float32)i32MusicFreq / MUSIC_DATA_FREQ;
				float32	f32Index = 0;
				float32	f32MaxSize = (float32)ui32Samples;
				uint8*	pDest = m_pMusicData;
				int32	i32DstCount = 0;
				int32	i32Sample;
				uint32	ui32PrevIndex = 0;
				int32	i32Min[2] = { 0, 0 };
				int32	i32Max[2] = { 0, 0 };

				{
					int8*		pSrc8 = 0;
					int16*	pSrc16 = 0;

					if( ui32Mode & FSOUND_8BITS )
						pSrc8 = (int8*)pData;
					else
						pSrc16 = (int16*)pData;

					float32	f32Scale = 1.0f;
					int32		nCh = 1;
					if( ui32Mode & FSOUND_STEREO )
						nCh = 2;

					ui32PrevIndex = 0;

					for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32MusicDataLength; f32Index += f32Delta ) 
					{
						uint32 ui32Idx = (uint32)f32Index;

						i32Max[0] = 0;
						i32Min[0] = 255;
						i32Max[1] = 0;
						i32Min[1] = 255;

						for( j = ui32PrevIndex; j < ui32Idx; j++ ) 
						{
							for( k = 0; k < nCh; k++ )
							{
								if( ui32Mode & FSOUND_8BITS )
									i32Sample = (int32)pSrc8[j * nCh + k] + 0x80;
								else
									i32Sample = (((int32)pSrc16[j * nCh + k] + 0x8000) >> 8);

								i32Min[k] = __min( i32Min[k], i32Sample );
								i32Max[k] = __max( i32Max[k], i32Sample );
							}
						}

						ui32PrevIndex = ui32Idx;

						for( k = 0; k < nCh; k++ )
						{
							pDest[i32DstCount] = (uint8)i32Min[k];
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32Max[k];
							i32DstCount++;
						}
					}
				}


/*
				if( ui32Mode & FSOUND_8BITS ) {
					// 8 bit
					int8*	pSrc = (int8*)pData;

					if( ui32Mode & FSOUND_STEREO ) {
						// stereo

						ui32PrevIndex = 0;

						for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32MusicDataLength; f32Index += f32Delta ) {
							i = (uint32)f32Index;

							i32MaxL = 0; //128;
							i32MinL = 255;//128;
							i32MaxR = 0;//128;
							i32MinR = 255;//128;
							for( j = ui32PrevIndex; j < i; j++ ) {
								i32Sample = (int32)pSrc[j * 2] + 0x80;
								i32MinL = __min( i32MinL, i32Sample );
								i32MaxL = __max( i32MaxL, i32Sample );
								i32Sample = (int32)pSrc[j * 2 + 1] + 0x80;
								i32MinR = __min( i32MinR, i32Sample );
								i32MaxR = __max( i32MaxR, i32Sample );
							}
							ui32PrevIndex = i;

							pDest[i32DstCount] = (uint8)i32MinL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MinR;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxR;
							i32DstCount++;
						}
						m_bMusicDataStereo = true;
					}
					else {
						// mono
						ui32PrevIndex = 0;
						for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32MusicDataLength; f32Index += f32Delta ) {
							i = (uint32)f32Index;
							i32MaxL = 0; //128;
							i32MinL = 255; //128;
							for( j = ui32PrevIndex; j < i; j++ ) {
								i32Sample = (int32)pSrc[j] + 0x80;
								i32MinL = __min( i32MinL, i32Sample );
								i32MaxL = __max( i32MaxL, i32Sample );
							}
							ui32PrevIndex = i;

							pDest[i32DstCount] = (uint8)i32MinL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxL;
							i32DstCount++;
						}
						m_bMusicDataStereo = false;
					}
				}
				else {
					// 16bit
					int16*	pSrc = (int16*)pData;

					if( ui32Mode & FSOUND_STEREO ) {
						// stereo
						ui32PrevIndex = 0;
						for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32MusicDataLength; f32Index += f32Delta ) {
							i = (uint32)f32Index;

							i32MaxL = 0; //128;
							i32MinL = 255; //128;
							i32MaxR = 0; //128;
							i32MinR = 255; //128;
							for( j = ui32PrevIndex; j < i; j++ ) {
								i32Sample = (((int32)pSrc[j * 2] + 0x8000) >> 8);
								i32MinL = __min( i32MinL, i32Sample );
								i32MaxL = __max( i32MaxL, i32Sample );
								i32Sample = (((int32)pSrc[j * 2 + 1] + 0x8000) >> 8);
								i32MinR = __min( i32MinR, i32Sample );
								i32MaxR = __max( i32MaxR, i32Sample );
							}
							ui32PrevIndex = i;

							pDest[i32DstCount] = (uint8)i32MinL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MinR;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxR;
							i32DstCount++;
						}
						m_bMusicDataStereo = true;
					}
					else {
						// mono
						ui32PrevIndex = 0;
						for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32MusicDataLength; f32Index += f32Delta ) {
							i = (uint32)f32Index;

							i32MaxL = 0; //128;
							i32MinL = 255; //128;
							for( j = ui32PrevIndex; j < i; j++ ) {
								i32Sample = (((int32)pSrc[j] + 0x8000) >> 8);
								i32MinL = __min( i32MinL, i32Sample );
								i32MaxL = __max( i32MaxL, i32Sample );
							}
							ui32PrevIndex = i;

							pDest[i32DstCount] = (uint8)i32MinL;
							i32DstCount++;
							pDest[i32DstCount] = (uint8)i32MaxL;
							i32DstCount++;
						}
						m_bMusicDataStereo = false;
					}
				}
*/


				//
				// Calculate bands
				//

				AudioSpectrumC*	pSpec = (AudioSpectrumC*)m_pDeviceContext->query_interface( CLASS_AUDIO_SPECTRUM );
				if( pSpec )
				{
					int8*		pSrc8 = 0;
					int16*	pSrc16 = 0;

					if( ui32Mode & FSOUND_8BITS )
						pSrc8 = (int8*)pData;
					else
						pSrc16 = (int16*)pData;

					float32	f32Scale = 1.0f;
					int32		nCh = 1;
					if( ui32Mode & FSOUND_STEREO )
					{
						nCh = 2;
						f32Scale = 0.5f;
					}

					float32*	pData = new float32[ui32Samples];
					memset( pData, 0, sizeof( float32 ) * ui32Samples );

					for( i = 0; i < ui32Samples; i++ )
					{
						float32	f32Sample = 0.0f;
						for( k = 0; k < nCh; k++ )
						{
							if( ui32Mode & FSOUND_8BITS )
								f32Sample += (float32)pSrc8[i * nCh + k] / 128.0f;
							else
								f32Sample += (float32)pSrc16[i * nCh + k] / 32768.0f;
						}
						pData[i] = f32Sample * f32Scale;
					}

					pSpec->analyze( MUSIC_BAND_COUNT, pData, ui32Samples, i32MusicFreq, m_pTimeContext );

					delete [] pData;
				}

/*
				m_i32BandDataLength = (int32)((float64)ui32Samples * BAND_DATA_FREQ / (float64)i32MusicFreq) * 2;
				for( i = 0; i < MUSIC_BAND_COUNT; i++ )
					m_pBandData[i] = new uint8[m_i32BandDataLength];

				f32Delta = (float32)i32MusicFreq / BAND_DATA_FREQ;
				f32Index = 0;
				f32MaxSize = (float32)ui32Samples;
				
				pDest = m_pMusicData;
				i32DstCount = 0;
				ui32PrevIndex = 0;

				float32	f32Sample;

				int8*		pSrc8 = 0;
				int16*	pSrc16 = 0;

				if( ui32Mode & FSOUND_8BITS )
					pSrc8 = (int8*)pData;
				else
					pSrc16 = (int16*)pData;

				float32	f32Scale = 1.0f;
				int32		nCh = 1;
				if( ui32Mode & FSOUND_STEREO )
				{
					nCh = 2;
					f32Scale = 0.5f;
				}

				// Init filters.
				CAudioFilter	Filters[MUSIC_BAND_COUNT];

				for( i = 0; i < MUSIC_BAND_COUNT; i++ )
				{
					float32	f32Center = (float32)i / (float32)MUSIC_BAND_COUNT;
					f32Center *= f32Center;
					float32	f32CutOff = 40.0f + f32Center * (17000.0f - 40.0f);
					Filters[i].Init( f32CutOff, (float32)i32MusicFreq );
				}

				float32		f32TempBars[MUSIC_BAND_COUNT];

				float32		f32BandValues[MUSIC_BAND_COUNT];
				float32		f32BandNormValues[MUSIC_BAND_COUNT];
				float32		f32BandMin[MUSIC_BAND_COUNT];
				float32		f32BandMax[MUSIC_BAND_COUNT];
				float32		f32FilterTimeBlur = 0.3f;

				for( i = 0; i < MUSIC_BAND_COUNT; i++ )
				{
					f32BandValues[i] = 0.0f;
					f32BandNormValues[i] = 0.0f;
					f32BandMin[i] = 1.0f;
					f32BandMax[i] = 0.0f;
				}

				for( f32Index = 0; f32Index < f32MaxSize && i32DstCount < m_i32BandDataLength; f32Index += f32Delta )
				{
					for( i = 0; i < MUSIC_BAND_COUNT; i++ )
						f32TempBars[i] = 0.0f;

					uint32	ui32Idx = (uint32)f32Index;

					for( j = ui32PrevIndex; j < ui32Idx; j++ )
					{
						f32Sample = 0.0f;
						for( k = 0; k < nCh; k++ )
						{
							if( ui32Mode & FSOUND_8BITS )
								f32Sample += (float32)pSrc8[j * nCh + k] / 128.0f;
							else
								f32Sample += (float32)pSrc16[j * nCh + k] / 32768.0f;
						}
						f32Sample *= f32Scale;

						for( k = 0; k < MUSIC_BAND_COUNT; k++ )
						{
							float32	f32Val = (float32)fabs( Filters[k].Process( f32Sample ) );
							if( f32Val > f32TempBars[k] )
								f32TempBars[k] = f32Val;
						}
					}
					ui32PrevIndex = ui32Idx;

					for( k = 0; k < MUSIC_BAND_COUNT; k++ )
					{
						float32	f32NewVal = (1.0f - f32FilterTimeBlur) * f32BandValues[k] + f32FilterTimeBlur * f32TempBars[k];
						if( f32TempBars[k] > f32NewVal )
							f32NewVal = f32TempBars[k];

						float32	f32Delta = f32NewVal - f32BandValues[k];

						// Decay values towards min/max.
						f32BandMax[k] = f32BandMax[k] * 0.9999f;	// adopt max faster.
						f32BandMin[k] = 1.0f - (1.0f - f32BandMin[k]) * 0.9999f;

						if( f32Delta < 0.0f )
						{
							// Update max
							if( f32NewVal < f32BandMin[k] )
								f32BandMin[k] = f32NewVal;
//								f32BandMin[k] = (1.0f - f32FilterTimeBlur) * f32BandMin[k] + f32FilterTimeBlur * f32NewVal;
						}
						else
						{
							// Update min
							if( f32NewVal > f32BandMax[k] )
								f32BandMax[k] = f32NewVal;
//								f32BandMax[k] = (1.0f - f32FilterTimeBlur) * f32BandMax[k] + f32FilterTimeBlur * f32NewVal;
						}

						if( f32BandMax[k] < 0.0f )
							f32BandMax[k] = 0.0f;
						if( f32BandMax[k] > 1.0f )
							f32BandMax[k] = 1.0f;

						if( f32BandMin[k] < 0.0f )
							f32BandMin[k] = 0.0f;
						if( f32BandMin[k] > 1.0f )
							f32BandMin[k] = 1.0f;
						
						f32BandValues[k] = f32NewVal;

						float32	f32Range = f32BandMax[k] - f32BandMin[k];
						float32	f32NewNormVal = f32NewVal;
						if( f32Range > 0.0001f )
						{
							if( f32NewNormVal < f32BandMin[k] )
								f32NewNormVal = f32BandMin[k];
							else if( f32NewNormVal > f32BandMax[k] )
								f32NewNormVal = f32BandMax[k];
							f32NewNormVal = (f32NewNormVal - f32BandMin[k]) / f32Range;
						}
						if( f32NewNormVal > 1.0f )
							f32NewNormVal = 1.0f;
						f32BandNormValues[k] = f32NewNormVal;

						m_pBandData[k][i32DstCount] = 0;		// min
						m_pBandData[k][i32DstCount + 1] = (uint8)(f32BandNormValues[k] * 255.0f);		// max
//						m_pBandData[k][i32DstCount + 1] = (uint8)(f32BandValues[k] * 255.0f);		// max
					}

					i32DstCount += 2;
				}
*/
			}

			FSOUND_Sample_Unlock( pSample, (void*)pData, (void*)pData2, ui32Length, ui32Length2 );

			if( m_bMusicDataStereo )
				m_i32MusicDataView = MUSIC_VIEW_STEREO;
			else
				m_i32MusicDataView = MUSIC_VIEW_MONO;
		}
		else {
			TRACE( "Could not lock sample\n" );
		}

		FSOUND_Sample_Free( pSample );
	}
	else {
		TRACE( "Failed to load sample: " );
		TRACE( "%s\n", FMOD_ErrorString( FSOUND_GetError() ) );
	}

	pApp->DoWaitCursor( -1 );

	return true;
}

void CDemopajaDoc::PlayMusic( PajaTypes::int32 i32Time )
{
	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	m_f64MusicTimeScale = (float64)GetBeatsPerMin() * (float64)GetQNotesPerBeat() * 256.0 / 60.0;

	if( pApp->MusicSystemPresent() && m_pStream )
	{
		i32Time += m_pCurrentScene->get_music_start_time();
		int32	i32Pos = (int32)((float64)i32Time / m_f64MusicTimeScale * 1000.0);	// Position in ms
		if( i32Pos < 0 )
			i32Pos = 0;
		FSOUND_Stream_SetTime( m_pStream, i32Pos );
		m_i32Channel = FSOUND_Stream_Play( FSOUND_FREE, m_pStream );
		FSOUND_SetPan( m_i32Channel, FSOUND_STEREOPAN );

		if( !m_bPlayMusic )
			FSOUND_SetMute( m_i32Channel, TRUE );
		else
			FSOUND_SetMute( m_i32Channel, FALSE );

		// init music start.
		m_i32MusicTime = (int32)((float64)i32Time * 1000.0 / m_f64MusicTimeScale);

		// catch up at the first tick, and then iterate with normal timer.
		m_bStreamTimeFirstUpdate = true;
	}
	else {
		m_i32MusicTime = (int32)((float64)i32Time * 1000.0 / m_f64MusicTimeScale);
		m_i32MusicStartTime = timeGetTime() - m_i32MusicTime;
	}
}

void CDemopajaDoc::StopMusic()
{
	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	if( pApp->MusicSystemPresent() && m_pStream ) {
		FSOUND_Stream_Stop( m_pStream );
		m_i32Channel = -1;
	}
}

void CDemopajaDoc::SetPosMusic( PajaTypes::int32 i32Time )
{
	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	i32Time += m_pCurrentScene->get_music_start_time();

	m_f64MusicTimeScale = (float64)GetBeatsPerMin() * (float64)GetQNotesPerBeat() * 256 / 60.0;

	if( pApp->MusicSystemPresent() && m_pStream )
	{
		int32	i32Pos = (int32)((float64)i32Time / m_f64MusicTimeScale * 1000.0);	// Position in ms
		if( i32Pos < 0 )
			i32Pos = 0;
		FSOUND_Stream_SetTime( m_pStream, i32Pos );

		// init music start.
		m_i32MusicTime = (int32)((float64)i32Time * 1000.0 / m_f64MusicTimeScale);

		// catch up at the first tick, and then iterate with normal timer.
		m_bStreamTimeFirstUpdate = true;
	}
	else {
		m_i32MusicTime = (int32)((float64)i32Time * 1000.0 / m_f64MusicTimeScale);
		m_i32MusicStartTime = timeGetTime() - m_i32MusicTime;
	}
}

int32 CDemopajaDoc::GetMusicPos()
{
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();

//	float64	f64RenderTimeScale = (float64)GetBeatsPerMin() * (float64)GetQNotesPerBeat() * 256.0 / 60.0;

	if( pApp->MusicSystemPresent() && m_pStream )
	{
/*
		int32	i32Time = (int32)((float64)FSOUND_Stream_GetTime( m_pStream ) / 1000.0 * f64RenderTimeScale);
		i32Time -= m_pCurrentScene->get_music_start_time();
		return i32Time;
*/
		// catch up if necessary.
		if( m_bStreamTimeFirstUpdate )
		{
			// init music start.
			m_i32MusicTime = FSOUND_Stream_GetTime( m_pStream );
			m_i32MusicStartTime = timeGetTime() - m_i32MusicTime;
		}
		else
		{
			int32	i32DiffTime = timeGetTime() - m_i32MusicStartTime;
			m_i32MusicTime = i32DiffTime;
		}
		return (int32)((float64)m_i32MusicTime / 1000.0 * m_f64MusicTimeScale);
	}
	else {
		int32	i32DiffTime = timeGetTime() - m_i32MusicStartTime;
		m_i32MusicTime = i32DiffTime;
		return (int32)((float64)m_i32MusicTime / 1000.0 * m_f64MusicTimeScale);
	}
}


void CDemopajaDoc::InitialiseData( uint32 ui32Reason )
{
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	uint32				i;

	// init importables
	for( i = 0; i < m_pFileList->get_file_count(); i++ ) {
		ImportableI*	pImp = m_pFileList->get_file( i )->get_importable();
		if( !pImp )
			continue;
		pImp->initialize( ui32Reason, m_pDemoInterface );
	}

	// init effects
	m_pMainScene->initialize( ui32Reason, m_pDemoInterface );
}

DeviceFeedbackC* CDemopajaDoc::GetDeviceFeedback()
{
	return &m_rDeviceFeedback;
}

DeviceContextC* CDemopajaDoc::GetDeviceContext()
{
	return m_pDeviceContext;
}

uint8* CDemopajaDoc::GetMusicData()
{
	return m_pMusicData;
}

int32 CDemopajaDoc::GetMusicDataLength()
{
	return m_i32MusicDataLength;
}

int32 CDemopajaDoc::GetMusicDataSamplesPerSecond()
{
	return MUSIC_DATA_FREQ;
}

uint8*
CDemopajaDoc::GetBandData( PajaTypes::uint32 ui32Band )
{
	AudioSpectrumC*	pSpec = (AudioSpectrumC*)m_pDeviceContext->query_interface( CLASS_AUDIO_SPECTRUM );
	if( pSpec )
		return pSpec->get_band_data_ptr( ui32Band );
	return 0;
/*
	if( ui32Band >= 0 && ui32Band < MUSIC_BAND_COUNT )
		return m_pBandData[ui32Band];
	return 0;*/
}

int32
CDemopajaDoc::GetBandDataLength()
{
	AudioSpectrumC*	pSpec = (AudioSpectrumC*)m_pDeviceContext->query_interface( CLASS_AUDIO_SPECTRUM );
	if( pSpec )
		return pSpec->get_band_data_size();
	return 0;
//	return m_i32BandDataLength;
}

int32
CDemopajaDoc::GetBandDataSamplesPerSecond()
{
//	return BAND_DATA_FREQ;
	AudioSpectrumC*	pSpec = (AudioSpectrumC*)m_pDeviceContext->query_interface( CLASS_AUDIO_SPECTRUM );
	if( pSpec )
		return pSpec->get_band_data_freq();
	return 0;
}

bool CDemopajaDoc::GetMusicDataStereo()
{
	return m_bMusicDataStereo;
}

int32 CDemopajaDoc::GetMusicDataView()
{
	return m_i32MusicDataView;
}

void CDemopajaDoc::SetMusicDataView( int32 i32View )
{
	m_i32MusicDataView = i32View;
}

bool CDemopajaDoc::GetMusicShowWaveform() const
{
	return m_bShowWaveform;
}

void CDemopajaDoc::SetMusicShowWaveform( bool bState )
{
	m_bShowWaveform = bState;
}


void CDemopajaDoc::OnPlayLoop() 
{
	m_bPlayLoop = !m_bPlayLoop;
}

void CDemopajaDoc::OnUpdatePlayLoop(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable();
	pCmdUI->SetCheck( m_bPlayLoop ? 1 : 0 );
}

void CDemopajaDoc::OnPlayPlaymusic() 
{
	m_bPlayMusic = !m_bPlayMusic;

	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	if( pApp->MusicSystemPresent() && m_pStream && m_i32Channel != -1 ) {
		if( !m_bPlayMusic )
			FSOUND_SetMute( m_i32Channel, TRUE );
		else
			FSOUND_SetMute( m_i32Channel, FALSE );
	}
}

void CDemopajaDoc::OnUpdatePlayPlaymusic(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable();
	pCmdUI->SetCheck( m_bPlayMusic ? 1 : 0 );
}

bool CDemopajaDoc::GetPlayLoop()
{
	return m_bPlayLoop;
}

void CDemopajaDoc::HandleParamNotify( uint32 ui32Notify )
{
	if( ui32Notify == PARAM_NOTIFY_UI_CHANGE ) {
		if( m_bDeferParamUpdateNotify ) {
			m_ui32DeferParamUpdateNotifyMsg = ui32Notify;
		}
		else {
			// invalidate layer list
			NotifyViews( NOTIFY_LAYERLIST_CHANGED );
			NotifyViews( NOTIFY_FILELIST_CHANGED );
		}
	}
}

void CDemopajaDoc::BeginDeferHandleParamNotify()
{
	m_bDeferParamUpdateNotify = true;
}

void CDemopajaDoc::EndDeferHandleParamNotify()
{
	m_bDeferParamUpdateNotify = false;

	if( m_ui32DeferParamUpdateNotifyMsg == PARAM_NOTIFY_UI_CHANGE ) {
		// invalidate layer list
		NotifyViews( NOTIFY_LAYERLIST_CHANGED );
		NotifyViews( NOTIFY_FILELIST_CHANGED );
	}

	m_ui32DeferParamUpdateNotifyMsg = 0;
}

void CDemopajaDoc::SetupDemoInterface( DemoInterfaceC* pImpIface )
{
	// get folders from registry
	CDemopajaApp*	pApp = (CDemopajaApp*)AfxGetApp();

	// Set project directory
	pImpIface->set_project_path( m_sProjectPath.c_str() );

	// Add common color picker dialog
	ColorCommonDialogC*	pColorDlg = ColorCommonDialogC::create_new();
	pImpIface->add_common_dialog( pColorDlg );

	// Add common importer chooser dialog
	ChooseImporterCommonDialogC*	pChooseDlg = ChooseImporterCommonDialogC::create_new();
	pImpIface->add_common_dialog( pChooseDlg );
}

ColorSwatchSetC* CDemopajaDoc::GetColorSwatchSet()
{
	return &m_rColorSwatches;
}


uint32 CDemopajaDoc::GetFrameID()
{
	return m_pTimeContext->get_frame_id();
}

void CDemopajaDoc::IncFrameID()
{
	m_pTimeContext->set_frame_id( m_pTimeContext->get_frame_id() + 1 );
}



enum RenderCheckTypesE {
	CHECK_ITEM_SCENE,
	CHECK_ITEM_EFFECT,
	CHECK_ITEM_PARAM,
	CHECK_ITEM_FILEHANDLE,
	CHECK_ITEM_RENDER_TO_TEXTURE,
};

struct RenderCheckS {
	uint32				m_ui32Flags;
	std::string			m_sName;
	SceneC*				m_pScene;
};


static bool		check_render_loop( SceneC* pScene, std::vector<RenderCheckS>& rList, int32 i32Indent, bool bRenderToTex );

static
bool
check_file_handle( SceneC* pScene, FileHandleC* pHandle, const std::vector<RenderCheckS>& rList, int32 i32Indent, bool bRenderToTex )
{
	if( !pHandle )
		return false;

	ImportableI*	pImp = pHandle->get_importable();

	if( !pImp )
		return false;

//	TRACE_LOG( "%*s- %s\n", i32Indent * 2, "", pImp->get_filename() );

	if( pImp->get_class_id() == CLASS_SUBSCENE_IMPORTABLE ) {
		// found scene... check recursion

		SceneImportC*	pSceneImp = (SceneImportC*)pImp;
		SceneC*			pSubScene = pSceneImp->get_scene();
		
		if( !pSubScene )
			return false;

		// if the scene already exists in the list, we are recursing.
		for( uint32 k = 0; k < rList.size(); k++ ) {
			if( rList[k].m_ui32Flags == CHECK_ITEM_SCENE && rList[k].m_pScene == pSubScene ) {


				CRecursionErrorDlg	rErrDlg;

				rErrDlg.m_sWarningText = "The file parameter you are about to set would cause infinite loop while rendering the demo.\n\nTo prevent the infinite loop, the file parameter will be set to <empty>.";

				for( uint32 x = 0; x < rList.size(); x++ ) {
					if( rList[x].m_ui32Flags == CHECK_ITEM_SCENE )
						rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_SCENE );
					else if( rList[x].m_ui32Flags == CHECK_ITEM_EFFECT )
						rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_EFFECT );
					else if( rList[x].m_ui32Flags == CHECK_ITEM_PARAM )
						rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_PARAM );
					else if( rList[x].m_ui32Flags == CHECK_ITEM_FILEHANDLE )
						rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_FILEHANDLE );
					else if( rList[x].m_ui32Flags == CHECK_ITEM_RENDER_TO_TEXTURE )
						rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_RENDER_TO_TEXTURE );
				}

				rErrDlg.AddErrorItem( pSubScene->get_name(), RECERR_ERROR );

				rErrDlg.DoModal();

				return true;
			}

		}

		// duplicate the stack, and recurse some more.
		std::vector<RenderCheckS>	rSubList = rList;

		RenderCheckS	rItem;
		rItem.m_ui32Flags = CHECK_ITEM_SCENE;
		rItem.m_pScene = pSubScene;
		rItem.m_sName = pSubScene->get_name();
		rSubList.push_back( rItem );

		if( check_render_loop( pSubScene, rSubList, i32Indent + 1, bRenderToTex ) )
			return true;

		return false;
	}
	else if( pImp->get_class_id() == CLASS_SCENETOIMAGE_IMPORTABLE ) {

		if( bRenderToTex ) {
			// if we are already rendering to texture and new render to texture would be started, bail out.
			CRecursionErrorDlg	rErrDlg;

			rErrDlg.m_sWarningText = "The system cannot use two \"Scene to Image\" files inside each other, file parameter will be set to <empty>.";

			for( uint32 x = 0; x < rList.size(); x++ ) {
				if( rList[x].m_ui32Flags == CHECK_ITEM_SCENE )
					rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_SCENE );
				else if( rList[x].m_ui32Flags == CHECK_ITEM_EFFECT )
					rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_EFFECT );
				else if( rList[x].m_ui32Flags == CHECK_ITEM_PARAM )
					rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_PARAM );
				else if( rList[x].m_ui32Flags == CHECK_ITEM_FILEHANDLE )
					rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_FILEHANDLE );
				else if( rList[x].m_ui32Flags == CHECK_ITEM_RENDER_TO_TEXTURE )
					rErrDlg.AddErrorItem( rList[x].m_sName.c_str(), RECERR_RENDER_TO_TEXTURE );
			}

			rErrDlg.AddErrorItem( pImp->get_filename(), RECERR_ERROR );

			rErrDlg.DoModal();

			return true;
		}

		// we are rendering to texture, set the flag.
		bRenderToTex = true;
	}

	// duplicate the stack, and recurse some more.
	std::vector<RenderCheckS>	rSubList = rList;

	RenderCheckS	rItem;
	if( bRenderToTex )
		rItem.m_ui32Flags = CHECK_ITEM_RENDER_TO_TEXTURE;
	else
		rItem.m_ui32Flags = CHECK_ITEM_FILEHANDLE;
	rItem.m_pScene = 0;
	rItem.m_sName = pImp->get_filename();
	rSubList.push_back( rItem );

	for( uint32 i = 0; i < pImp->get_reference_file_count(); i++ ) {
		FileHandleC*	pRefHandle = pImp->get_reference_file( i );
		if( check_file_handle( pScene, pRefHandle, rSubList, i32Indent + 1, bRenderToTex ) )
			return true;
	}

	return false;
}

static
bool
check_effect( SceneC* pScene, EffectI* pEffect, const std::vector<RenderCheckS>& rList, int32 i32Indent, bool bRenderToTex )
{
	// duplicate the stack, and recurse some more.
	std::vector<RenderCheckS>	rSubList = rList;

	RenderCheckS	rItem;
	rItem.m_ui32Flags = CHECK_ITEM_EFFECT;
	rItem.m_pScene = 0;
	rItem.m_sName = pEffect->get_name();
	rSubList.push_back( rItem );

	for( uint32 i = 0; i < pEffect->get_gizmo_count(); i++ ) {
		GizmoI*	pGizmo = pEffect->get_gizmo( i );

//		TRACE( "%*s- %s\n", i32Indent * 2, "", pGizmo->get_name() );

		for( uint32 j = 0; j < pGizmo->get_parameter_count(); j++ ) {
			ParamI*	pParam = pGizmo->get_parameter( j );
			if( pParam->get_type() == PARAM_TYPE_FILE ) {

				ParamFileC*	pParamFile = (ParamFileC*)pParam;

//				TRACE( "%*s- %s\n", (i32Indent + 1) * 2, "", pParamFile->get_name() );
				
				if( pParamFile->get_flags() & ITEM_ANIMATED ) {
					ControllerC*	pCont = pParamFile->get_controller();
					if( pCont && pCont->get_key_count() ) {
						for( uint32 k = 0; k < pCont->get_key_count(); k++ ) {
							FileKeyC*	pKey = (FileKeyC*)pCont->get_key( k );

							std::vector<RenderCheckS>	rParamSubList = rSubList;
							char	szName[256];
							_snprintf( szName, 255, "%s/%s (key %d)", pGizmo->get_name(), pParamFile->get_name(), k );
							RenderCheckS	rItem;
							rItem.m_ui32Flags = CHECK_ITEM_PARAM;
							rItem.m_pScene = 0;
							rItem.m_sName = szName;
							rParamSubList.push_back( rItem );

//							TRACE( "%*s- key %d\n", (i32Indent + 1) * 2, "", k );

							if( check_file_handle( pScene, pKey->get_file_handle(), rParamSubList, i32Indent + 3, bRenderToTex ) )
								return true;
						}
					}
					else {
						std::vector<RenderCheckS>	rParamSubList = rSubList;
						char	szName[256];
						_snprintf( szName, 255, "%s/%s", pGizmo->get_name(), pParamFile->get_name() );
						RenderCheckS	rItem;
						rItem.m_ui32Flags = CHECK_ITEM_PARAM;
						rItem.m_pScene = 0;
						rItem.m_sName = szName;
						rParamSubList.push_back( rItem );

						if( check_file_handle( pScene, pParamFile->get_file( 0 ), rParamSubList, i32Indent + 2, bRenderToTex ) )
							return true;
					}
				}
				else {
					std::vector<RenderCheckS>	rParamSubList = rSubList;
					char	szName[256];
					_snprintf( szName, 255, "%s/%s", pGizmo->get_name(), pParamFile->get_name() );
					RenderCheckS	rItem;
					rItem.m_ui32Flags = CHECK_ITEM_PARAM;
					rItem.m_pScene = 0;
					rItem.m_sName = szName;
					rParamSubList.push_back( rItem );

					if( check_file_handle( pScene, pParamFile->get_file( 0 ), rParamSubList, i32Indent + 2, bRenderToTex ) )
						return true;
				}
			}
		}
	}

	return false;
}

static
bool
check_render_loop( SceneC* pScene, std::vector<RenderCheckS>& rList, int32 i32Indent, bool bRenderToTex )
{

	RenderCheckS	rItem;

	for( uint32 i = 0; i < pScene->get_layer_count(); i++ ) {
		LayerC*	pLayer = pScene->get_layer( i );
		for( uint32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pEffect = pLayer->get_effect( j );

//			TRACE( "%*s- %s\n", i32Indent * 2, "", pEffect->get_name() );

			if( check_effect( pScene, pEffect, rList, i32Indent + 1, bRenderToTex ) )
				return true;
		}
	}
	return false;
}


bool CDemopajaDoc::CheckFileRecursion()
{

	uint32	ui32SceneCount = GetSceneCount();

//	TRACE_LOG( "Checking recursion:\n" );

	// "render" each scene.
	for( uint32 i = 0; i < ui32SceneCount; i++ ) {
		SceneC*	pScene = GetScene( i );

		std::vector<RenderCheckS>	rList;

		RenderCheckS	rItem;
		rItem.m_ui32Flags = CHECK_ITEM_SCENE;
		rItem.m_pScene = pScene;
		rItem.m_sName = pScene->get_name();
		rList.push_back( rItem );

//		TRACE( "%*s- %s\n", rList.size() * 2, "", pScene->get_name() );

		if( check_render_loop( pScene, rList, 1, false ) )
			return true;
	}

	return false;
}

int32
CDemopajaDoc::GetTimeFromString( const char* szStr, uint32 ui32Format )
{
	char*	szToken;
	char	szSep[] = ":";

	int32	i32Times[4];
	int32	i32TimeCount = 0;

	szToken = strtok( (char*)szStr, szSep );
	while( szToken ) {
		i32Times[i32TimeCount] = atoi( szToken );
		i32TimeCount++;
		if( i32TimeCount > 3 )
			break;
		szToken = strtok( 0, szSep );
	}

	//
	// note: 0x1555555 = 0x7FFFFFFF / 60
	//

	int32	i32Time = 0;

	if( ui32Format == TIME_FORMAT_HOUR_MIN_SEC ) {

		int32	i32TimeScale = GetBeatsPerMin() * GetQNotesPerBeat() * 256;

		// HOUR : MIN : SEC
		switch( i32TimeCount ) {
		case 1:
			{
				// secs
				int32	i32Secs = i32Times[0];

				float64	f64Check = ((float64)i32Secs * (float64)i32TimeScale) / 60.0;
				if( f64Check > (float64)0x1555555 ) {
					i32Time = 0x1555555;
				}
				else if( f64Check < -(float64)0x1555555 ) {
					i32Time = -(int32)0x1555555;
				}
				else {
					i32Time = (i32Secs * i32TimeScale) / 60;
				}
			}
			break;
		case 2:
			{
				// secs, and mins
				int32	i32Mins = i32Times[0];
				int32	i32Secs = i32Times[1];

				float64	f64Check = (((float64)i32Mins * 60.0 + (float64)i32Secs) * (float64)i32TimeScale) / 60.0;
				if( f64Check > (float64)0x1555555 ) {
					i32Time = 0x1555555;
				}
				else if( f64Check < -(float64)0x1555555 ) {
					i32Time = -(int32)0x1555555;
				}
				else {
					i32Time = ((i32Mins * 60 + i32Secs) * i32TimeScale) / 60;
				}
			}
			break;

		case 3:
			{
				// secs, mins, and hours
				int32	i32Hours = i32Times[0];
				int32	i32Mins = i32Times[1];
				int32	i32Secs = i32Times[2];

				float64	f64Check = (((float64)i32Hours * 3600.0 + (float64)i32Mins * 60.0 + (float64)i32Secs) * (float64)i32TimeScale) / 60.0;
				if( f64Check > (float64)0x1555555 ) {
					i32Time = 0x1555555;
				}
				else if( f64Check < -(float64)0x1555555 ) {
					i32Time = -(int32)0x1555555;
				}
				else {
					i32Time = ((i32Hours * 3600 + i32Mins * 60 + i32Secs) * i32TimeScale) / 60;
				}

			}
			break;
		}
	}
	else {
		// HOUR : MIN : SEC : FRAME

		int32	i32TimeScale = GetBeatsPerMin() * GetQNotesPerBeat() * GetEditAccuracy();

		switch( i32TimeCount ) {
		case 1:
			{
				// frames
				int32	i32Frames = i32Times[0];
				int32	i32Time;

				float64	f64Check = (float64)i32Frames;
				if( f64Check > (float64)0x1555555 ) {
					i32Time = 0x1555555;
				}
				else if( f64Check < -(float64)0x1555555 ) {
					i32Time = -(int32)0x1555555;
				}
				else {
					i32Time = i32Frames;
				}
			}
			break;
		case 2:
			{
				// frames, and secs
				int32	i32Secs = i32Times[0];
				int32	i32Frames = i32Times[1];

				float64	f64Check = (float64)((i32Secs * i32TimeScale) / 60 + i32Frames);
				if( f64Check > (float64)0x1555555 ) {
					i32Time = 0x1555555;
				}
				else if( f64Check < -(float64)0x1555555 ) {
					i32Time = -(int32)0x1555555;
				}
				else {
					i32Time = ((i32Secs * i32TimeScale) / 60 + i32Frames);
				}
			}
			break;
		case 3:
			{
				// frames, secs, and mins
				int32	i32Mins = i32Times[0];
				int32	i32Secs = i32Times[1];
				int32	i32Frames = i32Times[2];

				float64	f64Check = (float64)( ((i32Mins * 60 + i32Secs) * i32TimeScale) / 60 + i32Frames );
				if( f64Check > (float64)0x1555555 ) {
					i32Time = 0x1555555;
				}
				else if( f64Check < -(float64)0x1555555 ) {
					i32Time = -(int32)0x1555555;
				}
				else {
					i32Time = (((i32Mins * 60 + i32Secs) * i32TimeScale) / 60 + i32Frames);
				}
			}
			break;

		case 4:
			{
				// frames, secs, mins, and hours
				int32	i32Hours = i32Times[0];
				int32	i32Mins = i32Times[1];
				int32	i32Secs = i32Times[2];
				int32	i32Frames = i32Times[3];

				float64	f64Check = (float64)( (((i32Hours * 3600 + i32Mins * 60 + i32Secs) * i32TimeScale) / 60) + i32Frames );
				if( f64Check > (float64)0x1555555 ) {
					i32Time = 0x1555555;
				}
				else if( f64Check < -(float64)0x1555555 ) {
					i32Time = -(int32)0x1555555;
				}
				else {
					i32Time = (((i32Hours * 3600 + i32Mins * 60 + i32Secs) * i32TimeScale) / 60 + i32Frames);
				}

			}
			break;
		}

		i32Time *= GetFrameSizeInTicks();
	}

	return i32Time;
}

void
CDemopajaDoc::AddFlagsAll( int32 i32ModMask, int32 i32Flags )
{
	if( !GetCurrentScene() )
		return;

	for( uint32 i = 0; i < GetCurrentScene()->get_layer_count(); i++ ) {
		LayerC*	pLayer = GetCurrentScene()->get_layer( i );
		if( i32ModMask & ITEM_LAYER )
			pLayer->add_flags( i32Flags );

		for( uint32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pFx = pLayer->get_effect( j );
			if( i32ModMask & ITEM_EFFECT )
				pFx->add_flags( i32Flags );

			for( uint32 k = 0; k < pFx->get_gizmo_count(); k++ ) {
				GizmoI*	pGizmo = pFx->get_gizmo( k );
				if( i32ModMask & ITEM_GIZMO )
					pGizmo->add_flags( i32Flags );

				for( uint32 m = 0; m < pGizmo->get_parameter_count(); m++ ) {
					ParamI*	pParam = pGizmo->get_parameter( m );
					if( i32ModMask & ITEM_PARAMETER )
						pParam->add_flags( i32Flags );
				}
			}
		}
	}
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

void
CDemopajaDoc::DelFlagsAll( int32 i32ModMask, int32 i32Flags )
{
	if( !GetCurrentScene() )
		return;

	for( uint32 i = 0; i < GetCurrentScene()->get_layer_count(); i++ ) {
		LayerC*	pLayer = GetCurrentScene()->get_layer( i );
		if( i32ModMask & ITEM_LAYER )
			pLayer->del_flags( i32Flags );

		for( uint32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pFx = pLayer->get_effect( j );
			if( i32ModMask & ITEM_EFFECT )
				pFx->del_flags( i32Flags );

			for( uint32 k = 0; k < pFx->get_gizmo_count(); k++ ) {
				GizmoI*	pGizmo = pFx->get_gizmo( k );
				if( i32ModMask & ITEM_GIZMO )
					pGizmo->del_flags( i32Flags );

				for( uint32 m = 0; m < pGizmo->get_parameter_count(); m++ ) {
					ParamI*	pParam = pGizmo->get_parameter( m );
					if( i32ModMask & ITEM_PARAMETER )
						pParam->del_flags( i32Flags );
				}
			}
		}
	}
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

void
CDemopajaDoc::ToggleFlagsAll( int32 i32ModMask, int32 i32Flags )
{
	if( !GetCurrentScene() )
		return;

	for( uint32 i = 0; i < GetCurrentScene()->get_layer_count(); i++ ) {
		LayerC*	pLayer = GetCurrentScene()->get_layer( i );
		if( i32ModMask & ITEM_LAYER )
			pLayer->toggle_flags( i32Flags );

		for( uint32 j = 0; j < pLayer->get_effect_count(); j++ ) {
			EffectI*	pFx = pLayer->get_effect( j );
			if( i32ModMask & ITEM_EFFECT )
				pFx->toggle_flags( i32Flags );

			for( uint32 k = 0; k < pFx->get_gizmo_count(); k++ ) {
				GizmoI*	pGizmo = pFx->get_gizmo( k );
				if( i32ModMask & ITEM_GIZMO )
					pGizmo->toggle_flags( i32Flags );

				for( uint32 m = 0; m < pGizmo->get_parameter_count(); m++ ) {
					ParamI*	pParam = pGizmo->get_parameter( m );
					if( i32ModMask & ITEM_PARAMETER )
						pParam->toggle_flags( i32Flags );
				}
			}
		}
	}
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

//
// general editing
//

void
CDemopajaDoc::ApplyChanges()
{
	ASSERT( m_pCurrentUndo != 0 );
	
	m_rUndoManager.push( m_pCurrentUndo );
	m_pCurrentUndo = 0;

	SetModifiedFlag();
}

void
CDemopajaDoc::CancelChanges()
{
	delete m_pCurrentUndo;
	m_pCurrentUndo = 0;
}

//
// Keys
//

void
CDemopajaDoc::KeysDeleteSelected()
{
	UndoC*	pUndo = new UndoC( "Delete Keys" );
	UndoC*	pOldUndo = m_pKeySelector->begin_editing( pUndo );

	m_pKeySelector->delete_selected();

	m_pKeySelector->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );
	SetModifiedFlag();
}

void
CDemopajaDoc::KeysDeselectAll()
{
	if( m_pKeySelector->selected_items_count() )
		m_pKeySelector->deselect_all();
}


void
CDemopajaDoc::KeysSaveStateSelected( const char* szAction )
{
	ASSERT( m_pCurrentUndo == 0 );

	m_pCurrentUndo = new UndoC( szAction );
	UndoC*	pOldUndo;

	// Save state to undo
	pOldUndo = m_pKeySelector->begin_editing( m_pCurrentUndo );
	m_pKeySelector->save_state_selected();
	m_pKeySelector->end_editing( pOldUndo );
}

void
CDemopajaDoc::KeysSaveStateItem( SceneItemC* pItem, const char* szAction )
{
	ASSERT( m_pCurrentUndo == 0 );

	m_pCurrentUndo = new UndoC( szAction );

	// Save state to undo
	if( pItem->get_type() == SCENEITEM_LAYER || pItem->get_type() == SCENEITEM_EFFECT ) {

		TimeSegmentC*	pSeg;

		if( pItem->get_type() == SCENEITEM_LAYER ) {
			LayerC*			pLayer = pItem->get_layer();
			if( !pLayer )
				return;
			pSeg = pLayer->get_timesegment();
		}
		else if( pItem->get_type() == SCENEITEM_EFFECT ) {
			EffectI*			pEffect = pItem->get_effect();
			if( !pEffect )
				return;
			pSeg = pEffect->get_timesegment();
		}

		UndoC*	pOldUndo = pSeg->begin_editing( m_pCurrentUndo );

		for( uint32 i = 0; i < pSeg->get_key_count(); i++ ) {
			KeyC*	pKey = pSeg->get_key( i );
			if( pKey->get_flags() & KEY_SELECTED ) {
				UndoC*	pOldKeyUndo = pKey->begin_editing( m_pCurrentUndo );
				pKey->end_editing( pOldKeyUndo );
			}
		}

		pSeg->end_editing( pOldUndo );
	}
	else if( pItem->get_type() == SCENEITEM_PARAMETER ) {
		ControllerC*	pCont = pItem->get_parameter()->get_controller();

		UndoC*	pOldUndo = pCont->begin_editing( m_pCurrentUndo );

		// Save all keys. Calling "prepare" will change their values too.
		for( uint32 i = 0; i < pCont->get_key_count(); i++ ) {
			KeyC*	pKey = pCont->get_key( i );
			UndoC*	pOldKeyUndo = pKey->begin_editing( m_pCurrentUndo );
			pKey->end_editing( pOldKeyUndo );
		}

		pCont->end_editing( pOldUndo );

		// Save key positions
		if( pCont->get_type() != CONT_TYPE_FILE ) {
			m_rKeyStates.resize( pCont->get_key_count() );
			for( uint32 i = 0; i < pCont->get_key_count(); i++ ) {
				FloatKeyC*	pKey = (FloatKeyC*)pCont->get_key( i );
				m_rKeyStates[i].m_pKey = pKey;
				pKey->get_value( m_rKeyStates[i].m_f32Values );
			}
		}
	}

}

void
CDemopajaDoc::KeysMoveTime( PajaTypes::int32 i32DeltaTime )
{
	m_pKeySelector->move_keys( i32DeltaTime );
}

void
CDemopajaDoc::KeysMoveValue( SceneItemC* pItem, int32 i32Channel, float32 f32DeltaValue )
{
	ParamI*			pParam = pItem->get_parameter();

	if( pParam ) {
		ControllerC*	pCont = pParam->get_controller();

		if( pCont->get_type() == CONT_TYPE_FILE )
			return;

		float32	f32ClampMin[KEY_MAXCHANNELS], f32ClampMax[KEY_MAXCHANNELS];
		bool	bClampValues = pParam->get_min_max( f32ClampMin, f32ClampMax );

		// move keys
		for( uint32 i = 0; i < pCont->get_key_count(); i++ ) {
			FloatKeyC*	pKey = (FloatKeyC*)pCont->get_key( i );
			if( pKey->get_flags() & KEY_SELECTED ) {
				// move keys in increments defined in the parameter.
				float32	f32Val;

				ASSERT( m_rKeyStates[i].m_pKey == pKey );

				f32Val = m_rKeyStates[i].m_f32Values[i32Channel] + f32DeltaValue;

				// Snap values for integer parameters
				int32	i32Val = (int32)(f32Val / pParam->get_increment());
				f32Val = (float32)i32Val * pParam->get_increment();

				if( bClampValues ) {
					if( f32Val < f32ClampMin[i32Channel] )
						f32Val = f32ClampMin[i32Channel];
					if( f32Val > f32ClampMax[i32Channel] )
						f32Val = f32ClampMax[i32Channel];
				}

				float32	f32Value[KEY_MAXCHANNELS];
				pKey->get_value( f32Value );
				f32Value[i32Channel] = f32Val;
				pKey->set_value( f32Value );
			}
		}

		// update smooth data
		pCont->prepare();
	}
}

void
CDemopajaDoc::KeysDeselectKey( SceneItemC* pItem, int32 i32Time )
{
	m_pKeySelector->deselect_key( pItem, i32Time );
}

void
CDemopajaDoc::KeysSelectKey( SceneItemC* pItem, int32 i32Time )
{
	m_pKeySelector->select_key( pItem, i32Time );
}

bool
CDemopajaDoc::KeysSelectInRange( SceneItemC* pItem, int32 i32StartTime, int32 i32EndTime )
{
	return m_pKeySelector->select_keys_in_range( pItem, i32StartTime, i32EndTime );
}

void
CDemopajaDoc::KeysSelectAll( SceneItemC* pItem )
{
	m_pKeySelector->select_all_keys( pItem );
}

void
CDemopajaDoc::KeysSetInterpolation( uint32 ui32Type )
{
	UndoC*	pUndo = 0;
	
	if( ui32Type == KEY_SMOOTH ) {
		pUndo = new UndoC( "Set Smooth Interpolation" );
	}
	else if( ui32Type == KEY_LINEAR ) {
		pUndo = new UndoC( "Set Linear Interpolation" );
	}
	else if( ui32Type == KEY_HOLD ) {
		pUndo = new UndoC( "Set Hold Interpolation" );
	}
	else {
		pUndo = new UndoC( "Set Interpolation" );
	}

	UndoC*	pOldUndo = m_pKeySelector->begin_editing( pUndo );
	m_pKeySelector->set_interpolation( ui32Type );
	m_pKeySelector->end_editing( pOldUndo );

	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
}

void
CDemopajaDoc::KeysSetInterpolationProperty( uint32 ui32Type, float32 f32Val )
{
	UndoC*	pUndo = 0;

	if( ui32Type == PROPERTY_TENS )
		pUndo = new UndoC( "Set Ease Tension" );
	else if( ui32Type == PROPERTY_CONT )
		pUndo = new UndoC( "Set Ease Continuity" );
	else if( ui32Type == PROPERTY_BIAS )
		pUndo = new UndoC( "Set Ease Bias" );
	else if( ui32Type == PROPERTY_EASE_IN )
		pUndo = new UndoC( "Set Ease Ease In" );
	else if( ui32Type == PROPERTY_EASE_OUT )
		pUndo = new UndoC( "Set Ease Ease Out" );
	else
		pUndo = new UndoC( "Set Property" );

	UndoC*	pOldUndo = m_pKeySelector->begin_editing( pUndo );
	m_pKeySelector->set_interpolation_property( ui32Type, f32Val );
	m_pKeySelector->end_editing( pOldUndo );

	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
}

void
CDemopajaDoc::KeysGatherCommonValues( CommonKeyValuesS* pCommon )
{
	m_pKeySelector->gather_common_values( pCommon );
}


//
// Controller
//

void
CDemopajaDoc::ControllerAddKeyAtTime( ControllerC* pCont, int32 i32Time )
{
	UndoC*	pUndo = new UndoC( "Create Key" );
	UndoC*	pOldUndo = pCont->begin_editing( pUndo );
	KeyC*	pNewKey = pCont->add_key_at_time( i32Time );
	pCont->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );
	SetModifiedFlag();
}

void
CDemopajaDoc::ControllerSetStartOutOfRange( ControllerC* pCont, uint32 ui32ORT )
{
	// set out of range type
	UndoC*	pUndo = new UndoC( "Set Start Out of Range" );
	UndoC*	pOldUndo = pCont->begin_editing( pUndo );
	// save key state
	for( uint32 i = 0; i < pCont->get_key_count(); i++ ) {
		KeyC*	pKey = pCont->get_key( i );
		UndoC*	pOldKeyUndo = pKey->begin_editing( pUndo );
		pKey->end_editing( pOldKeyUndo );
	}

	pCont->set_start_ort( ui32ORT );
	pCont->prepare();

	pCont->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
}

void
CDemopajaDoc::ControllerSetEndOutOfRange( ControllerC* pCont, uint32 ui32ORT )
{
	// set out of range type
	UndoC*	pUndo = new UndoC( "Set End Out of Range" );
	UndoC*	pOldUndo = pCont->begin_editing( pUndo );
	// save key state
	for( uint32 i = 0; i < pCont->get_key_count(); i++ ) {
		KeyC*	pKey = pCont->get_key( i );
		UndoC*	pOldKeyUndo = pKey->begin_editing( pUndo );
		pKey->end_editing( pOldKeyUndo );
	}

	pCont->set_end_ort( ui32ORT );
	pCont->prepare();

	pCont->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
}


//
// Parameter
//
void
CDemopajaDoc::ParameterToggleAnim( ParamI* pParam, int32 i32Time )
{
	UndoC*			pUndo = new UndoC( "Change Animation" );
	UndoC*			pOldUndo;
	ControllerC*	pCont = pParam->get_controller();

	KeysDeselectAll();

	//parameter
	pOldUndo = pParam->begin_editing( pUndo );

	if( pParam->get_flags() & ITEM_ANIMATED ) {
		// toggle animation off
		if( pParam->get_flags() & ITEM_EXPANDABLE ) {
			// if the parameter is expandable, dont contract the parameter
			pParam->del_flags( ITEM_ANIMATED );
		}
		else {
			pParam->del_flags( ITEM_ANIMATED | ITEM_EXPANDED );
		}
		// delete all keys from controller
		pOldUndo = pCont->begin_editing( pUndo );
		for( int32 i = pCont->get_key_count() - 1; i >= 0 ; i-- )
			pCont->del_key( i );
		pCont->end_editing( pOldUndo );
	}
	else {
		// toggle animation on
		pParam->add_flags( ITEM_ANIMATED );
		// create key at the timecursor time.
		HandleParamNotify( ParameterCreateValue( pParam, i32Time ) );
	}

	pParam->end_editing( pOldUndo );

	m_rUndoManager.push( pUndo );
	SetModifiedFlag();

	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

float32
CDemopajaDoc::ParameterGetValue( ParamI* pParam, int32 i32Time, uint32 ui32Channel )
{
	i32Time -= ParameterGetTimeOffset( pParam );

	switch( pParam->get_type() ) {
	case PARAM_TYPE_INT:
		{
			ParamIntC*	pParamInt = (ParamIntC*)pParam;
			int32		i32Val;
			pParamInt->get_val( i32Time, i32Val );
			return (float32)i32Val;
		}
		break;
	case PARAM_TYPE_FLOAT:
		{
			ParamFloatC*	pParamFloat = (ParamFloatC*)pParam;
			float32			f32Val;
			pParamFloat->get_val( i32Time, f32Val );
			return f32Val;
		}
		break;
	case PARAM_TYPE_VECTOR2:
		{
			ParamVector2C*	pParamVec2 = (ParamVector2C*)pParam;
			Vector2C		rVal;
			pParamVec2->get_val( i32Time, rVal );
			ASSERT( ui32Channel <= 1 );
			return rVal[ui32Channel];
		}
		break;
	case PARAM_TYPE_VECTOR3:
		{
			ParamVector3C*	pParamVec3 = (ParamVector3C*)pParam;
			Vector3C		rVal;
			pParamVec3->get_val( i32Time, rVal );
			ASSERT( ui32Channel <= 2 );
			return rVal[ui32Channel];
		}
		break;
	case PARAM_TYPE_COLOR:
		{
			ParamColorC*	pParamColor = (ParamColorC*)pParam;
			ColorC			rVal;
			pParamColor->get_val( i32Time, rVal );
			ASSERT( ui32Channel <= 3 );
			return rVal[(int32)ui32Channel];
		}
		break;
	}

	return 0;
}

int32
CDemopajaDoc::ParameterSetValue( ParamI* pParam, int32 i32Time, uint32 ui32Channel, float32 f32Val )
{
	i32Time -= ParameterGetTimeOffset( pParam );

	switch( pParam->get_type() ) {
	case PARAM_TYPE_INT:
		{
			ParamIntC*	pParamInt = (ParamIntC*)pParam;
			int32		i32Val = (int32)f32Val;
			return pParamInt->set_val( i32Time, i32Val );
		}
		break;
	case PARAM_TYPE_FLOAT:
		{
			ParamFloatC*	pParamFloat = (ParamFloatC*)pParam;
			return pParamFloat->set_val( i32Time, f32Val );
		}
		break;
	case PARAM_TYPE_VECTOR2:
		{
			ParamVector2C*	pParamVec2 = (ParamVector2C*)pParam;
			Vector2C		rVal;
			pParamVec2->get_val( i32Time, rVal );
			ASSERT( ui32Channel <= 1 );
			rVal[ui32Channel] = f32Val;
			return pParamVec2->set_val( i32Time, rVal );
		}
		break;
	case PARAM_TYPE_VECTOR3:
		{
			ParamVector3C*	pParamVec3 = (ParamVector3C*)pParam;
			Vector3C		rVal;
			pParamVec3->get_val( i32Time, rVal );
			ASSERT( ui32Channel <= 2 );
			rVal[ui32Channel] = f32Val;
			return pParamVec3->set_val( i32Time, rVal );
		}
		break;
	case PARAM_TYPE_COLOR:
		{
			ParamColorC*	pParamColor = (ParamColorC*)pParam;
			ColorC			rVal;
			pParamColor->get_val( i32Time, rVal );
			ASSERT( ui32Channel <= 3 );
			rVal[(int32)ui32Channel] = f32Val;
			return pParamColor->set_val( i32Time, rVal );
		}
		break;
	}
	return 0;
}

int32
CDemopajaDoc::ParameterCreateValue( ParamI* pParam, int32 i32Time )
{
	i32Time -= ParameterGetTimeOffset( pParam );

	switch( pParam->get_type() ) {
	case PARAM_TYPE_INT:
		{
			ParamIntC*	pParamInt = (ParamIntC*)pParam;
			int32		i32Val;
			pParamInt->get_val( i32Time, i32Val );
			return pParamInt->set_val( i32Time, i32Val );
		}
		break;
	case PARAM_TYPE_FLOAT:
		{
			ParamFloatC*	pParamFloat = (ParamFloatC*)pParam;
			float32	f32Val;
			pParamFloat->get_val( i32Time, f32Val );
			return pParamFloat->set_val( i32Time, f32Val );
		}
		break;
	case PARAM_TYPE_VECTOR2:
		{
			ParamVector2C*	pParamVec2 = (ParamVector2C*)pParam;
			Vector2C		rVal;
			pParamVec2->get_val( i32Time, rVal );
			return pParamVec2->set_val( i32Time, rVal );
		}
		break;
	case PARAM_TYPE_VECTOR3:
		{
			ParamVector3C*	pParamVec3 = (ParamVector3C*)pParam;
			Vector3C		rVal;
			pParamVec3->get_val( i32Time, rVal );
			return pParamVec3->set_val( i32Time, rVal );
		}
		break;
	case PARAM_TYPE_COLOR:
		{
			ParamColorC*	pParamColor = (ParamColorC*)pParam;
			ColorC			rVal;
			pParamColor->get_val( i32Time, rVal );
			return pParamColor->set_val( i32Time, rVal );
		}
		break;
	case PARAM_TYPE_FILE:
		{
			ParamFileC*	pParamFile = (ParamFileC*)pParam;
			FileHandleC*	pHandle = pParamFile->get_file( i32Time );
			return pParamFile->set_file( i32Time, pHandle );
		}
		break;
	}
	return 0;
}

void
CDemopajaDoc::ParameterShow( ParamI* pParam, bool bShow )
{
	UndoC*	pUndo = 0;
	if( bShow )
		pUndo = new UndoC( "Show Parameter" );
	else
		pUndo = new UndoC( "Hide Parameter" );

	UndoC*	pOldUndo = pParam->begin_editing( pUndo );

	if( bShow )
		pParam->del_flags( ITEM_GUIHIDDEN );
	else
		pParam->add_flags( ITEM_GUIHIDDEN );

	pParam->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

int32
CDemopajaDoc::ParameterTypeIn( ParamI* pParam, int32 i32Time )
{
	i32Time -= ParameterGetTimeOffset( pParam );

	// save current state
	UndoC*	pUndo = new UndoC( "Change Parameter" );
	UndoC*	pOldUndo;

	// deselect all keys
	KeysDeselectAll();

	pOldUndo = pParam->begin_editing( pUndo );

	// create key at the timecursor time.
	HandleParamNotify( ParameterCreateValue( pParam, i32Time ) );

	pParam->end_editing( pOldUndo );

	// show typein
	int32	i32Res = ParameterTypeInShowDlg( pParam, i32Time );

	if( i32Res == IDOK ) {
		m_rUndoManager.push( pUndo );
		SetModifiedFlag();
	}
	else {
		pUndo->undo();
		delete pUndo;
	}

	return i32Res;
}

int32
CDemopajaDoc::ParameterTypeInShowDlg( ParamI* pParam, int32 i32Time )
{
	if( !pParam )
		return 0;

	// window position
	CPoint	rPt;
	::GetCursorPos( &rPt );
	rPt.x -= 5;
	rPt.y -= 5;

	// setup and launch pop-up
	if( pParam->get_type() == PARAM_TYPE_INT ) {

		if( pParam->get_style() == PARAM_STYLE_COMBOBOX ) {
			CIntComboTypeInDlg	rDlg;

			rDlg.m_sInfoText = pParam->get_name();
			rDlg.m_sInfoText += ":";
			rDlg.m_pParam = (ParamIntC*)pParam;
			rDlg.m_i32Time = i32Time;

			return rDlg.DoModal();
		}
		else {
			CIntTypeInDlg	rDlg;

			float32	f32Min[KEY_MAXCHANNELS], f32Max[KEY_MAXCHANNELS];
			bool	bClampValues = pParam->get_min_max( f32Min, f32Max );

			if( bClampValues ) {
				rDlg.m_i32Min = (int32)f32Min[0];
				rDlg.m_i32Max = (int32)f32Max[0];
				rDlg.m_bClampValues = true;
			}

			rDlg.m_sInfoText = pParam->get_name();
			rDlg.m_sInfoText += ":";
			rDlg.m_i32Inc = (int32)pParam->get_increment();
			rDlg.m_pParam = (ParamIntC*)pParam;
			rDlg.m_i32Time = i32Time;

			return rDlg.DoModal();
		}
	}
	else if( pParam->get_type() == PARAM_TYPE_FLOAT ) {

		CFloatTypeInDlg	rDlg;

		float32	f32Min[KEY_MAXCHANNELS], f32Max[KEY_MAXCHANNELS];
		bool	bClampValues = pParam->get_min_max( f32Min, f32Max );

		if( bClampValues ) {
			rDlg.m_f32Min = f32Min[0];
			rDlg.m_f32Max = f32Max[0];
			rDlg.m_bClampValues = true;
		}

		rDlg.m_sInfoText = pParam->get_name();
		rDlg.m_sInfoText += ":";
		rDlg.m_f32Inc = pParam->get_increment();
		rDlg.m_pParam = (ParamFloatC*)pParam;
		rDlg.m_i32Time = i32Time;

		return rDlg.DoModal();
	}
	else if( pParam->get_type() == PARAM_TYPE_VECTOR2 ) {
		CVector2TypeInDlg	rDlg;

		float32	f32Min[KEY_MAXCHANNELS], f32Max[KEY_MAXCHANNELS];
		bool	bClampValues = pParam->get_min_max( f32Min, f32Max );

		if( bClampValues ) {
			rDlg.m_rMin[0] = f32Min[0];
			rDlg.m_rMax[0] = f32Max[0];
			rDlg.m_rMin[1] = f32Min[1];
			rDlg.m_rMax[1] = f32Max[1];
			rDlg.m_bClampValues = true;
		}

		rDlg.m_sInfoText = pParam->get_name();
		rDlg.m_sInfoText += ":";
		rDlg.m_f32Inc = pParam->get_increment();
		rDlg.m_pParam = (ParamVector2C*)pParam;
		rDlg.m_i32Time = i32Time;

		return rDlg.DoModal();
	}
	else if( pParam->get_type() == PARAM_TYPE_VECTOR3 ) {
		CVector3TypeInDlg	rDlg;

		float32	f32Min[KEY_MAXCHANNELS], f32Max[KEY_MAXCHANNELS];
		bool	bClampValues = pParam->get_min_max( f32Min, f32Max );

		if( bClampValues ) {
			rDlg.m_rMin[0] = f32Min[0];
			rDlg.m_rMin[1] = f32Min[1];
			rDlg.m_rMin[2] = f32Min[2];

			rDlg.m_rMax[0] = f32Max[0];
			rDlg.m_rMax[1] = f32Max[1];
			rDlg.m_rMax[2] = f32Max[2];

			rDlg.m_bClampValues = true;
		}

		rDlg.m_sInfoText = pParam->get_name();
		rDlg.m_sInfoText += ":";
		rDlg.m_f32Inc = pParam->get_increment();
		rDlg.m_pParam = (ParamVector3C*)pParam;
		rDlg.m_i32Time = i32Time;

		return rDlg.DoModal();
	}
	else if( pParam->get_type() == PARAM_TYPE_COLOR ) {
		CColorTypeInDlg		rDlg;

		rDlg.m_pParam = (ParamColorC*)pParam;
		rDlg.m_i32Time = i32Time;

		return rDlg.DoModal();
	}
	else if( pParam->get_type() == PARAM_TYPE_FILE ) {

		CFileTypeInDlg	rDlg;

		rDlg.m_sInfoText = pParam->get_name();
		rDlg.m_sInfoText += ":";
		rDlg.m_pParam = (ParamFileC*)pParam;
		rDlg.m_i32Time = i32Time;
		rDlg.m_pFileList = GetFileList();

		return rDlg.DoModal();
	}
	else if( pParam->get_type() == PARAM_TYPE_TEXT ) {

		CTextTypeInDlg	rDlg;

		rDlg.m_sInfoText = pParam->get_name();
		rDlg.m_sInfoText += ":";
		rDlg.m_pParam = (ParamTextC*)pParam;
		rDlg.m_i32Time = i32Time;

		return rDlg.DoModal();
	}
	else if( pParam->get_type() == PARAM_TYPE_LINK ) {

		LayerC*		pLayer = 0;
		EffectI*	pEffect = 0;
		GizmoI*		pGizmo = 0;

		pGizmo = pParam->get_parent();
		if( pGizmo )
			pEffect = pGizmo->get_parent();
		if( pEffect )
			pLayer = pEffect->get_parent();

		if( !pEffect || !pLayer )
			return 0;

		if( pParam->get_style() == PARAM_STYLE_SINGLE_LINK )
		{
			CSingleLinkTypeIn	rDlg;

			rDlg.m_sInfoText = pParam->get_name();
			rDlg.m_sInfoText += ":";
			rDlg.m_pLayer = pLayer;
			rDlg.m_pEffect = pEffect;
			rDlg.m_pParam = (ParamLinkC*)pParam;
//			rDlg.m_i32Time = i32Time;

			return rDlg.DoModal();
		}
		else
		{
			CMultiLinkTypeIn	rDlg;

			rDlg.m_sInfoText = pParam->get_name();
			rDlg.m_sInfoText += ":";
			rDlg.m_pLayer = pLayer;
			rDlg.m_pEffect = pEffect;
			rDlg.m_pParam = (ParamLinkC*)pParam;
//			rDlg.m_i32Time = i32Time;

			return rDlg.DoModal();
		}
	}

	return 0;
}

void
CDemopajaDoc::ParameterSaveState( ParamI* pParam, int32 i32Time )
{
	// save current state
	UndoC*	pUndo = new UndoC( "Change Parameter" );
	UndoC*	pOldUndo;

	pOldUndo = pParam->begin_editing( pUndo );

	// create key at the timecursor time.
	HandleParamNotify( ParameterCreateValue( pParam, i32Time ) );

	pParam->end_editing( pOldUndo );

	m_rUndoManager.push( pUndo );
	SetModifiedFlag();
}

int32
CDemopajaDoc::ParameterGetTimeOffset( ParamI* pParam )
{
	int32		i32TimeOffset = 0;
	GizmoI*		pGizmo = pParam->get_parent();
	EffectI*	pEffect = pGizmo->get_parent();
	LayerC*		pLayer = pEffect->get_parent();
	if( pEffect && pLayer ) {
		TimeSegmentC*	pLayerSegment = pLayer->get_timesegment();
		TimeSegmentC*	pEffectSegment = pEffect->get_timesegment();
		if( pLayerSegment && pEffectSegment ) {
			i32TimeOffset += pLayerSegment->get_segment_start();
			i32TimeOffset += pEffectSegment->get_segment_start();
		}
	}
	return i32TimeOffset;
}

void
CDemopajaDoc::ParameterSetFile( ParamI* pParam, int32 i32Time, FileHandleC* pHandle )
{
	UndoC*	pUndo = new UndoC( "Drag and Drop File" );

	GizmoI*		pGizmo = pParam->get_parent();
	EffectI*	pEffect = pGizmo->get_parent();

	i32Time -= ParameterGetTimeOffset( pParam );

	UndoC*	pFxOldUndo = pEffect->begin_editing( pUndo );
	UndoC*	pOldUndo = pParam->begin_editing( pUndo );

	ParamFileC*	pParamFile = (ParamFileC*)pParam;

	BeginDeferHandleParamNotify();
	HandleParamNotify( pParamFile->set_file( i32Time, pHandle ) );
	if( CheckFileRecursion() )
		HandleParamNotify( pParamFile->set_file( i32Time, 0 ) );
	EndDeferHandleParamNotify();
	
	pParam->end_editing( pOldUndo );
	pEffect->end_editing( pFxOldUndo );

	m_rUndoManager.push( pUndo );
	UpdateFileReferences();
	SetModifiedFlag();
}


//
// Gizmo
//

void
CDemopajaDoc::GizmoShow( GizmoI* pGizmo, bool bShow )
{
	UndoC*	pUndo = 0;
	if( bShow )
		pUndo = new UndoC( "Show Gizmo" );
	else
		pUndo = new UndoC( "Hide Gizmo" );

	UndoC*	pOldUndo = pGizmo->begin_editing( pUndo );

	if( bShow )
		pGizmo->del_flags( ITEM_GUIHIDDEN );
	else
		pGizmo->add_flags( ITEM_GUIHIDDEN );

	pGizmo->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

//
// Time Segment
//
void
CDemopajaDoc::TimeSegmentTypeIn( EffectI* pEffect, int32 i32Time )
{
	int32			i32TimeOffset = 0;
	TimeSegmentC*	pSegment = 0;

	LayerC*		pLayer = pEffect->get_parent();
	if( pEffect && pLayer ) {
		TimeSegmentC*	pLayerSegment = pLayer->get_timesegment();
		if( pLayerSegment )
			i32TimeOffset += pLayerSegment->get_segment_start();
		pSegment = pEffect->get_timesegment();
	}

	TimeSegmentTypeIn( pSegment, i32Time - i32TimeOffset, i32TimeOffset, ITEM_EFFECT );
}

void
CDemopajaDoc::TimeSegmentTypeIn( LayerC* pLayer, int32 i32Time )
{
	TimeSegmentTypeIn( pLayer->get_timesegment(), i32Time, 0, ITEM_LAYER );
}

void
CDemopajaDoc::TimeSegmentTypeIn( TimeSegmentC* pSegment, int32 i32Time, int32 i32TimeOffset, int32 i32Type )
{
	if( pSegment && pSegment->get_key_count() > 0 ) {

		// save current state
		UndoC*	pUndo = new UndoC( "Change Timesegment" );
		UndoC*	pOldUndo;

		// deselect all keys
		KeysDeselectAll();

		pOldUndo = pSegment->begin_editing( pUndo );

		CTimesegmentTypeInDlg	rDlg;

		rDlg.m_i32Flags = i32Type;

		rDlg.m_i32BeatsPerMin = GetBeatsPerMin();
		rDlg.m_i32EditAccuracy = GetEditAccuracy();
		rDlg.m_i32QNotesPerBeat = GetQNotesPerBeat();

		rDlg.m_i32Time = i32Time;
		rDlg.m_i32TimeOffset = i32TimeOffset;
		rDlg.m_pTimeSegment = pSegment;

		int32	i32Res = rDlg.DoModal();

		pSegment->end_editing( pOldUndo );

		if( i32Res == IDOK ) {
			m_rUndoManager.push( pUndo );
			SetModifiedFlag();
		}
		else {
			pUndo->undo();
			delete pUndo;
		}
	}
	else {
		MessageBox( NULL, "The Timesegment does not have any keys.\nYou must have some keys to edit in the dialog.", "No Keys in Timesegment", MB_ICONWARNING | MB_OK );
	}
}

void
CDemopajaDoc::TimeSegmentAddKeyAtTime( EffectI* pEffect, int32 i32Time )
{
	LayerC*			pLayer = pEffect->get_parent();
	TimeSegmentC*	pLayerSeg = pLayer->get_timesegment();
	TimeSegmentC*	pSegment = pEffect->get_timesegment();
	TimeSegmentAddKeyAtTime( pSegment, i32Time - pLayerSeg->get_segment_start() );
}

void
CDemopajaDoc::TimeSegmentAddKeyAtTime( LayerC* pLayer, int32 i32Time )
{
	TimeSegmentAddKeyAtTime( pLayer->get_timesegment(), i32Time );
}

void
CDemopajaDoc::TimeSegmentAddKeyAtTime( TimeSegmentC* pSegment, int32 i32Time )
{
	// creata command and make changes
	UndoC*	pUndo = new UndoC( "Create Key" );
	UndoC*	pOldUndo = pSegment->begin_editing( pUndo );

	pSegment->add_key_at_time( i32Time );

	pSegment->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
}

void
CDemopajaDoc::TimeSegmentSaveState( TimeSegmentC* pSeg, const char* szAction )
{
	ASSERT( m_pCurrentUndo == 0 );

	m_pCurrentUndo = new UndoC( "Move Time Segment" );
	UndoC*	pOldUndo = pSeg->begin_editing( m_pCurrentUndo );
	pSeg->end_editing( pOldUndo );
}

//
// Effect
//
void
CDemopajaDoc::EffectEditName( EffectI* pEffect, const char* szName )
{
	UndoC*	pUndo;

	pUndo = new UndoC( "Rename Effect" );
	UndoC*	pOldUndo = pEffect->begin_editing( pUndo );
	pEffect->set_name( szName );
	pEffect->end_editing( pOldUndo );

	// store command
	m_rUndoManager.push( pUndo );

	// mark document as modified
	SetModifiedFlag();
}

EffectI*
CDemopajaDoc::EffectGetSelected()
{
	if( !GetCurrentScene()->get_layer_count() ) {
		return 0;
	}

	bool		bMatch = false;
	EffectI*	pSelEffect = 0;

	for( int32 i = 0; i < (int32)GetCurrentScene()->get_layer_count() && !bMatch; i++ ) {

		LayerC*	pLayer = GetCurrentScene()->get_layer( i );

		for( uint32 j = 0; j < pLayer->get_effect_count() && !bMatch; j++ ) {
			EffectI*	pEffect = pLayer->get_effect( j );
			if( pEffect->get_flags() & ITEM_SELECTED ) {
				if( pSelEffect ) {
					pSelEffect = 0;
					bMatch = true;
					break;
				}
				else
					pSelEffect = pEffect;
			}
			for( uint32 k = 0; k < pEffect->get_gizmo_count() && !bMatch; k++ ) {
				GizmoI*	pGizmo = pEffect->get_gizmo( k );
				if( pGizmo->get_flags() & ITEM_SELECTED ) {
					if( pSelEffect ) {
						pSelEffect = 0;
						bMatch = true;
						break;
					}
					else
						pSelEffect = pEffect;
				}
				for( uint32 m = 0; m < pGizmo->get_parameter_count() && !bMatch; m++ ) {
					ParamI*	pParam = pGizmo->get_parameter( m );
					if( pParam->get_flags() & ITEM_SELECTED ) {
						if( pSelEffect ) {
							pSelEffect = 0;
							bMatch = true;
							break;
						}
						else
							pSelEffect = pEffect;
					}
				}
			}
		}
	}

	return pSelEffect;
}

void
CDemopajaDoc::EffectMoveAfter( int32 i32Item, int32 i32ItemLayer, int32 i32ItemAfter, int32 i32ItemAfterLayer )
{
	EffectMoveBefore( i32Item, i32ItemLayer, i32ItemAfter + 1, i32ItemAfterLayer );
}

void
CDemopajaDoc::EffectMoveBefore( PajaTypes::int32 i32Item, PajaTypes::int32 i32ItemLayer, PajaTypes::int32 i32ItemBefore, PajaTypes::int32 i32ItemBeforeLayer )
{
	LayerC*	pFromLayer = GetCurrentScene()->get_layer( i32ItemLayer );
	LayerC*	pToLayer = GetCurrentScene()->get_layer( i32ItemBeforeLayer );

	UndoC*	pUndo = new UndoC( "Move Effect" );

	if( pFromLayer == pToLayer ) {

		UndoC*	pOldUndo = pFromLayer->begin_editing( pUndo );
		pFromLayer->end_editing( pOldUndo );

		if( i32Item == i32ItemBefore ) {
			delete pUndo;
			return;
		}

		EffectI*	pMovedEffect = pFromLayer->remove_effect( i32Item );
		if( !pMovedEffect ) {
			delete pUndo;
			return;
		}

		int32	i32IndexBefore = i32ItemBefore;

		if( i32IndexBefore > i32Item )
			i32IndexBefore--;

		if( i32IndexBefore < 0 )
			i32IndexBefore = 0;

		pFromLayer->insert_effect( i32IndexBefore, pMovedEffect );

		// Remove all the links from the effect that points to effects the are
		// evaluated later.

/*		for( int32 i = 0; i < pMovedEffect->get_gizmo_count(); i++ )
		{
			GizmoI*	pGizmo = pMovedEffect->get_gizmo( i );
			for( int32 j = 0; j < pGizmo->get_parameter_count(); j++ )
			{
				ParamI*	pParam = pGizmo->get_parameter( j );
				if( pParam->get_type() == PARAM_TYPE_LINK )
				{
					ParamLinkC*	pParamLink = (ParamLinkC*)pParam;
					UndoC*	pOldParamUndo = pParamLink->begin_editing( pUndo );

					// Go thru the list of effects in the current layer, and
					// remove every effect from the link effect that comes after
					// the current effect.
					bool	bDel = false;
					for( int32 k = pFromLayer->get_effect_count() - 1; k >= 0; k-- )
					{
						// If del flag is set, delete links to the effect.
						if( bDel )
							pParamLink->del_link( pFromLayer->get_effect( k ) );
						// Set del flag to delete every effect after this from the link parameter.
						if( pFromLayer->get_effect( k ) == pEffect )
							bDel = true;
					}

					pParamLink->end_editing( pOldParamUndo );
				}
			}
		}
*/

		// Remove all links referencing the effect.
		for( int32 m = 0; m < pFromLayer->get_effect_count(); m++ )
		{
			EffectI*	pEffect = pFromLayer->get_effect( m );

			for( int32 i = 0; i < pEffect->get_gizmo_count(); i++ )
			{
				GizmoI*	pGizmo = pEffect->get_gizmo( i );
				for( int32 j = 0; j < pGizmo->get_parameter_count(); j++ )
				{
					ParamI*	pParam = pGizmo->get_parameter( j );
					if( pParam->get_type() == PARAM_TYPE_LINK )
					{
						ParamLinkC*	pParamLink = (ParamLinkC*)pParam;
						UndoC*	pOldParamUndo = pParamLink->begin_editing( pUndo );

						// Go thru the list of effects in the current layer, and
						// remove every effect from the link effect that comes after
						// the current effect.
						bool	bDel = false;
						for( int32 k = pFromLayer->get_effect_count() - 1; k >= 0; k-- )
						{
							// If del flag is set, delete links to the effect.
							if( bDel )
								pParamLink->del_link( pFromLayer->get_effect( k ) );
							// Set del flag to delete every effect after this from the link parameter.
							if( pFromLayer->get_effect( k ) == pEffect )
								bDel = true;
						}

						pParamLink->end_editing( pOldParamUndo );
					}
				}
			}
		}

	}
	else {

		UndoC*	pOldUndo = pFromLayer->begin_editing( pUndo );
		pFromLayer->end_editing( pOldUndo );
		pOldUndo = pToLayer->begin_editing( pUndo );
		pToLayer->end_editing( pOldUndo );

		EffectI*	pMovedEffect = pFromLayer->remove_effect( i32Item );
		if( !pMovedEffect ) {
			delete pUndo;
			return;
		}

		if( i32ItemBefore < 0 )
			i32ItemBefore = 0;


		pToLayer->insert_effect( i32ItemBefore, pMovedEffect );

		// Remove all links from the object.
		for( int32 i = 0; i < pMovedEffect->get_gizmo_count(); i++ )
		{
			GizmoI*	pGizmo = pMovedEffect->get_gizmo( i );
			for( int32 j = 0; j < pGizmo->get_parameter_count(); j++ )
			{
				ParamI*	pParam = pGizmo->get_parameter( j );
				if( pParam->get_type() == PARAM_TYPE_LINK )
				{
					ParamLinkC*	pParamLink = (ParamLinkC*)pParam;
					UndoC*	pOldParamUndo = pParamLink->begin_editing( pUndo );
					pParamLink->del_all_links();
					pParamLink->end_editing( pOldParamUndo );
				}
			}
		}

		// Remove all links referencing the effect.
		for( int32 m = 0; m < pFromLayer->get_effect_count(); m++ )
		{
			EffectI*	pEffect = pFromLayer->get_effect( m );

			for( int32 i = 0; i < pEffect->get_gizmo_count(); i++ )
			{
				GizmoI*	pGizmo = pEffect->get_gizmo( i );
				for( int32 j = 0; j < pGizmo->get_parameter_count(); j++ )
				{
					ParamI*	pParam = pGizmo->get_parameter( j );
					if( pParam->get_type() == PARAM_TYPE_LINK )
					{
						ParamLinkC*	pParamLink = (ParamLinkC*)pParam;
						UndoC*	pOldParamUndo = pParamLink->begin_editing( pUndo );

						// Deleted links to the moved effect
						pParamLink->del_link( pMovedEffect );

						pParamLink->end_editing( pOldParamUndo );
					}
				}
			}
		}

	}

	m_rUndoManager.push( pUndo );

	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

void
CDemopajaDoc::EffectDeleteSelected( bool bCut )
{
	if( !GetCurrentScene()->get_layer_count() ) {
		return;
	}

	UndoC*	pUndo;
	if( bCut )
		pUndo = new UndoC( "Cut Effects" );
	else
		pUndo = new UndoC( "Delete Effects" );
	UndoC*	pOldUndo = GetCurrentScene()->begin_editing( pUndo );

	for( int32 i = (int32)GetCurrentScene()->get_layer_count() - 1; i >= 0; i-- ) {

		LayerC*	pLayer = GetCurrentScene()->get_layer( i );

		if( pLayer ) {
			UndoC*	pOldLayerUndo = pLayer->begin_editing( pUndo );

			for( int32 j = (int32)pLayer->get_effect_count() - 1; j >= 0; j-- )
				if( pLayer->get_effect( j )->get_flags() & ITEM_SELECTED )
					pLayer->del_effect( j );

			pLayer->end_editing( pOldLayerUndo );
		}
	}

	GetCurrentScene()->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
	UpdateFileReferences();
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

EffectI*
CDemopajaDoc::EffectCreate( const ClassIdC& rClassId, const char* szName, LayerC* pLayer, int32 i32Time, FileHandleC* pHandle )
{
	if( !pLayer || rClassId == NULL_CLASSID )
		return 0;

	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();
	FactoryC*			pFactory = pApp->GetFactory();

	UndoC*	pUndo = new UndoC( "Add New Effect" );
	UndoC*	pOldUndo = pLayer->begin_editing( pUndo );

	EffectI*	pEffect = (EffectI*)pFactory->create( rClassId );

	if( pEffect ) {

		TimeSegmentC*	pSegment = pEffect->get_timesegment();
		int32			i32Len = GetQNotesPerBeat() * GetBeatsPerMeasure() * 256;
		int32			i32Start = i32Time - pLayer->get_timesegment()->get_segment_start();
		int32			i32End = i32Start + i32Len;
		pSegment->set_segment_start( i32Start );
		pSegment->add_key_at_time( i32Start );
		pSegment->add_key_at_time( i32End );


		pLayer->add_effect( pEffect );

		pEffect->set_name( szName );
		pEffect->set_parent( pLayer );

		if( pHandle )
			pEffect->set_default_file( i32Start, pHandle );

		pEffect->initialize( INIT_INITIAL_UPDATE, m_pDemoInterface );

		pLayer->end_editing( pOldUndo );

		if( CheckFileRecursion() ) {
			pEffect->set_default_file( i32Start, 0 );
		}

		m_rUndoManager.push( pUndo );
		UpdateFileReferences();
		SetModifiedFlag();
		NotifyViews( NOTIFY_LAYERLIST_CHANGED );
	}

	return pEffect;
}


//
// Layer
//
void
CDemopajaDoc::LayerEditName( LayerC* pLayer, const char* szName )
{
	UndoC*	pUndo;

	pUndo = new UndoC( "Rename Layer" );
	UndoC*	pOldUndo = pLayer->begin_editing( pUndo );
	pLayer->set_name( szName );
	pLayer->end_editing( pOldUndo );

	// store command
	m_rUndoManager.push( pUndo );

	// mark document as modified
	SetModifiedFlag();
}


LayerC*
CDemopajaDoc::LayerGetSelected()
{
	if( !GetCurrentScene()->get_layer_count() ) {
		return 0;
	}

	bool		bMatch = false;
	LayerC*	pSelLayer = 0;

	for( uint32 i = 0; i < GetCurrentScene()->get_layer_count() && !bMatch; i++ ) {

		LayerC*	pLayer = GetCurrentScene()->get_layer( i );

		if( pLayer && pLayer->get_flags() & ITEM_SELECTED ) {
			if( pSelLayer ) {
				pSelLayer = 0;
				bMatch = true;
				break;
			}
			else
				pSelLayer = pLayer;
		}

		for( uint32 j = 0; j < pLayer->get_effect_count() && !bMatch; j++ ) {
			EffectI*	pEffect = pLayer->get_effect( j );
			if( pEffect->get_flags() & ITEM_SELECTED ) {
				if( pSelLayer ) {
					pSelLayer = 0;
					bMatch = true;
					break;
				}
				else
					pSelLayer = pLayer;
			}
			for( uint32 k = 0; k < pEffect->get_gizmo_count() && !bMatch; k++ ) {
				GizmoI*	pGizmo = pEffect->get_gizmo( k );
				if( pGizmo->get_flags() & ITEM_SELECTED ) {
					if( pSelLayer ) {
						pSelLayer = 0;
						bMatch = true;
						break;
					}
					else
						pSelLayer = pLayer;
				}
				for( uint32 m = 0; m < pGizmo->get_parameter_count() && !bMatch; m++ ) {
					ParamI*	pParam = pGizmo->get_parameter( m );
					if( pParam->get_flags() & ITEM_SELECTED ) {
						if( pSelLayer ) {
							pSelLayer = 0;
							bMatch = true;
							break;
						}
						else
							pSelLayer = pLayer;
					}
				}
			}
		}
	}

	return pSelLayer;
}

void
CDemopajaDoc::LayerAdd( PajaTypes::int32 i32Time )
{
	UndoC*	pUndo = new UndoC( "Add New Layer" );
	UndoC*	pOldUndo = GetCurrentScene()->begin_editing( pUndo );

	LayerC*	pLayer = GetCurrentScene()->add_layer();
	pLayer->set_name( "New Layer" );
	TimeSegmentC*	pSegment = pLayer->get_timesegment();
	int32			i32Len = GetQNotesPerBeat() * GetBeatsPerMeasure() * 256;
	pSegment->set_segment_start( i32Time );
	pSegment->add_key_at_time( i32Time );
	pSegment->add_key_at_time( i32Time + i32Len );

	GetCurrentScene()->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
	UpdateFileReferences();
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

void
CDemopajaDoc::LayerDeleteSelected( bool bCut )
{
	UndoC*	pUndo;
	if( bCut )
		pUndo = new UndoC( "Cut Layers" );
	else
		pUndo = new UndoC( "Delete Layers" );
	UndoC*	pOldUndo = GetCurrentScene()->begin_editing( pUndo );

	for( int32 i = (int32)GetCurrentScene()->get_layer_count() - 1; i >= 0; i-- )
		if( GetCurrentScene()->get_layer( i )->get_flags() & ITEM_SELECTED )
			GetCurrentScene()->del_layer( i );

	GetCurrentScene()->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
	UpdateFileReferences();
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

void
CDemopajaDoc::LayerAndEffectDeleteSelected( bool bCut )
{
	if( !GetCurrentScene()->get_layer_count() ) {
		return;
	}

	UndoC*	pUndo;
	if( bCut )
		pUndo = new UndoC( "Cut Layers/Effects" );
	else
		pUndo = new UndoC( "Delete Layers/Effects" );
	UndoC*	pOldUndo = GetCurrentScene()->begin_editing( pUndo );

	for( int32 i = (int32)GetCurrentScene()->get_layer_count() - 1; i >= 0; i-- ) {

		LayerC*	pLayer = GetCurrentScene()->get_layer( i );

		if( pLayer ) {
			UndoC*	pOldLayerUndo = pLayer->begin_editing( pUndo );

			for( int32 j = (int32)pLayer->get_effect_count() - 1; j >= 0; j-- )
				if( pLayer->get_effect( j )->get_flags() & ITEM_SELECTED )
					pLayer->del_effect( j );

			pLayer->end_editing( pOldLayerUndo );
		}

		if( pLayer->get_flags() & ITEM_SELECTED ) {
			GetCurrentScene()->del_layer( i );
		}
	}

	GetCurrentScene()->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	SetModifiedFlag();
	UpdateFileReferences();
	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

void
CDemopajaDoc::LayerMoveAfter( int32 i32Item, int32 i32ItemAfter )
{
	UndoC*	pUndo = new UndoC( "Move Layer" );
	UndoC*	pOldUndo = GetCurrentScene()->begin_editing( pUndo );

	GetCurrentScene()->move_layer_before( i32Item, i32ItemAfter + 1 );

	GetCurrentScene()->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}

void
CDemopajaDoc::LayerMoveBefore( int32 i32Item, int32 i32ItemBefore )
{
	UndoC*	pUndo = new UndoC( "Move Layer" );
	UndoC*	pOldUndo = GetCurrentScene()->begin_editing( pUndo );

	GetCurrentScene()->move_layer_before( i32Item, i32ItemBefore );

	GetCurrentScene()->end_editing( pOldUndo );
	m_rUndoManager.push( pUndo );

	NotifyViews( NOTIFY_LAYERLIST_CHANGED );
}






//
// Markers
//

void
CDemopajaDoc::MarkerTypeIn( int32 i32MarkerIdx )
{
	CMarkerTypeInDlg	rDlg;
	SceneC*				pScene = GetCurrentScene();

	rDlg.m_i32BeatsPerMin = GetBeatsPerMin();
	rDlg.m_i32QNotesPerBeat = GetQNotesPerBeat();
	rDlg.m_i32EditAccuracy = GetEditAccuracy();

	rDlg.m_i32Time = TimeToFrame( pScene->get_marker_time( (uint32)i32MarkerIdx ) );
	rDlg.m_i32Color = pScene->get_marker_color( (uint32)i32MarkerIdx );
	rDlg.m_sName = pScene->get_marker_name( (uint32)i32MarkerIdx );

	if( rDlg.DoModal() == IDOK ) {
		UndoC*	pUndo = new UndoC( "Edit Marker" );
		UndoC*	pOldUndo;

		pOldUndo = pScene->begin_editing( pUndo );

		pScene->set_marker_time( (uint32)i32MarkerIdx, FrameToTime( rDlg.m_i32Time ) );
		pScene->set_marker_color( (uint32)i32MarkerIdx, rDlg.m_i32Color );
		pScene->set_marker_name( (uint32)i32MarkerIdx, rDlg.m_sName );
		pScene->sort_markers();

		pScene->end_editing( pOldUndo );

		m_rUndoManager.push( pUndo );
		SetModifiedFlag();
	}
}

void
CDemopajaDoc::MarkerDelete( int32 i32MarkerIdx )
{
	UndoC*	pUndo = new UndoC( "Delete Marker" );
	UndoC*	pOldUndo;
	SceneC*	pScene = GetCurrentScene();

	pOldUndo = pScene->begin_editing( pUndo );
	pScene->del_marker( (uint32)i32MarkerIdx );
	pScene->end_editing( pOldUndo );

	m_rUndoManager.push( pUndo );
	SetModifiedFlag();
}

void
CDemopajaDoc::MarkerAdd( int32 i32Time )
{
	CMarkerTypeInDlg	rDlg;
	SceneC*				pScene = GetCurrentScene();

	rDlg.m_i32BeatsPerMin = GetBeatsPerMin();
	rDlg.m_i32QNotesPerBeat = GetQNotesPerBeat();
	rDlg.m_i32EditAccuracy = GetEditAccuracy();

	rDlg.m_i32Time = TimeToFrame( i32Time );
	rDlg.m_i32Color = 0;
	rDlg.m_sName = "New Marker";

	if( rDlg.DoModal() == IDOK ) {
		UndoC*	pUndo = new UndoC( "Add Marker" );
		UndoC*	pOldUndo;

		pOldUndo = pScene->begin_editing( pUndo );

		pScene->add_marker( FrameToTime( rDlg.m_i32Time ), rDlg.m_sName, rDlg.m_i32Color );
		pScene->sort_markers();
		
		pScene->end_editing( pOldUndo );

		m_rUndoManager.push( pUndo );
		SetModifiedFlag();
	}
}

void
CDemopajaDoc::MarkerSaveState( const char* szAction )
{
	UndoC*	pUndo = new UndoC( szAction );
	UndoC*	pOldUndo;

	SceneC*	pScene = GetCurrentScene();

	pOldUndo = pScene->begin_editing( pUndo );
	pScene->end_editing( pOldUndo );

	m_rUndoManager.push( pUndo );
	SetModifiedFlag();
}


void
CDemopajaDoc::SetProjectPath( const char* szPath, bool bTemp )
{
	CDemopajaApp*		pApp = (CDemopajaApp*)AfxGetApp();

	// save old if necessary
	if( m_bSaveProjectPath )
		pApp->WriteProfileString( "ProjectPath", "Path", m_sProjectPath.c_str() );

	// set new
	if( bTemp )
		m_bSaveProjectPath = false;
	else
		m_bSaveProjectPath = true;

	m_sProjectPath = szPath;

	// update import interface
	if( m_pDemoInterface )
		m_pDemoInterface->set_project_path( m_sProjectPath.c_str() );
}

const char*
CDemopajaDoc::GetProjectPath()
{
	return m_sProjectPath.c_str();
}

bool
CDemopajaDoc::IsProjectPathTemporary()
{
	if( m_bSaveProjectPath )
		return false;
	return true;
}
