// demopajaView.cpp : implementation of the CDemopajaView class
//

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

#include "stdafx.h"

#include <math.h>
#include <mmsystem.h>
#include "demopaja.h"
#include "demopajaDoc.h"
#include "demopajaView.h"
#include "PajaTypes.h"
#include "Vector2C.h"
#include "BBox2C.h"
#include "DeviceContextC.h"
#include "DeviceInterfaceI.h"
#include "TimeContextC.h"
#include "SceneC.h"
#include "LayerC.h"
#include "EffectI.h"
#include "EffectPostProcessI.h"
#include "ParamI.h"
#include "TimelineBar.h"
#include "Mainfrm.h"
#include "UndoC.h"
#include "ControllerC.h"
#include "GraphicsDeviceI.h"
#include "GraphicsViewportI.h"
#include "GUIDrawInterfaceI.h"
#include "DeviceFeedbackC.h"


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


#ifndef M_PI
#define M_PI 3.1415926536
#endif



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



const int32	RULERSIZE = 20;


/////////////////////////////////////////////////////////////////////////////
// CDemopajaView

IMPLEMENT_DYNCREATE(CDemopajaView, CView)

BEGIN_MESSAGE_MAP(CDemopajaView, CView)
	//{{AFX_MSG_MAP(CDemopajaView)
	ON_WM_DESTROY()
	ON_WM_SIZE()
	ON_WM_ERASEBKGND()
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_WM_SETCURSOR()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONUP()
	ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
	ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
	ON_COMMAND(ID_EDIT_CUT, OnEditCut)
	ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
	ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
	ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
	ON_COMMAND(ID_VIEW_ZOOM_100, OnViewZoom100)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ZOOM_100, OnUpdateViewZoom100)
	ON_COMMAND(ID_VIEW_ZOOM_200, OnViewZoom200)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ZOOM_200, OnUpdateViewZoom200)
	ON_COMMAND(ID_VIEW_ZOOM_300, OnViewZoom300)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ZOOM_300, OnUpdateViewZoom300)
	ON_COMMAND(ID_VIEW_ZOOM_400, OnViewZoom400)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ZOOM_400, OnUpdateViewZoom400)
	ON_COMMAND(ID_VIEW_ZOOM_50, OnViewZoom50)
	ON_UPDATE_COMMAND_UI(ID_VIEW_ZOOM_50, OnUpdateViewZoom50)
	ON_WM_CREATE()
	ON_WM_TIMER()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDemopajaView construction/destruction

CDemopajaView::CDemopajaView()
{
	m_bSpacePressed = false;
	m_bCtrlPressed  = false;
	m_hCursor = 0;

	m_pCurrentUndo = 0;
	m_ui32CurTool = 0;
	m_bStoreOrigSelBBox = false;

	m_ui32TimerID = 0;

	m_bViewResetRequest = false;
}

CDemopajaView::~CDemopajaView()
{
}


#define CUSTOM_CLASSNAME _T("OpenglView")

BOOL CDemopajaView::PreCreateWindow(CREATESTRUCT& cs)
{
	// modify window styles and such here
	cs.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS);          

	// call base class PreCreateWindow to get the cs.lpszClass filled in with the MFC default class name
	if( !CView::PreCreateWindow(cs) )
		return 0;

	// Register the window class if it has not already been registered.
	WNDCLASS wndcls;
	HINSTANCE hInst = AfxGetInstanceHandle();

	if( !(::GetClassInfo( hInst, CUSTOM_CLASSNAME, &wndcls) ) ) {			// check if our class is registered
		if( ::GetClassInfo( hInst, cs.lpszClass, &wndcls) ) {			// get default MFC class settings 
			 wndcls.lpszClassName = CUSTOM_CLASSNAME;				// set our class name

			 wndcls.style |= CS_OWNDC;								// change settings for your custom class
			 wndcls.hbrBackground = NULL;

			 if (!AfxRegisterClass(&wndcls))						// register class
				AfxThrowResourceException();						// could not register class
		}
		else
			AfxThrowResourceException();							// default MFC class not registered
	}

	cs.lpszClass = CUSTOM_CLASSNAME;								// set our class name in CREATESTRUCT

	return 1;
}

/////////////////////////////////////////////////////////////////////////////
// CDemopajaView drawing


//
//  return "nice number" ie. 2, 50, 1000 etc.
//
static
float32
NiceNum( float32 f32X, bool bRound )
{
	float32	f32Expt;
	float32	f32F;
	float32	f32Nf;

	f32Expt = floor( log10( f32X ) );
	f32F = f32X / pow( 10.0, f32Expt );

	if( bRound ) {
		if( f32F < 1.5 )
			f32Nf = 1.0;
		else if( f32F < 3.0 )
			f32Nf = 2;
		else if( f32F < 7.0 )
			f32Nf = 5.0;
		else
			f32Nf = 10.0;
	} else {
		if( f32F <= 1.0 )
			f32Nf = 1.0;
		else if( f32F <= 2.0 )
			f32Nf = 2;
		else if( f32F <= 5.0 )
			f32Nf = 5.0;
		else
			f32Nf = 10.0;
	}

	return f32Nf * pow( 10.0, f32Expt );
}


void CDemopajaView::OnDraw( CDC* pDC )
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	//
	// If the device driver is not present, print error.
	//
	if( !pGraphDevice ) {
		CRect	rRect;
		GetClientRect( rRect );
		pDC->FillSolidRect( rRect, RGB( 128, 128, 128 ) );
		pDC->TextOut( 10, 10, "*" );
		return;
	}


	float32	f32LayoutWidth = (float32)pDoc->GetLayoutWidth();
	float32	f32LayoutHeight = (float32)pDoc->GetLayoutHeight();

	BBox2C				rLayout( Vector2C( 0, 0 ), Vector2C( f32LayoutWidth, f32LayoutHeight ) );

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	GUIDrawInterfaceI*	pGUI = (GUIDrawInterfaceI*)pGraphDevice->query_interface( GRAPHICSDEVICE_GUIDRAW_INTERFACE );

	pDoc->IncFrameID();

	pViewport->set_viewport( pDoc->GetSceneViewport() );
	pViewport->set_layout( rLayout );

	SceneC*	pScene = pDoc->GetCurrentScene();
	DemoInterfaceC*	pDemoInterface = pDoc->GetDemoInterface();

	int32		i32NumSel = 0;
	int32		i, j, k;

	bool		bFirstBBox = true;
	m_rSelBBox = BBox2C( Vector2C( 0, 0 ), Vector2C( 0, 0 ) );


	pGraphDevice->activate();

	// Clear device
	pGraphDevice->clear_device( GRAPHICSDEVICE_ALLBUFFERS, ColorC( 0.3f, 0.3f, 0.3f, 1.0f ) );

	if( !pGraphDevice->begin_draw() )
		return;

	// Begin layout drawing
	pGUI->begin_layout();

	// draw layout box (black)
	pGUI->draw_layout( pScene->get_layout_color() );


	// draw ruler

	if( pDoc->GetDrawRulers() ) {

		BBox2C	rViewport = pDoc->GetSceneViewport();

		{
			float32	f32Y;
			CString	sLabel;

			float32		f32Range = NiceNum( rViewport[1][1] - rViewport[0][1], false );
			float32		f32Delta = NiceNum( f32Range / (float32)(pViewport->get_height() / 30), true );

			float32		f32GraphMin = (float32)floor( rViewport[0][1] / f32Delta ) * f32Delta;
			float32		f32GraphMax = (float32)ceil( rViewport[1][1] / f32Delta ) * f32Delta;
			int32		i32NumFrac = (int32)__max( -floor( log10( f32Delta ) ), 0 );

			// make the printf format string
			char		szFormat[16];
			_snprintf( szFormat, 15, "%%.%df", i32NumFrac );


			Vector2C	rPos( rViewport[0] );
			Vector2C	rXAxis = pViewport->delta_client_to_layout( Vector2C( 6, 0 ) );
			Vector2C	rLabelPos = pViewport->delta_client_to_layout( Vector2C( 3, 3 ) );

			pGUI->set_color( ColorC( 0.7f, 0.7f, 0.7f ) );

			for( f32Y = f32GraphMin; f32Y < f32GraphMax + f32Delta; f32Y += f32Delta ) {
				rPos[1] = f32Y;
				pGUI->draw_line( rPos, rPos + rXAxis );

				sLabel.Format( szFormat, f32Y );
				pGUI->draw_text( rPos + rLabelPos, sLabel );
			}
		}


		{
			float32	f32X;
			CString	sLabel;

			float32		f32Range = NiceNum( rViewport[1][0] - rViewport[0][0], false );
			float32		f32Delta = NiceNum( f32Range / (float32)(pViewport->get_width() / 30), true );

			float32		f32GraphMin = (float32)floor( rViewport[0][0] / f32Delta ) * f32Delta;
			float32		f32GraphMax = (float32)ceil( rViewport[1][0] / f32Delta ) * f32Delta;
			int32		i32NumFrac = (int32)__max( -floor( log10( f32Delta ) ), 0 );

			// make the printf format string
			char		szFormat[16];
			_snprintf( szFormat, 15, "%%.%df", i32NumFrac );


			Vector2C	rPos( rViewport[0] );
			Vector2C	rYAxis = pViewport->delta_client_to_layout( Vector2C( 0, 6 ) );
			Vector2C	rLabelPos = pViewport->delta_client_to_layout( Vector2C( 3, 3 ) );

			pGUI->set_color( ColorC( 0.7f, 0.7f, 0.7f ) );

			for( f32X = f32GraphMin; f32X < f32GraphMax + f32Delta; f32X += f32Delta ) {
				rPos[0] = f32X;
				pGUI->draw_line( rPos, rPos + rYAxis );

				sLabel.Format( szFormat, f32X );
				pGUI->draw_text( rPos + rLabelPos, sLabel );
			}
		}
	}

	//
	// draw effects
	//

	// Start drawing effects
	pGraphDevice->begin_effects();


	int32	i32Time = pDoc->GetTimecursor();

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

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

		if( !pLayer )
			continue;

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

		if( !(pLayer->get_flags() & ITEM_VISIBLE) )
			continue;

		// Make sure there's enough space for all the layers in the stack.
		if( m_rEffectStack.empty() || m_rEffectStack.size() < pLayer->get_effect_count() )
		{
			m_rEffectStack.resize( pLayer->get_effect_count() + 1 );
			m_rEffectTimeStack.resize( pLayer->get_effect_count() + 1 );
		}

		// Init stack
		for( j = 0; j < (int32)m_rEffectStack.size(); j++ )
		{
			m_rEffectStack[j] = 0;
			m_rEffectTimeStack[j] = 0;
		}

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

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

			if( !pEffect )
				continue;

			if( pEffect->get_flags() & ITEM_SELECTED )
				i32NumSel++;

			if( !(pEffect->get_flags() & ITEM_VISIBLE) )
				continue;

			if( pEffect->get_timesegment()->is_visible( i32Time - i32TimeOffset ) )
			{
				if( pEffect->get_super_class_id() == SUPERCLASS_EFFECT_POST_PROCESS )
				{
					EffectPostProcessI*	pPostProcess = (EffectPostProcessI*)pEffect;

					// use render target instead
					GraphicsBufferI*	pOldGBuffer = 0;
					GraphicsBufferI*	pGBuffer = pPostProcess->get_render_target();

					if( !pGBuffer )
					{
						TRACE( "no buffer\n" );
						continue;
					}

					pOldGBuffer = pGraphDevice->set_render_target( pGBuffer );

					if( pGBuffer->get_state() == DEVICE_STATE_LOST )
					{
						pGraphDevice->set_render_target( pOldGBuffer );
						TRACE( "buffer lost\n" );
						continue;
					}

					GraphicsViewportI*	pBufViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
					if( !pBufViewport )
					{
						pGraphDevice->set_render_target( pOldGBuffer );
						TRACE( "no viewport\n" );
						return;
					}

					// Save current layout and viewport
					BBox2C	rOldLayout = pBufViewport->get_layout();
					BBox2C	rOldViewport = pBufViewport->get_viewport();
					float32	f32OldAspect = pBufViewport->get_pixel_aspect_ratio();

					// Set new based on the scene effects bounding box
					BBox2C	rNewLayout( Vector2C( 0, 0 ), Vector2C( (float32)pScene->get_layout_width(), (float32)pScene->get_layout_height() ) );
					BBox2C	rNewViewport( Vector2C( 0, 0 ), Vector2C( (float32)pScene->get_layout_width(), (float32)pScene->get_layout_height() ) );
					BBox2C	rOldClipLayout;


					// set newly calculated layout viewport
					// the viewport is set after begin_draw() because begin draw saves the viewport and projection matrices.
					pBufViewport->set_layout( rNewLayout );
					pBufViewport->set_viewport( rNewViewport );

					// Set correct pixels size.
					// It's often that the graphics buffer has different aspect ratio than the rendering
					// output is. Problems arise on perspective projection etc. The following code
					// handles the situation.
					float32	f32AspectVP = (float32)pBufViewport->get_height() / (float32)pBufViewport->get_width();
					float32	f32AspectHW = (float32)pScene->get_layout_height() / (float32)pScene->get_layout_width();
					pBufViewport->set_pixel_aspect_ratio( f32AspectHW / f32AspectVP  );


					// clear device
					ColorC	BGCol = pScene->get_layout_color();
					BGCol[3] = 0.0f;
					pGraphDevice->clear_device( GRAPHICSDEVICE_ALLBUFFERS, BGCol );

					pGraphDevice->begin_draw();

					//
					// draw effects
					//

					pGraphDevice->begin_effects();

					// Render all effects in the stack into the effect's render target.
					for( k = 0; k < i32StackHead; k++ )
					{
						if( m_rEffectStack[k] )
						{
							EffectI*	pFX = m_rEffectStack[k];
							pFX->eval_state( m_rEffectTimeStack[k] );
							if( pFX->get_flags() & ITEM_SELECTED )
							{
								BBox2C	rEffectBBox = pFX->get_bbox();
								if( rEffectBBox.width() != 0.0f && rEffectBBox.height() != 0.0f )
								{
									// collect selected bounds
									if( bFirstBBox )
									{
										m_rSelBBox = rEffectBBox;
										bFirstBBox = false;
									}
									else
										m_rSelBBox = m_rSelBBox.combine( rEffectBBox );
								}
							}
						}
					}

					// restore old layout and viewport
					pBufViewport->set_layout( rOldLayout );
					pBufViewport->set_viewport( rOldViewport );
					pBufViewport->set_pixel_aspect_ratio( f32OldAspect );

					pGraphDevice->end_effects();
					pGraphDevice->end_draw();

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

					pGraphDevice->set_render_target( pOldGBuffer );

					// Reset stack.
					i32StackHead = 0;
				}

				m_rEffectStack[i32StackHead] = pEffect;
				m_rEffectTimeStack[i32StackHead] = i32Time - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start());
				i32StackHead++;
			}
		}

		// Flush the stack.
		for( k = 0; k < i32StackHead; k++ )
		{
			if( m_rEffectStack[k] )
			{
				EffectI*	pFX = m_rEffectStack[k];
				pFX->eval_state( m_rEffectTimeStack[k] );
				if( pFX->get_flags() & ITEM_SELECTED )
				{
					BBox2C	rEffectBBox = pFX->get_bbox();
					if( rEffectBBox.width() != 0.0f && rEffectBBox.height() != 0.0f )
					{
						// collect selected bounds
						if( bFirstBBox )
						{
							m_rSelBBox = rEffectBBox;
							bFirstBBox = false;
						}
						else
							m_rSelBBox = m_rSelBBox.combine( rEffectBBox );
					}
				}
			}
		}
	}

	// End drawing effects
	pGraphDevice->end_effects();


	// Draw effect UIs
	for( i = pScene->get_layer_count() - 1; i >= 0 ; i-- ) {

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

		if( !pLayer )
			continue;

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

		if( !(pLayer->get_flags() & ITEM_VISIBLE) )
			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_flags() & ITEM_SELECTED )
				i32NumSel++;

			if( !(pEffect->get_flags() & ITEM_VISIBLE) )
				continue;

			if( pEffect->get_timesegment()->is_visible( i32Time - i32TimeOffset ) )
				pEffect->draw_ui( 0 );
		}
	}


	if( m_bStoreOrigSelBBox ) {
		m_rOrigSelBBox = m_rSelBBox;
		m_bStoreOrigSelBBox = false;
	}

	//
	// draw bounding boxes
	//

	// Drawing colors
	ColorC	rColFocus;
	ColorC	rColHi;
	ColorC	rColGrid;
	rColFocus.convert_from_uint8( 160, 160, 255 );
	rColHi.convert_from_uint8( 60, 60, 128 );
	rColGrid.convert_from_uint8( 160, 160, 255, 128 );

	//
	// draw selection bounding box
	//

	if( m_ui32CurTool != TOOL_ARROW && m_rSelBBox.width() != 0.0f && m_rSelBBox.height() != 0.0f ) {

		pGUI->set_point_size( 3 );
		pGUI->set_color( rColHi );

		BBox2C	rBox = m_rSelBBox;

		// draw effect hilight
		pGUI->draw_box( rBox[0], rBox[1] );

		// draw corners
		pGUI->draw_point( Vector2C( rBox[0][0], rBox[0][1] ) );
		pGUI->draw_point( Vector2C( rBox[1][0], rBox[0][1] ) );
		pGUI->draw_point( Vector2C( rBox[1][0], rBox[1][1] ) );
		pGUI->draw_point( Vector2C( rBox[0][0], rBox[1][1] ) );
	}

	//
	// draw bounding boxes and parameters
	//

	// calc pixel size in layout
	Vector2C	rSize( 1, 1 );
	rSize = pViewport->delta_client_to_layout( rSize );


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

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

		if( !pLayer )
			continue;

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

		bool	bLayerLocked = false;
		if( pLayer->get_flags() & ITEM_LOCKED )
			bLayerLocked = true;

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

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

			if( !pEffect )
				continue;

			if( !(pEffect->get_flags() & ITEM_SELECTED) )
				continue;

			if( !pEffect->get_timesegment()->is_visible( i32Time - i32TimeOffset ) )
				continue;

			BBox2C	rEffectBBox = pEffect->get_bbox();

			// Skip empty effects
			if( rEffectBBox.width() == 0.0f && rEffectBBox.height() == 0.0f )
				continue;

			ParamFloatC*	pParamRot = (ParamFloatC*)pEffect->get_default_param( DEFAULT_PARAM_ROTATION );
			ParamVector2C*	pParamPos = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_POSITION );
			ParamVector2C*	pParamPivot = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_PIVOT );

			Matrix2C	rTM = pEffect->get_transform_matrix();
			Vector2C	rPos, rPivot;
			int32		i32ParamTime = i32Time - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start());

			if( pParamPos )
				pParamPos->get_val( i32ParamTime, rPos );
			if( pParamPivot )
				pParamPivot->get_val( i32ParamTime, rPivot );

			//
			// draw current position
			//
			pGUI->set_color( rColHi );
			pGUI->draw_line( Vector2C( rPos[0] - rSize[0] * 4.0f, rPos[1] ), Vector2C( rPos[0] + rSize[0] * 5.0f, rPos[1] ) );
			pGUI->draw_line( Vector2C( rPos[0], rPos[1] - rSize[0] * 4.0f ), Vector2C( rPos[0], rPos[1] + rSize[0] * 5.0f ) );


			//
			// draw position track
			//

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

				if( pCont ) {

					if( m_ui32CurTool == TOOL_KEYARROW )
						pGUI->set_color( rColFocus );
					else
						pGUI->set_color( rColHi );

					pGUI->set_point_size( 2 );

					int32		i32TimeStep = pDoc->GetFrameSizeInTicks();
					Vector2C	rKeyPos;

					for( int32 i32ContTime = pCont->get_min_time(); i32ContTime < pCont->get_max_time(); i32ContTime += i32TimeStep ) {
						pCont->get_value( rKeyPos, i32ContTime );
						pGUI->draw_point( rKeyPos );
					}

					for( int32 k = 0; k < pCont->get_key_count(); k++ ) {
						FloatKeyC*	pKey = (FloatKeyC*)pCont->get_key( k );
						pCont->get_value( rKeyPos, pKey->get_time() );
						pGUI->draw_marker( rKeyPos, rSize[0] * 7.0f );

						// tangent test
/*						float32		f32Values[KEY_MAXCHANNELS];
						Vector2C	rInTangent, rOutTangent;

						pKey->get_out_tan( f32Values );
						rOutTangent[0] = f32Values[0];
						rOutTangent[1] = f32Values[1];

						pKey->get_in_tan( f32Values );
						rInTangent[0] = f32Values[0];
						rInTangent[1] = f32Values[1];

						// convert hermite tangents to bezier control points
						rOutTangent = -3.0f * rKeyPos + 3.0f * rOutTangent;
						rInTangent = -3.0f * rInTangent + 3.0f * rKeyPos;

						pGUI->draw_line( rKeyPos, rOutTangent );
						pGUI->draw_line( rKeyPos, rInTangent );*/
					}

				}
			}

			//
			// draw other position curves
			//
			if( m_ui32CurTool == TOOL_KEYARROW ) {
				for( uint32 k = 0; k < pEffect->get_gizmo_count(); k++ ) {
					GizmoI*	pGizmo = pEffect->get_gizmo( k );
					for( uint32 m = 0; m <  pGizmo->get_parameter_count(); m++ ) {
						ParamI*	pParam = pGizmo->get_parameter( m );

						if( pParam->get_style() & PARAM_STYLE_REL_POSITION ) {
							ParamVector2C*	pPosParam = (ParamVector2C*)pParam;

							if( pPosParam->get_style() & PARAM_STYLE_WORLD_SPACE ) {

								if( m_ui32CurTool == TOOL_KEYARROW )
									pGUI->set_color( rColFocus );
								else
									pGUI->set_color( rColHi );

								ControllerC*	pPosCont = pPosParam->get_controller();
								Vector2C		rKeyOffset = rPos;

								if( pPosParam == pParamPivot )
									rKeyOffset -= rPivot;		// special case for pivot

								if( pPosCont ) {

									pGUI->set_point_size( 2 );

									// draw curve
									int32		i32TimeStep = pDoc->GetFrameSizeInTicks();
									Vector2C	rKeyPos;

									for( int32 i32ContTime = pPosCont->get_min_time(); i32ContTime < pPosCont->get_max_time(); i32ContTime += i32TimeStep ) {
										pPosCont->get_value( rKeyPos, i32ContTime );
										rKeyPos += rKeyOffset; 
										pGUI->draw_point( rKeyPos );
									}

									// draw keys
									for( int32 k = 0; k < pPosCont->get_key_count(); k++ ) {
										KeyC*	pKey = pPosCont->get_key( k );
										pPosCont->get_value( rKeyPos, pKey->get_time() );
										rKeyPos += rKeyOffset; 
										pGUI->draw_marker( rKeyPos, rSize[0] * 7.0f );
									}
								
								}

								// draw current pos

								Vector2C	rKeyPos;
								pPosParam->get_val( i32ParamTime, rKeyPos );
								rKeyPos += rKeyOffset; 

								pGUI->draw_marker( rKeyPos, rSize[0] * 7.0f );

								std::string	sStr = pGizmo->get_name();
								sStr += ' ';
								sStr += pPosParam->get_name();

								pGUI->draw_text( Vector2C( rKeyPos[0] + rSize[0] * 9.0f, rKeyPos[1] + rSize[0] * 3.0f ), sStr.c_str() );

							}
							else if( pPosParam->get_style() & PARAM_STYLE_OBJECT_SPACE ) {

								if( m_ui32CurTool == TOOL_KEYARROW )
									pGUI->set_color( rColFocus );
								else
									pGUI->set_color( rColHi );

								ControllerC*	pPosCont = pPosParam->get_controller();

								if( pPosCont ) {

									// draw curve
									pGUI->set_point_size( 2 );

									int32		i32TimeStep = pDoc->GetFrameSizeInTicks();
									Vector2C	rKeyPos;

									for( int32 i32ContTime = pPosCont->get_min_time(); i32ContTime < pPosCont->get_max_time(); i32ContTime += i32TimeStep ) {
										pPosCont->get_value( rKeyPos, i32ContTime );
										rKeyPos *= rTM; 
										pGUI->draw_point( rKeyPos );
									}

									// draw keys
									for( int32 k = 0; k < pPosCont->get_key_count(); k++ ) {
										KeyC*	pKey = pPosCont->get_key( k );
										pPosCont->get_value( rKeyPos, pKey->get_time() );
										rKeyPos *= rTM; 
//										rKeyPos += rPos; 
										pGUI->draw_marker( rKeyPos, rSize[0] * 7.0f );
									}
								}

								// draw current pos
								Vector2C	rKeyPos;
								pPosParam->get_val( i32ParamTime, rKeyPos );
								rKeyPos *= rTM; 

								pGUI->draw_marker( rKeyPos, rSize[0] * 7.0f );

								std::string	sStr = pGizmo->get_name();
								sStr += ' ';
								sStr += pPosParam->get_name();

								pGUI->draw_text( Vector2C( rKeyPos[0] + rSize[0] * 9.0f, rKeyPos[1] + rSize[0] * 3.0f ), sStr.c_str() );
							}

						}
					}
				}
			}

			//
			// draw bounding box
			//

			if( m_ui32CurTool == TOOL_SCALE )
				pGUI->set_point_size( 5 );
			else
				pGUI->set_point_size( 3 );

			if( m_ui32CurTool == TOOL_ARROW )
				pGUI->set_color( rColHi );
			else
				pGUI->set_color( rColFocus );

			// draw effect hilight
			pGUI->draw_box( rEffectBBox[0], rEffectBBox[1] );

			// draw hi-lighted corners for locked objects
			if( bLayerLocked || pEffect->get_flags() & ITEM_LOCKED )
				pGUI->set_color( ColorC( 1, 1, 0.6f ) );
			else {
				if( m_ui32CurTool == TOOL_ARROW )
					pGUI->set_color( rColHi );
				else
					pGUI->set_color( rColFocus );
			}

			// draw corners
			pGUI->draw_point( Vector2C( rEffectBBox[0][0], rEffectBBox[0][1] ) );
			pGUI->draw_point( Vector2C( rEffectBBox[1][0], rEffectBBox[0][1] ) );
			pGUI->draw_point( Vector2C( rEffectBBox[1][0], rEffectBBox[1][1] ) );
			pGUI->draw_point( Vector2C( rEffectBBox[0][0], rEffectBBox[1][1] ) );

			if( m_ui32CurTool == TOOL_SCALE ) {
				// middle edges
				pGUI->draw_point( Vector2C( rEffectBBox[0][0], (rEffectBBox[0][1] + rEffectBBox[1][1]) * 0.5f ) );
				pGUI->draw_point( Vector2C( rEffectBBox[1][0], (rEffectBBox[0][1] + rEffectBBox[1][1]) * 0.5f ) );
				pGUI->draw_point( Vector2C( (rEffectBBox[0][0] + rEffectBBox[1][0]) * 0.5f, rEffectBBox[0][1] ) );
				pGUI->draw_point( Vector2C( (rEffectBBox[0][0] + rEffectBBox[1][0]) * 0.5f, rEffectBBox[1][1] ) );
			}

			if( m_ui32CurTool == TOOL_ARROW )
				pGUI->set_color( rColHi );
			else
				pGUI->set_color( rColFocus );

			//
			// draw pivot & rot handle
			//

			float32	f32Size = __min( rEffectBBox.width(), rEffectBBox.height() ) / 3.0f;

			if( m_ui32CurTool == TOOL_ROTATE ) {

				float32	f32Angle = 0;


				// draw pivot
				pGUI->set_point_size( 5 );
				pGUI->draw_marker( rPos, rSize[0] * 7.0f );

				if( pParamRot ) {
					pParamRot->get_val( i32Time, f32Angle );
					f32Angle *= (float32)M_PI / 180.0f;
					Vector2C	rRotPos( (float32)cos( f32Angle ) * f32Size, (float32)sin( f32Angle ) * f32Size );

					rRotPos += rPos;

					// rotation handle
					pGUI->draw_line( rPos, rRotPos );
					pGUI->draw_point( rRotPos );
				}
			}

		}
	}


	if( m_ui32CurTool == TOOL_ARROW && m_rSelBBox.width() != 0.0f && m_rSelBBox.height() != 0.0f ) {

		//glColor3ubv( ui8ColFocus );
		pGUI->set_color( rColFocus );
		pGUI->set_point_size( 5 );

		BBox2C	rBox = m_rSelBBox;

		// draw effect hilight
		pGUI->draw_box( rBox[0], rBox[1] );

		// draw corners
		pGUI->draw_point( Vector2C( rBox[0][0], rBox[0][1] ) );
		pGUI->draw_point( Vector2C( rBox[1][0], rBox[0][1] ) );
		pGUI->draw_point( Vector2C( rBox[1][0], rBox[1][1] ) );
		pGUI->draw_point( Vector2C( rBox[0][0], rBox[1][1] ) );

		// middle edges
		pGUI->draw_point( Vector2C( rBox[0][0], (rBox[0][1] + rBox[1][1]) * 0.5f ) );	// left
		pGUI->draw_point( Vector2C( rBox[1][0], (rBox[0][1] + rBox[1][1]) * 0.5f ) );	// right
		pGUI->draw_point( Vector2C( (rBox[0][0] + rBox[1][0]) * 0.5f, rBox[0][1] ) );	// bottom
		pGUI->draw_point( Vector2C( (rBox[0][0] + rBox[1][0]) * 0.5f, rBox[1][1] ) );	// top
	}


	//
	// draw grid
	//

	if( pDoc->GetDrawGrid() ) {
		pGUI->set_color( rColGrid );
		pGUI->draw_grid( (float32)pDoc->GetLayoutWidth(), (float32)pDoc->GetLayoutHeight(), (float32)pDoc->GetGridSize() );
	}


	// draw selection box
	if( m_eTrackingAction == TRACKING_BOX_SELECT ) {

		Vector2C	rStartPos = pViewport->client_to_layout( Vector2C( (float32)m_rStartMousePos.x, (float32)pViewport->get_height() - (float32)m_rStartMousePos.y ) );
		Vector2C	rEndPos = pViewport->client_to_layout( Vector2C( (float32)m_rEndMousePos.x, (float32)pViewport->get_height() - (float32)m_rEndMousePos.y ) );

		pGUI->draw_selection_box( rStartPos, rEndPos );
	}

	pGUI->end_layout();

	pGraphDevice->end_draw();

	pGraphDevice->flush();
}


/////////////////////////////////////////////////////////////////////////////
// CDemopajaView diagnostics

#ifdef _DEBUG
void CDemopajaView::AssertValid() const
{
	CView::AssertValid();
}

void CDemopajaView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}

CDemopajaDoc* CDemopajaView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDemopajaDoc)));
	return (CDemopajaDoc*)m_pDocument;
}
#endif //_DEBUG






void CDemopajaView::OnDestroy() 
{
	if( m_ui32TimerID )
		KillTimer( m_ui32TimerID );

	CView::OnDestroy();
}

void CDemopajaView::OnSize( UINT nType, int cx, int cy )
{
	CView::OnSize(nType, cx, cy);

	if( cx == 0 || cy == 0 )
		return;

	CDemopajaDoc*		pDoc = GetDocument();
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	// resize device
	if( pGraphDevice ) {
		GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

		Vector2C	rDelta( (float32)(cx - pViewport->get_width()) / 2.0f, (float32)(cy - pViewport->get_height()) / 2.0f );
		rDelta[0] /= (float32)pViewport->get_width();
		rDelta[1] /= (float32)pViewport->get_height();
		pDoc->AdjustSceneViewports( rDelta );

		pGraphDevice->set_size( 0, 0, cx, cy );
	}

	// The view was reset before the window was correctly resized, reset the view again.
	if( m_bViewResetRequest ) {
		m_bViewResetRequest = false;
		ResetView();
	}
}


BOOL CDemopajaView::OnEraseBkgnd( CDC* pDC )
{
	return TRUE;
}

void CDemopajaView::NudgeEffects( const PajaTypes::Vector2C& rDelta )
{
	CDemopajaDoc*	pDoc = GetDocument();

	UndoC*	pUndo = new UndoC( "Nudge Position" );
	UndoC*	pOldUndo;

	SceneC*		pScene = pDoc->GetCurrentScene();
	int32		i32Time = pDoc->GetTimecursor();

	int32	i32Count = 0;

	for( uint32 i = 0; i < pScene->get_layer_count(); i++ ) {
		LayerC*	pLayer = pScene->get_layer( i );
		if( !pLayer )
			continue;
		if( !(pLayer->get_flags() & ITEM_VISIBLE) )
			continue;
		if( pLayer->get_flags() & ITEM_LOCKED )
			continue;

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

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

			int32			i32ParamTime = i32Time - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start());

			ParamI*	pParam = pEffect->get_default_param( DEFAULT_PARAM_POSITION );

			if( pParam && pParam->get_type() == PARAM_TYPE_VECTOR2 ) {
				Vector2C	rPos;
				ParamVector2C*	pPosParam = (ParamVector2C*)pParam;
				pOldUndo = pPosParam->begin_editing( pUndo );
				pPosParam->get_val( i32ParamTime, rPos );
				rPos += rDelta;
				pDoc->HandleParamNotify( pPosParam->set_val( i32ParamTime, rPos ) );	// create key at the timecursor time.
				pPosParam->end_editing( pOldUndo );
				i32Count++;
			}
		}
	}

	if( i32Count ) {
		pDoc->GetUndoManager()->push( pUndo );
		pDoc->SetModifiedFlag();
	}
	else
		delete pUndo;
}

void CDemopajaView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CDemopajaDoc*	pDoc = GetDocument();

	m_bSpacePressed = (::GetAsyncKeyState( VK_SPACE ) & 0x8000 ) != 0;
	m_bCtrlPressed = (::GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) != 0;
	bool	bShiftPressed = (::GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) != 0;

	if( m_eTrackingAction != TRACKING_NONE )
		CView::OnKeyDown(nChar, nRepCnt, nFlags);;

	if( m_bSpacePressed && !m_bCtrlPressed ) {
		// pan
		m_hCursor = AfxGetApp()->LoadCursor( IDC_DPHAND );
		::SetCursor( m_hCursor );
		return;
	}
	else if( m_bSpacePressed && m_bCtrlPressed ) {
		// zoom
		m_hCursor = AfxGetApp()->LoadCursor( IDC_ZOOM );
		::SetCursor( m_hCursor );
		return;
	}

//	if( m_ui32CurTool == TOOL_ARROW && m_bCtrlPressed )
//		m_ui32CurTool = TOOL_KEYARROW;

	if( nChar == VK_RETURN ) {
		CMainFrame*	pMain = (CMainFrame*)AfxGetMainWnd();
		pMain->GetTimelineBar()->OnPlay();
	}
	else if( nChar == VK_DELETE ) {
		pDoc->EffectDeleteSelected();
		pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
	}
	else if( nChar == VK_ESCAPE ) {
		pDoc->DelFlagsAll( ITEM_EFFECT, ITEM_SELECTED );
//		pDoc->LayerlistSelectionChanged();
		pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
	}
	else if( nChar == 'V' ) {
		pDoc->SetTool( TOOL_ARROW );
	}
	else if( nChar == 'R' ) {
		pDoc->SetTool( TOOL_ROTATE );
	}
	else if( nChar == 'S' ) {
		pDoc->SetTool( TOOL_SCALE );
	}
	else if( nChar == 'Z' ) {
		pDoc->SetTool( TOOL_ZOOM );
	}
	else if( nChar == 'H' ) {
		pDoc->SetTool( TOOL_PAN );
	}
	else if( nChar == 'A' ) {
		pDoc->SetTool( TOOL_KEYARROW );
	}
	else if( nChar == VK_UP ) {
		if( bShiftPressed )
			NudgeEffects( Vector2C( 0, 10 ) );
		else
			NudgeEffects( Vector2C( 0, 1 ) );
		pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
	}
	else if( nChar == VK_DOWN ) {
		if( bShiftPressed )
			NudgeEffects( Vector2C( 0, -10 ) );
		else
			NudgeEffects( Vector2C( 0, -1 ) );
		pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
	}
	else if( nChar == VK_LEFT ) {
		if( bShiftPressed )
			NudgeEffects( Vector2C( -10, 0 ) );
		else
			NudgeEffects( Vector2C( -1, 0 ) );
		pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
	}
	else if( nChar == VK_RIGHT ) {
		if( bShiftPressed )
			NudgeEffects( Vector2C( 10, 0 ) );
		else
			NudgeEffects( Vector2C( 1, 0 ) );
		pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
	}

	CView::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CDemopajaView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	bool	bSpaceWasPressed = m_bSpacePressed;
	m_bSpacePressed = (::GetAsyncKeyState( VK_SPACE ) & 0x8000 ) != 0;
	m_bCtrlPressed = (::GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) != 0;

	if( !m_bSpacePressed && bSpaceWasPressed ) {
		m_hCursor = 0;
		::SetCursor( ::LoadCursor( NULL, IDC_ARROW ) );
		return;
	}
	else if( m_bSpacePressed && !m_bCtrlPressed ) {
		// pan
		m_hCursor = AfxGetApp()->LoadCursor( IDC_DPHAND );
		::SetCursor( m_hCursor );
		return;
	}
	else if( m_bSpacePressed && m_bCtrlPressed ) {
		// zoom
		m_hCursor = AfxGetApp()->LoadCursor( IDC_ZOOM );
		::SetCursor( m_hCursor );
		return;
	}
	
	CView::OnKeyUp(nChar, nRepCnt, nFlags);
}

BOOL CDemopajaView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
	if( m_hCursor ) {
		::SetCursor( m_hCursor );
		return TRUE;
	}
	
	return CView::OnSetCursor(pWnd, nHitTest, message);
}


void CDemopajaView::BoxSelect( const BBox2C& rSelBox, bool bToggle )
{
	CDemopajaDoc*	pDoc = GetDocument();

	SceneC*	pScene = pDoc->GetCurrentScene();
	int32	i32Time = pDoc->GetTimecursor();

	if( !bToggle )
		pDoc->DelFlagsAll( ITEM_EFFECT, ITEM_SELECTED );

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

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

		if( !pLayer )
			continue;
		if( !(pLayer->get_flags() & ITEM_VISIBLE) )
			continue;
		if( pLayer->get_flags() & ITEM_LOCKED )
			continue;
		if( !pLayer->get_timesegment()->is_visible( i32Time ) )
			continue;

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

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

			if( !pEffect )
				continue;
			if( !(pEffect->get_flags() & ITEM_VISIBLE) )
				continue;
			if( pEffect->get_flags() & ITEM_LOCKED )
				continue;
			if( !pEffect->get_timesegment()->is_visible( i32Time - i32TimeOffset ) )
				continue;

			BBox2C		rEffectBBox = pEffect->get_bbox();

			// Skip empty effects
			if( rEffectBBox.width() == 0.0f && rEffectBBox.height() == 0.0f )
				continue;

			rEffectBBox = rEffectBBox.normalize();

			if( rSelBox.contains( rEffectBBox ) ) {
				if( bToggle )
					pEffect->toggle_flags( ITEM_SELECTED );
				else
					pEffect->add_flags( ITEM_SELECTED );
			}
		}
	}

}


int32 CDemopajaView::HitTestEffects( Vector2C rPoint, EffectI** pSelEffect, ParamI** pHitParam, Vector2C* pOriginPt, Vector2C* pHitPt, int32* pTime, bool bSelectNext )
{
	CDemopajaDoc*	pDoc = GetDocument();
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	SceneC*	pScene = pDoc->GetCurrentScene();
	int32	i32Time = pDoc->GetTimecursor();
	ParamI*		pHitPosParam = 0;
	Vector2C	rOrigin, rHit;
	int32		i32HitTime = 0;

	if( pSelEffect )
		*pSelEffect = 0;
	if( pOriginPt )
		*pOriginPt = Vector2C( 0, 0 );
	if( pHitPt )
		*pHitPt = Vector2C( 0, 0 );
	if( pHitParam )
		*pHitParam = 0;
	if( pTime )
		*pTime = 0;


	if( !pGraphDevice )
		return 0;

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

	Vector2C	rHandleSize = pViewport->delta_client_to_layout( Vector2C( 3, 3 ) );
	Vector2C	rKeySize = pViewport->delta_client_to_layout( Vector2C( 7, 7 ) );


	std::vector<SelRecordS>	rSelected;

	// for arrow tool check forst the selected bbox
	if( m_ui32CurTool == TOOL_ARROW ) {
		Vector2C	rHandlePos[8];
		int32		i32HitLut[8] = {
			HIT_SCALE_LEFT | HIT_SCALE_BOTTOM,
			HIT_SCALE_RIGHT | HIT_SCALE_BOTTOM,
			HIT_SCALE_RIGHT | HIT_SCALE_TOP,
			HIT_SCALE_LEFT | HIT_SCALE_TOP,
			HIT_SCALE_LEFT,
			HIT_SCALE_RIGHT,
			HIT_SCALE_BOTTOM,
			HIT_SCALE_TOP,
		};

		rHandlePos[0] = Vector2C( m_rSelBBox[0][0], m_rSelBBox[0][1] );	// bottom-left
		rHandlePos[1] = Vector2C( m_rSelBBox[1][0], m_rSelBBox[0][1] );	// bottom-right
		rHandlePos[2] = Vector2C( m_rSelBBox[1][0], m_rSelBBox[1][1] );	// top-right
		rHandlePos[3] = Vector2C( m_rSelBBox[0][0], m_rSelBBox[1][1] );	// top-left
		rHandlePos[4] = Vector2C( m_rSelBBox[0][0], (m_rSelBBox[0][1] + m_rSelBBox[1][1]) * 0.5f );	// left
		rHandlePos[5] = Vector2C( m_rSelBBox[1][0], (m_rSelBBox[0][1] + m_rSelBBox[1][1]) * 0.5f );	// right
		rHandlePos[6] = Vector2C( (m_rSelBBox[0][0] + m_rSelBBox[1][0]) * 0.5f, m_rSelBBox[0][1] );	// bottom
		rHandlePos[7] = Vector2C( (m_rSelBBox[0][0] + m_rSelBBox[1][0]) * 0.5f, m_rSelBBox[1][1] );	// top

		// origin is the opposite corner
		int32		i32OriginLut[8] = { 2, 3, 0, 1, 5, 4, 7, 6 };
		uint32		k;

		for( k = 0; k < 8; k++ ) {
			BBox2C	rHandleBox( rHandlePos[k] - rHandleSize, rHandlePos[k] + rHandleSize );
			if( rHandleBox.contains( rPoint ) ) {
				if( pHitPt )
					*pHitPt = rHandlePos[k];
				if( pOriginPt )
					*pOriginPt = rHandlePos[i32OriginLut[k]];
				return i32HitLut[k] | HIT_SEL_BBOX | HIT_SELECTED;
			}
		}
	}

	// loop twice... first time only check selected effects
	for( int32 i32Selected = 0; i32Selected < 2; i32Selected++ ) {

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

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

			if( !pLayer )
				continue;
			if( !(pLayer->get_flags() & ITEM_VISIBLE) )
				continue;
			if( pLayer->get_flags() & ITEM_LOCKED )
				continue;
			if( !pLayer->get_timesegment()->is_visible( i32Time ) )
				continue;

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

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

				if( !pEffect )
					continue;
				// at first round check only selected effects
				if( i32Selected == 0 && !(pEffect->get_flags() & ITEM_SELECTED) )
					continue;
				// at second round check only non-selected effects
				if( i32Selected == 1 && pEffect->get_flags() & ITEM_SELECTED )
					continue;
				if( !(pEffect->get_flags() & ITEM_VISIBLE) )
					continue;
				if( pEffect->get_flags() & ITEM_LOCKED )
					continue;
				if( !pEffect->get_timesegment()->is_visible( i32Time - i32LayerTimeOffset ) )
					continue;

				BBox2C		rEffectBBox = pEffect->get_bbox();

				// Skip empty effects
				if( rEffectBBox.width() == 0.0f && rEffectBBox.height() == 0.0f )
					continue;

				Matrix2C	rTM = pEffect->get_transform_matrix();
				int32		i32Hit = 0;

				//
				// test scale handles
				//

				if( m_ui32CurTool == TOOL_SCALE ) {
					Vector2C	rHandlePos[8];
					int32		i32HitLut[8] = {
						HIT_SCALE_LEFT | HIT_SCALE_BOTTOM,
						HIT_SCALE_RIGHT | HIT_SCALE_BOTTOM,
						HIT_SCALE_RIGHT | HIT_SCALE_TOP,
						HIT_SCALE_LEFT | HIT_SCALE_TOP,
						HIT_SCALE_LEFT,
						HIT_SCALE_RIGHT,
						HIT_SCALE_BOTTOM,
						HIT_SCALE_TOP,
					};

					rHandlePos[0] = Vector2C( rEffectBBox[0][0], rEffectBBox[0][1] );	// bottom-left
					rHandlePos[1] = Vector2C( rEffectBBox[1][0], rEffectBBox[0][1] );	// bottom-right
					rHandlePos[2] = Vector2C( rEffectBBox[1][0], rEffectBBox[1][1] );	// top-right
					rHandlePos[3] = Vector2C( rEffectBBox[0][0], rEffectBBox[1][1] );	// top-left
					rHandlePos[4] = Vector2C( rEffectBBox[0][0], (rEffectBBox[0][1] + rEffectBBox[1][1]) * 0.5f );	// left
					rHandlePos[5] = Vector2C( rEffectBBox[1][0], (rEffectBBox[0][1] + rEffectBBox[1][1]) * 0.5f );	// right
					rHandlePos[6] = Vector2C( (rEffectBBox[0][0] + rEffectBBox[1][0]) * 0.5f, rEffectBBox[0][1] );	// bottom
					rHandlePos[7] = Vector2C( (rEffectBBox[0][0] + rEffectBBox[1][0]) * 0.5f, rEffectBBox[1][1] );	// top

					// origin is the opposite corner
					int32		i32OriginLut[8] = { 2, 3, 0, 1, 5, 4, 7, 6 };
					uint32		k;

					for( k = 0; k < 8; k++ ) {
						BBox2C	rHandleBox( rHandlePos[k] - rHandleSize, rHandlePos[k] + rHandleSize );
						if( rHandleBox.contains( rPoint ) ) {
//							TRACE( "hit scale %d\n", k );
							i32Hit |= i32HitLut[k];
							rHit = rHandlePos[k];
							break;	// dont even try to hit test all the scale handles
						}
					}
				}

				if( pEffect->hit_test( rPoint ) )
					i32Hit |= HIT_MOVE;

				ParamFloatC*	pParamRot = (ParamFloatC*)pEffect->get_default_param( DEFAULT_PARAM_ROTATION );
				ParamVector2C*	pParamPos = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_POSITION );
				ParamVector2C*	pParamPivot = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_PIVOT );

				Vector2C	rPos, rPivot;
				int32		i32ParamTime = i32Time - (i32LayerTimeOffset + pEffect->get_timesegment()->get_segment_start());

				if( pParamPos )
					pParamPos->get_val( i32ParamTime, rPos );
				if( pParamPivot )
					pParamPivot->get_val( i32ParamTime, rPivot );

				if( m_ui32CurTool == TOOL_KEYARROW ) {

					//
					// check other position curves
					//
					for( uint32 k = 0; k < pEffect->get_gizmo_count(); k++ ) {
						GizmoI*	pGizmo = pEffect->get_gizmo( k );
						for( uint32 m = 0; m <  pGizmo->get_parameter_count(); m++ ) {
							ParamI*	pParam = pGizmo->get_parameter( m );

							if( pParam->get_style() & PARAM_STYLE_REL_POSITION ) {

								ParamVector2C*	pPosParam = (ParamVector2C*)pParam;
								Vector2C		rKeyPos;

								pPosParam->get_val( i32ParamTime, rKeyPos );

								if( pParamPivot == pPosParam )
									rKeyPos = rPos;		// special case for pivot
								else if( pPosParam->get_style() & PARAM_STYLE_WORLD_SPACE )
									rKeyPos += rPos; 
								else if( pPosParam->get_style() & PARAM_STYLE_OBJECT_SPACE )
									rKeyPos *= rTM; 

								BBox2C	rParamHandleBox( rKeyPos - rKeySize, rKeyPos + rKeySize );
								if( rParamHandleBox.contains( rPoint ) ) {
									i32Hit |= HIT_POS_PARAM;

									// we have special threatment for pivots
									if( pParamPivot == pPosParam ) {
//										TRACE( "is pivot\n" );
										i32Hit |= HIT_PIVOT;
									}

									rHit = rKeyPos;
									pHitPosParam = pPosParam;
								}

								//
								// check keys
								//
								ControllerC*	pCont = pPosParam->get_controller();

								if( pCont && pCont->get_key_count() ) {

									for( uint32 n = 0; n < pCont->get_key_count(); n++ ) {

										FloatKeyC*	pKey = (FloatKeyC*)pCont->get_key( n );
										float32	f32Values[KEY_MAXCHANNELS];

										pKey->get_value( f32Values );

										rKeyPos[0] = f32Values[0];
										rKeyPos[1] = f32Values[1];
										
										if( pParamPivot == pPosParam )
											rKeyPos += rPos - rPivot;		// special case for pivot
										else if( pPosParam->get_style() & PARAM_STYLE_WORLD_SPACE )
											rKeyPos += rPos; 
										else if( pPosParam->get_style() & PARAM_STYLE_OBJECT_SPACE )
											rKeyPos *= rTM; 

										BBox2C	rParamHandleBox( rKeyPos - rKeySize, rKeyPos + rKeySize );
										if( rParamHandleBox.contains( rPoint ) ) {
											i32Hit |= HIT_POS_PARAM_KEY;
											rHit = rKeyPos;
											pHitPosParam = pPosParam;
											i32HitTime = pKey->get_time();
//											TRACE( "TIME: %d\n", i32HitTime );
											break;
										}
									}
								}

							}
							else if( pParam->get_style() & PARAM_STYLE_ABS_POSITION ) {

								//
								// this is most likely the position parameter
								//

								ParamVector2C*	pPosParam = (ParamVector2C*)pParam;
								Vector2C		rKeyPos;

								//
								// check keys
								//
								ControllerC*	pCont = pPosParam->get_controller();

								if( pCont && pCont->get_key_count() ) {
									for( uint32 n = 0; n < pCont->get_key_count(); n++ ) {
										FloatKeyC*	pKey = (FloatKeyC*)pCont->get_key( n );
										float32	f32Values[KEY_MAXCHANNELS];

										pKey->get_value( f32Values );

										rKeyPos[0] = f32Values[0];
										rKeyPos[1] = f32Values[1];
										
										BBox2C	rParamHandleBox( rKeyPos - rKeySize, rKeyPos + rKeySize );
										if( rParamHandleBox.contains( rPoint ) ) {
											i32Hit |= HIT_POS_PARAM_KEY;
											rHit = rKeyPos;
											pHitPosParam = pPosParam;
											i32HitTime = pKey->get_time(); // + i32LayerTimeOffset + pEffect->get_timesegment()->get_segment_start();
											break;
										}
									}
								}

							}
						}
					}
				}

				float32	f32Size = __min( rEffectBBox.width(), rEffectBBox.height() ) / 3.0f;
				float32	f32Angle = 0;

				if( m_ui32CurTool == TOOL_ROTATE && pParamRot ) {
					pParamRot->get_val( i32ParamTime, f32Angle );
					f32Angle *= (float32)M_PI / 180.0f;
					Vector2C	rRotPos( (float32)cos( f32Angle ) * f32Size, (float32)sin( f32Angle ) * f32Size );

					rRotPos += rPos;;

					BBox2C	rRotHandleBox( rRotPos - rHandleSize, rRotPos + rHandleSize );
					if( rRotHandleBox.contains( rPoint ) ) {
//						TRACE( "hit rot\n" );
						i32Hit |= HIT_ROTATE;
						rHit = rRotPos;
					}
				}

				rOrigin = rPos; //rPivot;

				// check other position parameters


				// is this a possible hit candidate?
				if( i32Hit ) {
					if( pEffect->get_flags() & ITEM_SELECTED )
						i32Hit |= HIT_SELECTED;

					SelRecordS	rSel;
					
					rSel.m_i32Hit = i32Hit;
					rSel.m_pEffect = pEffect;
					rSel.m_pHitParam = pHitPosParam;
					rSel.m_rHit = rHit;
					rSel.m_rOrigin = rOrigin;
					rSel.m_i32Time = i32HitTime;

					rSelected.push_back( rSel );
				}
			}
		}
	}

	if( rSelected.size() ) {
		int32	i32Index = 0;

		if( bSelectNext ) {
			// find next not selected
			for( uint32 i = 0; i < rSelected.size(); i++ ) {
				if( !(rSelected[i].m_i32Hit & HIT_SELECTED) ) {
					i32Index = i;
					break;
				}
			}
		}

		if( pSelEffect )
			*pSelEffect = rSelected[i32Index].m_pEffect;
		if( pOriginPt )
			*pOriginPt = rSelected[i32Index].m_rOrigin;
		if( pHitPt )
			*pHitPt = rSelected[i32Index].m_rHit;
		if( pHitParam )
			*pHitParam = rSelected[i32Index].m_pHitParam;
		if( pTime )
			*pTime = rSelected[i32Index].m_i32Time;

//		TRACE( "SEL TIME: %d\n", *pTime );

		return rSelected[i32Index].m_i32Hit;
	}

	return 0;
}


void CDemopajaView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( !pGraphDevice ) {
		CView::OnLButtonDown(nFlags, point);
		return;
	}

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

	pDoc->SetEditFocus( FOCUS_VIEW );

	m_rOldMousePos = Vector2C( (float32)point.x, (float32)(pViewport->get_height() - point.y) );
	m_rStartMousePos = point;
	m_bStoreOrigSelBBox = true;

	// get the latest state
	m_bSpacePressed = (::GetAsyncKeyState( VK_SPACE ) & 0x8000 ) != 0;
	m_bCtrlPressed = (::GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) != 0;
	
	if( (m_bSpacePressed && !m_bCtrlPressed) || m_ui32CurTool == TOOL_PAN ) {
		// pan
		m_eTrackingAction = TRACKING_PAN;
		m_rOldViewport = pDoc->GetSceneViewport();
		SetCapture();
		return;
	}
	else if( (m_bSpacePressed && m_bCtrlPressed) || m_ui32CurTool == TOOL_ZOOM ) {
		// zoom
		m_eTrackingAction = TRACKING_ZOOM;
		m_rOldViewport = pDoc->GetSceneViewport();
		SetCapture();
		return;
	}
	else {
		Vector2C	rPos = pViewport->client_to_layout( m_rOldMousePos );

		SceneC*		pScene = pDoc->GetCurrentScene();
		EffectI*	pSelEffect;
		ParamI*		pHitPosParam;
		int32		i32Time = pDoc->GetTimecursor();
		uint32		i;
		int32		i32HitTime;
		bool		bSelNext = false;

		m_rOrigEffects.clear();

		if( nFlags & MK_CONTROL )
			bSelNext = true;

		int32 i32Hit = HitTestEffects( rPos, &pSelEffect, &pHitPosParam, &m_rEffectOrigin, &m_rEffectHit, &i32HitTime, bSelNext );

		m_i32LastHit = i32Hit;

		if( !i32Hit ) {
			// start box select
			m_rEndMousePos = point;
			m_eTrackingAction = TRACKING_BOX_SELECT;
			
			pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
			return;
		}

		for( i = 0; i < KEY_MAXCHANNELS; i++ )
			m_f32OrigValues[i] = 0;

		bool	bSelChange = false;

		if( !(nFlags & MK_SHIFT) && !(i32Hit & HIT_SELECTED) ) {
			pDoc->DelFlagsAll( ITEM_LAYER | ITEM_EFFECT | ITEM_GIZMO | ITEM_PARAMETER, ITEM_SELECTED );
			bSelChange = true;
		}

		if( !(nFlags & MK_SHIFT) && pSelEffect ) {
			pSelEffect->add_flags( ITEM_SELECTED );
			bSelChange = true;
		}

		if( nFlags & MK_SHIFT && pSelEffect ) {
			pSelEffect->toggle_flags( ITEM_SELECTED );
			bSelChange = true;
		}

//		if( bSelChange )
//			pDoc->LayerlistSelectionChanged();

		if( i32Hit & HIT_SEL_BBOX ) {
//			TRACE( "move & scale\n" );
			m_pCurrentUndo = new UndoC( "Artistic Scale Effect(s)" );
			m_eTrackingAction = TRACKING_SCALE_SELECTED;
			m_rOrigScaleSize = m_rEffectHit - m_rEffectOrigin;

			UndoC*	pOldUndo;

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

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

				if( !pLayer )
					continue;
				if( !(pLayer->get_flags() & ITEM_VISIBLE) )
					continue;
				if( pLayer->get_flags() & ITEM_LOCKED )
					continue;
				if( !pLayer->get_timesegment()->is_visible( i32Time ) )
					continue;

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

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

					if( !pEffect )
						continue;
					if( !(pEffect->get_flags() & ITEM_VISIBLE) )
						continue;
					if( pEffect->get_flags() & ITEM_LOCKED )
						continue;
					if( !pEffect->get_timesegment()->is_visible( i32Time - i32TimeOffset ) )
						continue;

					if( pEffect->get_flags() & ITEM_SELECTED ) {

						ParamVector2C*	pParamMove = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_POSITION );
						ParamVector2C*	pParamScale = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_SCALE );

						int32			i32ParamTime = i32Time - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start());

						SelEffectS	rSelFx;
						rSelFx.m_pEffect = pEffect;
						rSelFx.m_i32Time = i32ParamTime;

						// save undo stuff
						if( pParamMove ) {
							Vector2C	rPos;
							pOldUndo = pParamMove->begin_editing( m_pCurrentUndo );
							pParamMove->get_val( i32ParamTime, rPos );
							pDoc->HandleParamNotify( pParamMove->set_val( i32ParamTime, rPos ) );	// create key at the timecursor time.
							pParamMove->end_editing( pOldUndo );
							rSelFx.m_f32Values[0] = rPos[0];
							rSelFx.m_f32Values[1] = rPos[1];
						}

						if( pParamScale ) {
							Vector2C	rScale;
							pOldUndo = pParamScale->begin_editing( m_pCurrentUndo );
							pParamScale->get_val( i32ParamTime, rScale );
							pDoc->HandleParamNotify( pParamScale->set_val( i32ParamTime, rScale ) );	// create key at the timecursor time.
							pParamScale->end_editing( pOldUndo );
							rSelFx.m_f32Values2[0] = rScale[0];
							rSelFx.m_f32Values2[1] = rScale[1];
						}

						m_rOrigEffects.push_back( rSelFx );

					}
				}
			}

		}
		else if( i32Hit & HIT_PIVOT ) {

//			TRACE( "pivot\n" );
			m_pCurrentUndo = new UndoC( "Move Pivot" );
			m_eTrackingAction = TRACKING_MOVE_PIVOT;

			UndoC*	pOldUndo;

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

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

				if( !pLayer )
					continue;
				if( !(pLayer->get_flags() & ITEM_VISIBLE) )
					continue;
				if( pLayer->get_flags() & ITEM_LOCKED )
					continue;
				if( !pLayer->get_timesegment()->is_visible( i32Time ) )
					continue;

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

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

					if( !pEffect )
						continue;
					if( !(pEffect->get_flags() & ITEM_VISIBLE) )
						continue;
					if( pEffect->get_flags() & ITEM_LOCKED )
						continue;
					if( !pEffect->get_timesegment()->is_visible( i32Time - i32TimeOffset ) )
						continue;

					if( pEffect->get_flags() & ITEM_SELECTED ) {


						ParamVector2C*	pParamMove = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_POSITION );
						ParamVector2C*	pParamPivot = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_PIVOT );
						int32		i32ParamTime = i32Time - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start());
						SelEffectS	rSelFx;

						rSelFx.m_pEffect = pEffect;
						rSelFx.m_i32Time = i32ParamTime;

						// save undo stuff
						if( pParamPivot ) {
							Vector2C	rPivot;
							pOldUndo = pParamPivot->begin_editing( m_pCurrentUndo );
							pParamPivot->get_val( i32ParamTime, rPivot );
							pDoc->HandleParamNotify( pParamPivot->set_val( i32ParamTime, rPivot ) );	// create key at the timecursor time.
							pParamPivot->end_editing( pOldUndo );
							rSelFx.m_f32Values[0] = rPivot[0];
							rSelFx.m_f32Values[1] = rPivot[1];
						}

						if( pParamMove ) {
							Vector2C	rPos;
							pOldUndo = pParamMove->begin_editing( m_pCurrentUndo );
							pParamMove->get_val( i32ParamTime, rPos );
							pDoc->HandleParamNotify( pParamMove->set_val( i32ParamTime, rPos ) );	// create key at the timecursor time.
							pParamMove->end_editing( pOldUndo );
							rSelFx.m_f32Values2[0] = rPos[0];
							rSelFx.m_f32Values2[1] = rPos[1];
						}

						// store inverted matrix
						rSelFx.m_rInvTM = pEffect->get_transform_matrix();
						rSelFx.m_rInvTM = rSelFx.m_rInvTM.invert();
						rSelFx.m_rInvTM[2] = Vector2C( 0, 0 );	// Kill translation

						m_rOrigEffects.push_back( rSelFx );

					}
				}
			}
		}
		else if( i32Hit & HIT_POS_PARAM ) {

//			TRACE( "move pos param\n" );
			m_pCurrentUndo = new UndoC( "Move Position Param" );
			m_eTrackingAction = TRACKING_MOVE_POS_PARAM;

			UndoC*	pOldUndo;

			if( pHitPosParam ) {

				bool	bFxFound = false;

				for( i = 0; i < pScene->get_layer_count() && !bFxFound; i++ ) {
					LayerC*	pLayer = pScene->get_layer( i );
					if( !pLayer )
						continue;
					if( !(pLayer->get_flags() & ITEM_VISIBLE) )
						continue;
					if( pLayer->get_flags() & ITEM_LOCKED )
						continue;
					int32	i32TimeOffset;
					i32TimeOffset = pLayer->get_timesegment()->get_segment_start();
					for( uint32 j = 0; j < pLayer->get_effect_count() && !bFxFound; j++ ) {
						EffectI*	pEffect = pLayer->get_effect( j );
						if( !pEffect )
							continue;
						if( pEffect->get_flags() & ITEM_LOCKED )
							continue;
						if( pEffect == pSelEffect ) {

							ParamVector2C*	pParamMove = (ParamVector2C*)pHitPosParam;
							int32			i32ParamTime = i32Time - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start());

							SelEffectS	rSelFx;
							rSelFx.m_pEffect = pEffect;
							rSelFx.m_i32Time = i32ParamTime;
							rSelFx.m_pParam = pHitPosParam;

							// save undo stuff
							Vector2C	rPos;
							pOldUndo = pParamMove->begin_editing( m_pCurrentUndo );
							pParamMove->get_val( i32ParamTime, rPos );
							pDoc->HandleParamNotify( pParamMove->set_val( i32ParamTime, rPos ) );	// create key at the timecursor time.
							pParamMove->end_editing( pOldUndo );
							rSelFx.m_f32Values[0] = rPos[0];
							rSelFx.m_f32Values[1] = rPos[1];

							if( pEffect == pSelEffect ) {
								for( uint32 k = 0; k < KEY_MAXCHANNELS; k++ )
									m_f32OrigValues[k] = rSelFx.m_f32Values[k];
							}

							m_rOrigEffects.push_back( rSelFx );
						}
					}
				}
			}
		}
		else if( i32Hit & HIT_POS_PARAM_KEY ) {

//			TRACE( "hit pos param key\n" );
			m_pCurrentUndo = new UndoC( "Move Position Param" );
			m_eTrackingAction = TRACKING_MOVE_POS_PARAM;

			UndoC*	pOldUndo;

			if( pHitPosParam ) {

				bool	bFxFound = false;

				for( i = 0; i < pScene->get_layer_count() && !bFxFound; i++ ) {
					LayerC*	pLayer = pScene->get_layer( i );
					if( !pLayer )
						continue;
					if( !(pLayer->get_flags() & ITEM_VISIBLE) )
						continue;
					if( pLayer->get_flags() & ITEM_LOCKED )
						continue;
					int32	i32TimeOffset;
					i32TimeOffset = pLayer->get_timesegment()->get_segment_start();
					for( uint32 j = 0; j < pLayer->get_effect_count() && !bFxFound; j++ ) {
						EffectI*	pEffect = pLayer->get_effect( j );
						if( !pEffect )
							continue;
						if( pEffect->get_flags() & ITEM_LOCKED )
							continue;
						if( pEffect == pSelEffect ) {

							ParamVector2C*	pParamMove = (ParamVector2C*)pHitPosParam;

							SelEffectS	rSelFx;
							rSelFx.m_pEffect = pEffect;
							rSelFx.m_i32Time = i32HitTime;
							rSelFx.m_pParam = pHitPosParam;

							// save undo stuff
							Vector2C	rPos;
							pOldUndo = pParamMove->begin_editing( m_pCurrentUndo );
							pParamMove->get_val( i32HitTime, rPos );
							pParamMove->end_editing( pOldUndo );
							rSelFx.m_f32Values[0] = rPos[0];
							rSelFx.m_f32Values[1] = rPos[1];

							for( uint32 k = 0; k < KEY_MAXCHANNELS; k++ )
								m_f32OrigValues[k] = rSelFx.m_f32Values[k];

							m_rOrigEffects.push_back( rSelFx );
						}
					}
				}
			}
		}
		else if( i32Hit & HIT_SCALE || i32Hit & HIT_ROTATE || i32Hit & HIT_MOVE ) {
			if( i32Hit & HIT_SCALE ) {
//				TRACE( "scale\n" );
				m_pCurrentUndo = new UndoC( "Scale Effect(s)" );
				m_eTrackingAction = TRACKING_SCALE_EFFECT;
				m_rOrigScaleSize = m_rEffectHit - m_rEffectOrigin;
			}
			else if( i32Hit & HIT_ROTATE ) {
//				TRACE( "rot\n" );
				m_pCurrentUndo = new UndoC( "Rotate Effect(s)" );
				m_eTrackingAction = TRACKING_ROTATE_EFFECT;
			}
			else if( i32Hit & HIT_MOVE ) {
//				TRACE( "move\n" );
				m_pCurrentUndo = new UndoC( "Move Effect(s)" );
				m_eTrackingAction = TRACKING_MOVE_EFFECT;
			}

			UndoC*	pOldUndo;

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

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

				if( !pLayer )
					continue;
				if( !(pLayer->get_flags() & ITEM_VISIBLE) )
					continue;
				if( pLayer->get_flags() & ITEM_LOCKED )
					continue;
				if( !pLayer->get_timesegment()->is_visible( i32Time ) )
					continue;

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

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

					if( !pEffect )
						continue;
					if( !(pEffect->get_flags() & ITEM_VISIBLE) )
						continue;
					if( pEffect->get_flags() & ITEM_LOCKED )
						continue;
					if( !pEffect->get_timesegment()->is_visible( i32Time - i32TimeOffset ) )
						continue;

					if( pEffect->get_flags() & ITEM_SELECTED ) {


						int32			i32ParamTime = i32Time - (i32TimeOffset + pEffect->get_timesegment()->get_segment_start());

						SelEffectS	rSelFx;
						rSelFx.m_pEffect = pEffect;
						rSelFx.m_i32Time = i32ParamTime;

						if( m_eTrackingAction == TRACKING_MOVE_EFFECT && pEffect->get_default_param( DEFAULT_PARAM_POSITION ) ) {
							ParamVector2C*	pParam = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_POSITION );

							// save undo stuff
							Vector2C	rPos;
							pOldUndo = pParam->begin_editing( m_pCurrentUndo );
							pParam->get_val( i32ParamTime, rPos );
							pDoc->HandleParamNotify( pParam->set_val( i32ParamTime, rPos ) );	// create key at the timecursor time.
							pParam->end_editing( pOldUndo );
							rSelFx.m_f32Values[0] = rPos[0];
							rSelFx.m_f32Values[1] = rPos[1];

							if( pEffect == pSelEffect ) {
								for( uint32 k = 0; k < KEY_MAXCHANNELS; k++ )
									m_f32OrigValues[k] = rSelFx.m_f32Values[k];
							}

							m_rOrigEffects.push_back( rSelFx );
						}
						else if( m_eTrackingAction == TRACKING_SCALE_EFFECT && pEffect->get_default_param( DEFAULT_PARAM_SCALE ) ) {
							ParamVector2C*	pParam = (ParamVector2C*)pEffect->get_default_param( DEFAULT_PARAM_SCALE );
						
							// save undo stuff
							Vector2C	rScale;
							pOldUndo = pParam->begin_editing( m_pCurrentUndo );
							pParam->get_val( i32ParamTime, rScale );
							pDoc->HandleParamNotify( pParam->set_val( i32ParamTime, rScale ) );	// create key at the timecursor time.
							pParam->end_editing( pOldUndo );
							rSelFx.m_f32Values[0] = rScale[0];
							rSelFx.m_f32Values[1] = rScale[1];

							if( pEffect == pSelEffect ) {
								for( uint32 k = 0; k < KEY_MAXCHANNELS; k++ )
									m_f32OrigValues[k] = rSelFx.m_f32Values[k];
							}

							m_rOrigEffects.push_back( rSelFx );
						}
						else if( m_eTrackingAction == TRACKING_ROTATE_EFFECT && pEffect->get_default_param( DEFAULT_PARAM_ROTATION ) ) {
							ParamFloatC*	pParam = (ParamFloatC*)pEffect->get_default_param( DEFAULT_PARAM_ROTATION );
						
							// save undo stuff
							float32	f32Rot;
							pOldUndo = pParam->begin_editing( m_pCurrentUndo );
							pParam->get_val( i32ParamTime, f32Rot );
							pDoc->HandleParamNotify( pParam->set_val( i32ParamTime, f32Rot ) );	// create key at the timecursor time.
							pParam->end_editing( pOldUndo );
							rSelFx.m_f32Values[0] = f32Rot;

							if( pEffect == pSelEffect ) {
								for( uint32 k = 0; k < KEY_MAXCHANNELS; k++ )
									m_f32OrigValues[k] = rSelFx.m_f32Values[k];
							}

							m_rOrigEffects.push_back( rSelFx );
						}

					}
				}
			}
		}

		if( m_rOrigEffects.size() ) {
			SetCapture();
		}
		else {
			// no selection... kill undo
			delete m_pCurrentUndo;
			m_pCurrentUndo = 0;

			// start box select
			m_eTrackingAction = TRACKING_BOX_SELECT;
			m_rEndMousePos = point;
		}

		pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
		return;
	}

	CView::OnLButtonDown(nFlags, point);
}

void CDemopajaView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CDemopajaDoc* pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( !pGraphDevice ) {
		CView::OnLButtonUp(nFlags, point);
		return;
	}

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

	// get the latest state
	m_bSpacePressed = (::GetAsyncKeyState( VK_SPACE ) & 0x8000 ) != 0;
	
	if( !m_bSpacePressed  )
		m_hCursor = 0;


	pDoc->SetEditFocus( FOCUS_VIEW );

	if( GetCapture() == this )
		ReleaseCapture();

	if( (m_eTrackingAction == TRACKING_MOVE_EFFECT ||
		 m_eTrackingAction == TRACKING_ROTATE_EFFECT ||
		 m_eTrackingAction == TRACKING_SCALE_EFFECT ||
		 m_eTrackingAction == TRACKING_MOVE_POS_PARAM ||
		 m_eTrackingAction == TRACKING_SCALE_SELECTED ||
		 m_eTrackingAction == TRACKING_MOVE_PIVOT ) &&
		 m_pCurrentUndo && m_rStartMousePos != point ) {
		pDoc->GetUndoManager()->push( m_pCurrentUndo );
		pDoc->SetModifiedFlag();
		m_pCurrentUndo = 0;
	}
	else if( m_eTrackingAction == TRACKING_BOX_SELECT ) {

		Vector2C	rStartPos = pViewport->client_to_layout( Vector2C( (float32)m_rStartMousePos.x, (float32)(pViewport->get_height() - m_rStartMousePos.y) ) );
		Vector2C	rEndPos = pViewport->client_to_layout( Vector2C( (float32)m_rEndMousePos.x, (float32)(pViewport->get_height() - m_rEndMousePos.y) ) );
		BBox2C		rSelBox( rStartPos, rEndPos );
		rSelBox = rSelBox.normalize();

		BoxSelect( rSelBox, (nFlags & MK_SHIFT) ? true : false );

//		pDoc->LayerlistSelectionChanged();

		pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
	}
	else {
		delete m_pCurrentUndo;
		m_pCurrentUndo = 0;
	}

	if( m_eTrackingAction ) {
		m_eTrackingAction = TRACKING_NONE;
		return;
	}
	
	CView::OnLButtonUp(nFlags, point);
}


void
CDemopajaView::OnMouseMove(UINT nFlags, CPoint point) 
{
	CDemopajaDoc* pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( !pGraphDevice ) {
		CView::OnMouseMove(nFlags, point);
		return;
	}

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

	// get the latest state
	m_bSpacePressed = (::GetAsyncKeyState( VK_SPACE ) & 0x8000 ) != 0;
	m_bCtrlPressed = (::GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) != 0;

	Vector2C	rMousePos;
	rMousePos = Vector2C( (float32)point.x, (float32)(pViewport->get_height() - point.y) );

	m_rEndMousePos = point;

	m_ui32CurTool = pDoc->GetTool();

//	if( m_eTrackingAction == TRACKING_NONE && m_ui32CurTool == TOOL_ARROW && m_bCtrlPressed )
//		m_ui32CurTool = TOOL_KEYARROW;

	if( m_eTrackingAction == TRACKING_PAN ) {
		// pan

		Vector2C	rOffset = pViewport->delta_client_to_layout( m_rOldMousePos - rMousePos );
		m_hCursor = AfxGetApp()->LoadCursor( IDC_DPHAND );

		BBox2C	rViewport = m_rOldViewport.offset( rOffset );

		pDoc->SetSceneViewport( rViewport );

		Invalidate();
		return;
	}
	else if( m_eTrackingAction == TRACKING_ZOOM ) {
		// zoom

		float64	f64Delta = m_rOldMousePos[1] - rMousePos[1];

		Vector2C	rCenter = m_rOldViewport.center();
		BBox2C	rViewport;

		if( f64Delta < 0 ) {
			f64Delta = -f64Delta;
			float64	f64Scale = 1.0 + f64Delta / 100.0;
			rViewport[0][0] = rCenter[0] + (m_rOldViewport[0][0] - rCenter[0]) / f64Scale;
			rViewport[1][0] = rCenter[0] + (m_rOldViewport[1][0] - rCenter[0]) / f64Scale;
			rViewport[0][1] = rCenter[1] + (m_rOldViewport[0][1] - rCenter[1]) / f64Scale;
			rViewport[1][1] = rCenter[1] + (m_rOldViewport[1][1] - rCenter[1]) / f64Scale;
		} else {
			float64	f64Scale = 1.0 + f64Delta / 100.0;
			rViewport[0][0] = rCenter[0] + (m_rOldViewport[0][0] - rCenter[0]) * f64Scale;
			rViewport[1][0] = rCenter[0] + (m_rOldViewport[1][0] - rCenter[0]) * f64Scale;
			rViewport[0][1] = rCenter[1] + (m_rOldViewport[0][1] - rCenter[1]) * f64Scale;
			rViewport[1][1] = rCenter[1] + (m_rOldViewport[1][1] - rCenter[1]) * f64Scale;
		}

		m_hCursor = AfxGetApp()->LoadCursor( IDC_ZOOM );

		pDoc->SetSceneViewport( rViewport );

		Invalidate();
		return;
	}
	else if( m_eTrackingAction == TRACKING_MOVE_EFFECT ) {
		if( nFlags & MK_LBUTTON ) {

			Vector2C	rOldPos = pViewport->client_to_layout( m_rOldMousePos );
			Vector2C	rCurMousePos = pViewport->client_to_layout( rMousePos );
			float32		f32GSize = (float32)pDoc->GetGridSize();

			Vector2C	rOffset = rCurMousePos - rOldPos;

			BBox2C		rBBox = m_rOrigSelBBox.offset( rOffset );

			if( pDoc->GetSnapToGrid() ) {

				float32	f32Left = (int32)(rBBox[0][0] / f32GSize + 0.5) * f32GSize;
				float32	f32Right = (int32)(rBBox[1][0] / f32GSize + 0.5) * f32GSize;

				float32	f32Top = (int32)(rBBox[0][1] / f32GSize + 0.5) * f32GSize;
				float32	f32Bottom = (int32)(rBBox[1][1] / f32GSize + 0.5) * f32GSize;

				if( fabs( f32Left - rBBox[0][0] ) < fabs( f32Right - rBBox[1][0] ) )
					rOffset[0] += f32Left - rBBox[0][0];
				else
					rOffset[0] += f32Right - rBBox[1][0];

				if( fabs( f32Top - rBBox[0][1] ) < fabs( f32Bottom - rBBox[1][1] ) )
					rOffset[1] += f32Top - rBBox[0][1];
				else
					rOffset[1] += f32Bottom - rBBox[1][1];
			}

			if( nFlags & MK_SHIFT ) {
				if( fabs( rOffset[0] ) > fabs( rOffset[1] ) )
					rOffset[1] = 0;
				else
					rOffset[0] = 0;
			}


			for( uint32 i = 0; i < m_rOrigEffects.size(); i++ ) {
				ParamVector2C*	pParam = (ParamVector2C*)m_rOrigEffects[i].m_pEffect->get_default_param( DEFAULT_PARAM_POSITION );
				if( pParam ) {
					Vector2C	rOrigPos( m_rOrigEffects[i].m_f32Values[0], m_rOrigEffects[i].m_f32Values[1] );
					Vector2C	rPos = rOffset + rOrigPos;
					pDoc->HandleParamNotify( pParam->set_val( m_rOrigEffects[i].m_i32Time, rPos ) );
				}
			}

			pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
			return;
		}
	}
	else if( m_eTrackingAction == TRACKING_SCALE_SELECTED ) {
		if( nFlags & MK_LBUTTON ) {

			Vector2C	rCurPos = pViewport->client_to_layout( rMousePos );

			if( pDoc->GetSnapToGrid() ) {
				float32		f32GSize = (float32)pDoc->GetGridSize();
				rCurPos[0] = (int32)(rCurPos[0] / f32GSize + 0.5) * f32GSize;
				rCurPos[1] = (int32)(rCurPos[1] / f32GSize + 0.5) * f32GSize;
			}

			Vector2C	rOffset; // = rCurPos - m_rEffectOrigin;
			Vector2C	rOffset2;
			Vector2C	rScaleCenter = m_rOrigSelBBox.center();

			float32	f32ScaleX = 1;
			float32	f32ScaleY = 1;

			if( nFlags & MK_CONTROL ) {

				Vector2C	rScaleCenterSize = m_rEffectHit - rScaleCenter;

				rOffset = rCurPos - rScaleCenter;

				if( m_i32LastHit & HIT_SCALE_LEFT || m_i32LastHit & HIT_SCALE_RIGHT ) {
					f32ScaleX = rOffset[0] / rScaleCenterSize[0];
					rOffset2[0] = rOffset[0];
				}

				if( m_i32LastHit & HIT_SCALE_BOTTOM || m_i32LastHit & HIT_SCALE_TOP ) {
					f32ScaleY = rOffset[1] / rScaleCenterSize[1];
					rOffset2[1] = rOffset[1];
				}
			}
			else {

				rOffset = rCurPos - m_rEffectOrigin;

				if( m_i32LastHit & HIT_SCALE_LEFT || m_i32LastHit & HIT_SCALE_RIGHT ) {
					f32ScaleX = rOffset[0] / m_rOrigScaleSize[0];
					rOffset2[0] = rOffset[0];
				}

				if( m_i32LastHit & HIT_SCALE_BOTTOM || m_i32LastHit & HIT_SCALE_TOP ) {
					f32ScaleY = rOffset[1] / m_rOrigScaleSize[1];
					rOffset2[1] = rOffset[1];
				}
			}

			if( nFlags & MK_SHIFT ) {
				if( fabs( rOffset2[0] ) > fabs( rOffset2[1] ) ) {
					// use the x scale
					float32	f32Sign = f32ScaleY > 0 ? 1.0f : -1.0f;
					f32ScaleY = fabs( f32ScaleX ) *f32Sign;
				}
				else {
					float32	f32Sign = f32ScaleX > 0 ? 1.0f : -1.0f;
					f32ScaleX = fabs( f32ScaleY ) * f32Sign;
				}
			}

			for( uint32 i = 0; i < m_rOrigEffects.size(); i++ ) {
				ParamVector2C*	pParamScale = (ParamVector2C*)m_rOrigEffects[i].m_pEffect->get_default_param( DEFAULT_PARAM_SCALE );
				if( pParamScale ) {
					Vector2C	rScale = Vector2C( m_rOrigEffects[i].m_f32Values2[0] * f32ScaleX, m_rOrigEffects[i].m_f32Values2[1] * f32ScaleY );
					pDoc->HandleParamNotify( pParamScale->set_val( m_rOrigEffects[i].m_i32Time, rScale ) );
				}

				ParamVector2C*	pParamPos = (ParamVector2C*)m_rOrigEffects[i].m_pEffect->get_default_param( DEFAULT_PARAM_POSITION );
				if( pParamPos ) {
					Vector2C	rOrigPos( m_rOrigEffects[i].m_f32Values[0], m_rOrigEffects[i].m_f32Values[1] );
					Vector2C	rPos;
					if( nFlags & MK_CONTROL ) {
						rPos[0] = (rOrigPos[0] - rScaleCenter[0]) * f32ScaleX + rScaleCenter[0];
						rPos[1] = (rOrigPos[1] - rScaleCenter[1]) * f32ScaleY + rScaleCenter[1];
					}
					else {
						rPos[0] = (rOrigPos[0] - m_rEffectOrigin[0]) * f32ScaleX + m_rEffectOrigin[0];
						rPos[1] = (rOrigPos[1] - m_rEffectOrigin[1]) * f32ScaleY + m_rEffectOrigin[1];
					}
					pDoc->HandleParamNotify( pParamPos->set_val( m_rOrigEffects[i].m_i32Time, rPos ) );
				}
			}

			pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
			return;
		}
	}
	else if( m_eTrackingAction == TRACKING_SCALE_EFFECT ) {
		if( nFlags & MK_LBUTTON ) {

			Vector2C	rCurPos = pViewport->client_to_layout( rMousePos );

			if( pDoc->GetSnapToGrid() ) {
				float32		f32GSize = (float32)pDoc->GetGridSize();
				rCurPos[0] = (int32)(rCurPos[0] / f32GSize + 0.5) * f32GSize;
				rCurPos[1] = (int32)(rCurPos[1] / f32GSize + 0.5) * f32GSize;
			}

			Vector2C	rOffset = rCurPos - m_rEffectOrigin;
			Vector2C	rOffset2;

			float32	f32ScaleX = 1;
			float32	f32ScaleY = 1;

			if( m_i32LastHit & HIT_SCALE_LEFT || m_i32LastHit & HIT_SCALE_RIGHT ) {
				f32ScaleX = rOffset[0] / m_rOrigScaleSize[0];
				rOffset2[0] = rOffset[0];
			}

			if( m_i32LastHit & HIT_SCALE_BOTTOM || m_i32LastHit & HIT_SCALE_TOP ) {
				f32ScaleY = rOffset[1] / m_rOrigScaleSize[1];
				rOffset2[1] = rOffset[1];
			}

			if( nFlags & MK_SHIFT ) {
				if( fabs( rOffset2[0] ) > fabs( rOffset2[1] ) ) {
					// use the x scale
					float32	f32Sign = f32ScaleY > 0 ? 1.0f : -1.0f;
					f32ScaleY = fabs( f32ScaleX ) *f32Sign;
				}
				else {
					float32	f32Sign = f32ScaleX > 0 ? 1.0f : -1.0f;
					f32ScaleX = fabs( f32ScaleY ) * f32Sign;
				}
			}

			for( uint32 i = 0; i < m_rOrigEffects.size(); i++ ) {
				ParamVector2C*	pParam = (ParamVector2C*)m_rOrigEffects[i].m_pEffect->get_default_param( DEFAULT_PARAM_SCALE );
				if( pParam ) {
					Vector2C	rScale = Vector2C( m_rOrigEffects[i].m_f32Values[0] * f32ScaleX, m_rOrigEffects[i].m_f32Values[1] * f32ScaleY );
					pDoc->HandleParamNotify( pParam->set_val( m_rOrigEffects[i].m_i32Time, rScale ) );
				}
			}

			pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
			return;
		}
	}
	else if( m_eTrackingAction == TRACKING_ROTATE_EFFECT ) {
		if( nFlags & MK_LBUTTON ) {

			Vector2C	rOffset = pViewport->client_to_layout( rMousePos ) - m_rEffectOrigin;

			float32	f32DeltaAngle = (float32)atan2( rOffset[0], rOffset[1] ) / M_PI * 180.0f;

			if( nFlags & MK_SHIFT ) {
				f32DeltaAngle = (int32)(f32DeltaAngle / 15.0f) * 15.0f;
			}

			f32DeltaAngle = 90 - m_f32OrigValues[0] - f32DeltaAngle;

//			TRACE( "angle: %f\n", f32DeltaAngle );
//			TRACE( "effet orig: (%f, %f)\n", m_rEffectOrigin[0], m_rEffectOrigin[1] );

			for( uint32 i = 0; i < m_rOrigEffects.size(); i++ ) {
				ParamFloatC*	pParam = (ParamFloatC*)m_rOrigEffects[i].m_pEffect->get_default_param( DEFAULT_PARAM_ROTATION );
				if( pParam ) {
					float32	f32Angle = m_rOrigEffects[i].m_f32Values[0] + f32DeltaAngle;
					pDoc->HandleParamNotify( pParam->set_val( m_rOrigEffects[i].m_i32Time, f32Angle ) );
				}
			}

			pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
			return;
		}
	}
	else if( m_eTrackingAction == TRACKING_MOVE_PIVOT ) {

		if( nFlags & MK_LBUTTON ) {

			Vector2C	rOldPos = pViewport->client_to_layout( m_rOldMousePos );
			Vector2C	rCurPos = pViewport->client_to_layout( rMousePos );

			if( pDoc->GetSnapToGrid() ) {
				float32		f32GSize = (float32)pDoc->GetGridSize();
				rCurPos[0] = (int32)(rCurPos[0] / f32GSize + 0.5) * f32GSize;
				rCurPos[1] = (int32)(rCurPos[1] / f32GSize + 0.5) * f32GSize;
			}

			Vector2C	rOffset = rCurPos - rOldPos;

			if( nFlags & MK_SHIFT ) {
				if( fabs( rOffset[0] ) > fabs( rOffset[1] ) )
					rOffset[1] = 0;
				else
					rOffset[0] = 0;
			}


			for( uint32 i = 0; i < m_rOrigEffects.size(); i++ ) {
				ParamVector2C*	pParamPivot = (ParamVector2C*)m_rOrigEffects[i].m_pEffect->get_default_param( DEFAULT_PARAM_PIVOT );
				ParamVector2C*	pParamPos = (ParamVector2C*)m_rOrigEffects[i].m_pEffect->get_default_param( DEFAULT_PARAM_POSITION );

				if( pParamPivot ) {
					Vector2C	rPos = (-rOffset * m_rOrigEffects[i].m_rInvTM) +
										Vector2C( m_rOrigEffects[i].m_f32Values[0], m_rOrigEffects[i].m_f32Values[1] );
					pDoc->HandleParamNotify( pParamPivot->set_val( m_rOrigEffects[i].m_i32Time, rPos ) );
				}

				if( pParamPos ) {
					Vector2C	rPos = rOffset +
										Vector2C( m_rOrigEffects[i].m_f32Values2[0], m_rOrigEffects[i].m_f32Values2[1] );
					pDoc->HandleParamNotify( pParamPos->set_val( m_rOrigEffects[i].m_i32Time, rPos ) );
				}

			}

			pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
			return;
		}
	}
	else if( m_eTrackingAction == TRACKING_MOVE_POS_PARAM ) {
		if( nFlags & MK_LBUTTON ) {

			Vector2C	rOldPos = pViewport->client_to_layout( m_rOldMousePos );
			Vector2C	rCurPos = pViewport->client_to_layout( rMousePos );

			if( pDoc->GetSnapToGrid() ) {
				float32		f32GSize = (float32)pDoc->GetGridSize();
				rCurPos[0] = (int32)(rCurPos[0] / f32GSize + 0.5) * f32GSize;
				rCurPos[1] = (int32)(rCurPos[1] / f32GSize + 0.5) * f32GSize;
			}

			Vector2C	rOffset = rCurPos - rOldPos;

			if( nFlags & MK_SHIFT ) {
				if( fabs( rOffset[0] ) > fabs( rOffset[1] ) )
					rOffset[1] = 0;
				else
					rOffset[0] = 0;
			}

			for( uint32 i = 0; i < m_rOrigEffects.size(); i++ ) {
				ParamVector2C*	pParam = (ParamVector2C*)m_rOrigEffects[i].m_pParam;
				if( pParam ) {
					if( pParam->get_style() & PARAM_STYLE_OBJECT_SPACE ) {
						Matrix2C	rTM = m_rOrigEffects[i].m_pEffect->get_transform_matrix();
						rTM = rTM.invert();
						rTM[2] = Vector2C( 0, 0 );
						rOffset *= rTM;
					}
					Vector2C	rPos = rOffset + Vector2C( m_rOrigEffects[i].m_f32Values[0], m_rOrigEffects[i].m_f32Values[1] );
					pDoc->HandleParamNotify( pParam->set_val( m_rOrigEffects[i].m_i32Time, rPos ) );

				}
			}

			pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
			return;
		}
	}
	else if( m_eTrackingAction == TRACKING_BOX_SELECT ) {
		pDoc->NotifyViews( NOTIFY_REDRAW_GRAPHICS );
		return;
	}

	m_hCursor = 0;
	if( (m_bSpacePressed && !m_bCtrlPressed) || pDoc->GetTool() == TOOL_PAN )
		m_hCursor = AfxGetApp()->LoadCursor( IDC_DPHAND );
	else if( (m_bSpacePressed && m_bCtrlPressed) || pDoc->GetTool() == TOOL_ZOOM )
		m_hCursor = AfxGetApp()->LoadCursor( IDC_ZOOM );

	CView::OnMouseMove(nFlags, point);
}


void CDemopajaView::OnRButtonDown(UINT nFlags, CPoint point) 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );

	pDoc->SetEditFocus( FOCUS_VIEW );
	
	CView::OnRButtonDown(nFlags, point);
}

void CDemopajaView::OnRButtonUp(UINT nFlags, CPoint point) 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );

	pDoc->SetEditFocus( FOCUS_VIEW );
	
	CView::OnRButtonUp(nFlags, point);
}

void CDemopajaView::OnEditCopy() 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	pDoc->OnCopy();
}

void CDemopajaView::OnUpdateEditCopy(CCmdUI* pCmdUI) 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	if( pDoc->CanCopy() )
		pCmdUI->Enable( TRUE );
	else
		pCmdUI->Enable( FALSE );
}

void CDemopajaView::OnEditCut() 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	pDoc->OnCut();
}

void CDemopajaView::OnUpdateEditCut(CCmdUI* pCmdUI) 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	if( pDoc->CanCopy() )
		pCmdUI->Enable( TRUE );
	else
		pCmdUI->Enable( FALSE );
}

void CDemopajaView::OnEditPaste() 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	pDoc->OnPaste();
}

void CDemopajaView::OnUpdateEditPaste(CCmdUI* pCmdUI) 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	if( pDoc->CanPaste() )
		pCmdUI->Enable( TRUE );
	else
		pCmdUI->Enable( FALSE );
}

void CDemopajaView::OnInitialUpdate() 
{
	CView::OnInitialUpdate();
	ResetView();
}



void CDemopajaView::OnViewZoom50() 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( !pGraphDevice )
		return;

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

	float32	f32HalfWidth = (float32)pDoc->GetLayoutWidth() / 2;
	float32	f32HalfHeight = (float32)pDoc->GetLayoutHeight() / 2;

	BBox2C	rViewport;
	rViewport[0] = Vector2C( f32HalfWidth - (pViewport->get_width() / 2) * 2, f32HalfHeight - (pViewport->get_height() / 2) * 2 );
	rViewport[1] = rViewport[0] + Vector2C( (float32)(pViewport->get_width() * 2), (float32)(pViewport->get_height() * 2));

	pDoc->SetSceneViewport( rViewport );

	Invalidate();
}

void CDemopajaView::OnViewZoom100() 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( !pGraphDevice )
		return;

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

	float32	f32HalfWidth = (float32)pDoc->GetLayoutWidth() / 2;
	float32	f32HalfHeight = (float32)pDoc->GetLayoutHeight() / 2;

	BBox2C	rViewport;
	rViewport[0] = Vector2C( f32HalfWidth - pViewport->get_width() / 2, f32HalfHeight - pViewport->get_height() / 2 );
	rViewport[1] = rViewport[0] + Vector2C( (float32)pViewport->get_width(), (float32)pViewport->get_height() );

	pDoc->SetSceneViewport( rViewport );

	Invalidate();
}

void CDemopajaView::OnViewZoom200() 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( !pGraphDevice )
		return;

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

	float32	f32HalfWidth = (float32)pDoc->GetLayoutWidth() / 2;
	float32	f32HalfHeight = (float32)pDoc->GetLayoutHeight() / 2;

	BBox2C	rViewport;
	rViewport[0] = Vector2C( f32HalfWidth - (pViewport->get_width() / 2) / 2, f32HalfHeight - (pViewport->get_height() / 2) / 2 );
	rViewport[1] = rViewport[0] + Vector2C( (float32)(pViewport->get_width() / 2), (float32)(pViewport->get_height() / 2) );

	pDoc->SetSceneViewport( rViewport );

	Invalidate();
}

void CDemopajaView::OnViewZoom300() 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( !pGraphDevice )
		return;

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

	float32	f32HalfWidth = (float32)pDoc->GetLayoutWidth() / 2;
	float32	f32HalfHeight = (float32)pDoc->GetLayoutHeight() / 2;

	BBox2C	rViewport;
	rViewport[0] = Vector2C( f32HalfWidth - (pViewport->get_width() / 2) / 3, f32HalfHeight - (pViewport->get_height() / 2) / 3 );
	rViewport[1] = rViewport[0] + Vector2C( (float32)(pViewport->get_width() / 3), (float32)(pViewport->get_height() / 3) );

	pDoc->SetSceneViewport( rViewport );

	Invalidate();
}

void CDemopajaView::OnViewZoom400() 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );
	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( !pGraphDevice )
		return;

	GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );

	float32	f32HalfWidth = (float32)pDoc->GetLayoutWidth() / 2;
	float32	f32HalfHeight = (float32)pDoc->GetLayoutHeight() / 2;

	BBox2C	rViewport;
	rViewport[0] = Vector2C( f32HalfWidth - (pViewport->get_width() / 2) / 4, f32HalfHeight - (pViewport->get_height() / 2) / 4 );
	rViewport[1] = rViewport[0] + Vector2C( (float32)(pViewport->get_width() / 4), (float32)(pViewport->get_height() / 4) );

	pDoc->SetSceneViewport( rViewport );

	Invalidate();
}



void CDemopajaView::OnUpdateViewZoom100(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable( TRUE );
}

void CDemopajaView::OnUpdateViewZoom200(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable( TRUE );
}

void CDemopajaView::OnUpdateViewZoom300(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable( TRUE );
}

void CDemopajaView::OnUpdateViewZoom400(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable( TRUE );
}

void CDemopajaView::OnUpdateViewZoom50(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable( TRUE );
}


int CDemopajaView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;

	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );

	return 0;
}

void CDemopajaView::StartRenderTimer( int32 i32Time, int32 i32MaxTime )
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );

	if( m_ui32TimerID )
		return;

	m_i32RenderTime = i32Time;
	m_i32MaxRenderTime = i32MaxTime;
	m_f64RenderTimeScale = (float64)pDoc->GetBeatsPerMin() * (float64)pDoc->GetQNotesPerBeat() * 256.0 / 60.0;

	m_i32RenderStartTime = timeGetTime() - (int32)((float64)m_i32RenderTime * 1000.0 / m_f64RenderTimeScale);

	// start timer
	m_ui32TimerID = SetTimer( RENDER_TIMER_ID, 1, 0 );

	pDoc->PlayMusic( i32Time );

//	float64	f64TimeScale = (float64)pDoc->GetBeatsPerMin() * (float64)pDoc->GetQNotesPerBeat() * (float64)pDoc->GetEditAccuracy() / 60.0;
//	int32	i32Pos = (int32)((float64)i32Time / f64TimeScale * 1000.0);	// Position in ms
//	TRACE( "CDemopajaView::StartRenderTimer:  set: %d  get: %d\n", i32Pos, pDoc->GetMusicPos() );
}

void CDemopajaView::StopRenderTimer()
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );

	if( m_ui32TimerID )
		KillTimer( m_ui32TimerID );

	m_ui32TimerID = 0;

	pDoc->StopMusic();
}

void CDemopajaView::SetTimePos( int32 i32Time )
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );

	m_i32RenderTime = i32Time;
	m_i32RenderStartTime = timeGetTime() - (int32)((float64)m_i32RenderTime * 1000.0 / m_f64RenderTimeScale);

	pDoc->SetPosMusic( i32Time );
}


bool CDemopajaView::IsPlaying()
{
	return m_ui32TimerID != 0;
}

void CDemopajaView::OnTimer(UINT nIDEvent) 
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );

	if( nIDEvent == RENDER_TIMER_ID ) {

//		float64	f64RenderTimeScale = (float64)pDoc->GetBeatsPerMin() * (float64)pDoc->GetQNotesPerBeat() * (float64)pDoc->GetEditAccuracy() / 60.0 * (float64)pDoc->GetFrameSizeInTicks();
		m_i32RenderTime = pDoc->GetMusicPos(); //(int32)((float64)pDoc->GetMusicPos() / 1000.0 * f64RenderTimeScale);

		if( m_i32RenderTime > m_i32MaxRenderTime ) {
			if( pDoc->GetPlayLoop() ) {
				m_i32RenderTime = 0;
				pDoc->SetPosMusic( 0 );
			}
			else {
				// stop the playing
				GetTimeline()->OnPlay();
			}
		}
		
		CMainFrame*	pMain = (CMainFrame*)AfxGetMainWnd();

		pMain->GetTimelineBar()->UpdateTimeCursor( m_i32RenderTime );

		Invalidate();
	}
	else
		CView::OnTimer(nIDEvent);
}

CTimeLineBar* CDemopajaView::GetTimeline()
{
	CMainFrame*		pMain = (CMainFrame*)AfxGetMainWnd();
	return pMain->GetTimelineBar();
}

void
CDemopajaView::UpdateNotify( uint32 ui32Notify )
{
	if( ui32Notify == NOTIFY_REDRAW_GRAPHICS ) {
		Invalidate();
	}
	else if( ui32Notify == NOTIFY_REDRAW_ALL ) {
		Invalidate();
	}
	else if( ui32Notify == NOTIFY_RESET_VIEWS ) {
		ResetView();
	}
	else if( ui32Notify == NOTIFY_FILELIST_CHANGED ) {
		// nothing
	}
	else if( ui32Notify == NOTIFY_LAYERLIST_CHANGED ) {
		// nothing
	}
}

void
CDemopajaView::ResetView()
{
	CDemopajaDoc*	pDoc = GetDocument();
	ASSERT_VALID( pDoc );

	DeviceContextC*		pContext = pDoc->GetDeviceContext();
	GraphicsDeviceI*	pGraphDevice = (GraphicsDeviceI*)pContext->query_interface( SUPERCLASS_GRAPHICSDEVICE );

	if( pGraphDevice ) {
		GraphicsViewportI*	pViewport = (GraphicsViewportI*)pGraphDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
		pViewport->set_layout( BBox2C( Vector2C( 0, 0 ), Vector2C( (float32)pDoc->GetLayoutWidth(), (float32)pDoc->GetLayoutHeight() ) ) );

		// Zoom 100%
		float32	f32HalfWidth = (float32)pDoc->GetLayoutWidth() / 2;
		float32	f32HalfHeight = (float32)pDoc->GetLayoutHeight() / 2;

		BBox2C	rViewport;
		rViewport[0] = Vector2C( f32HalfWidth - pViewport->get_width() / 2, f32HalfHeight - pViewport->get_height() / 2 );
		rViewport[1] = rViewport[0] + Vector2C( (float32)pViewport->get_width(), (float32)pViewport->get_height() );

		pDoc->SetSceneViewport( rViewport );
	}

	// If width or height is zero, we need to reset the view again when the next resize occurs.
	CRect	rClientRect;
	GetClientRect( rClientRect );
	if( rClientRect.Width() == 0 || rClientRect.Height() == 0 ) {
		m_bViewResetRequest = true;
	}
	else {
		m_bViewResetRequest = false;
	}

	Invalidate();
}
