/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_svx.hxx"

#include <svx/dlgctl3d.hxx>
#include <svx/dialogs.hrc>
#include <svx/view3d.hxx>
#include <svx/fmmodel.hxx>
#include <svl/itempool.hxx>
#include <svx/fmpage.hxx>
#include <svx/polysc3d.hxx>
#include <svx/sphere3d.hxx>
#include <svx/cube3d.hxx>
#include <vcl/svapp.hxx>
#include <svx/helperhittest3d.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <svx/polygn3d.hxx>
#include <svx/xlnclit.hxx>
#include <svx/xlnwtit.hxx>
#include "helpid.hrc"
#include <algorithm>
#include <svx/dialmgr.hxx>

//////////////////////////////////////////////////////////////////////////////

Svx3DPreviewControl::Svx3DPreviewControl(Window* pParent, const ResId& rResId) 
:   Control(pParent, rResId),
    mpModel(0),
    mpFmPage(0),
    mp3DView(0),
    mpScene(0),
    mp3DObj(0),
    mnObjectType(PREVIEW_OBJECTTYPE_SPHERE)
{
	Construct();

	// do not paint background self, DrawingLayer paints this buffered and as page
	SetControlBackground();
    SetBackground();
}

Svx3DPreviewControl::Svx3DPreviewControl(Window* pParent, WinBits nStyle) 
:   Control(pParent, nStyle),
    mpModel(0),
    mpFmPage(0),
    mp3DView(0),
    mpScene(0),
    mp3DObj(0),
    mnObjectType(PREVIEW_OBJECTTYPE_SPHERE)
{
	Construct();

	// do not paint background self, DrawingLayer paints this buffered and as page
	SetControlBackground();
    SetBackground();
}

Svx3DPreviewControl::~Svx3DPreviewControl()
{
	delete mp3DView;
	delete mpModel;
}

void Svx3DPreviewControl::Construct()
{
    // Do never mirror the preview window.  This explicitly includes right
    // to left writing environments.
    EnableRTL (sal_False);
	SetMapMode( MAP_100TH_MM );

	// Model
	mpModel = new FmFormModel();
	mpModel->GetItemPool().FreezeIdRanges();

	// Page
	mpFmPage = new FmFormPage( *mpModel, NULL );
	mpModel->InsertPage( mpFmPage, 0 );

	// 3D View
	mp3DView = new E3dView( mpModel, this );
	mp3DView->SetBufferedOutputAllowed(true);
	mp3DView->SetBufferedOverlayAllowed(true);

	// 3D Scene
	mpScene = new E3dPolyScene(mp3DView->Get3DDefaultAttributes());

	// initially create object
	SetObjectType(PREVIEW_OBJECTTYPE_SPHERE);

	// camera and perspective
	Camera3D& rCamera  = (Camera3D&) mpScene->GetCamera();
	const basegfx::B3DRange& rVolume = mpScene->GetBoundVolume();
	double fW = rVolume.getWidth();
	double fH = rVolume.getHeight();
	double fCamZ = rVolume.getMaxZ() + ((fW + fH) / 2.0);

	rCamera.SetAutoAdjustProjection(sal_False);
	rCamera.SetViewWindow(- fW / 2, - fH / 2, fW, fH);
	basegfx::B3DPoint aLookAt;
	double fDefaultCamPosZ = mp3DView->GetDefaultCamPosZ();
	basegfx::B3DPoint aCamPos(0.0, 0.0, fCamZ < fDefaultCamPosZ ? fDefaultCamPosZ : fCamZ);
	rCamera.SetPosAndLookAt(aCamPos, aLookAt);
	double fDefaultCamFocal = mp3DView->GetDefaultCamFocal();
	rCamera.SetFocalLength(fDefaultCamFocal);
	rCamera.SetDefaults(basegfx::B3DPoint(0.0, 0.0, fDefaultCamPosZ), aLookAt, fDefaultCamFocal);

	mpScene->SetCamera( rCamera );
	mpFmPage->InsertObject( mpScene );

	basegfx::B3DHomMatrix aRotation;
	aRotation.rotate(DEG2RAD( 25 ), 0.0, 0.0);
	aRotation.rotate(0.0, DEG2RAD( 40 ), 0.0);
	mpScene->SetTransform(aRotation * mpScene->GetTransform());

	// invalidate SnapRects of objects
	mpScene->SetRectsDirty();

	SfxItemSet aSet( mpModel->GetItemPool(),
		XATTR_LINESTYLE, XATTR_LINESTYLE,
		XATTR_FILL_FIRST, XATTR_FILLBITMAP, 
		0, 0 );
	aSet.Put( XLineStyleItem( XLINE_NONE ) );
	aSet.Put( XFillStyleItem( XFILL_SOLID ) );
	aSet.Put( XFillColorItem( String(), Color( COL_WHITE ) ) );

    mpScene->SetMergedItemSet(aSet);

	// PageView
	SdrPageView* pPageView = mp3DView->ShowSdrPage( mpFmPage );
	mp3DView->hideMarkHandles();

	// mark scene
	mp3DView->MarkObj( mpScene, pPageView );
}

void Svx3DPreviewControl::Resize()
{
	// size of page
	Size aSize( GetSizePixel() );
	aSize = PixelToLogic( aSize );
	mpFmPage->SetSize( aSize );

	// set size
	Size aObjSize( aSize.Width()*5/6, aSize.Height()*5/6 );
	Point aObjPoint( (aSize.Width() - aObjSize.Width()) / 2,
		(aSize.Height() - aObjSize.Height()) / 2);
	Rectangle aRect( aObjPoint, aObjSize);
	mpScene->SetSnapRect( aRect );
}

void Svx3DPreviewControl::Paint(const Rectangle& rRect)
{
	mp3DView->CompleteRedraw(this, Region(rRect));
}

void Svx3DPreviewControl::MouseButtonDown(const MouseEvent& rMEvt)
{
	Control::MouseButtonDown(rMEvt);

    if( rMEvt.IsShift() && rMEvt.IsMod1() )
	{
        if(PREVIEW_OBJECTTYPE_SPHERE == GetObjectType())
        {
            SetObjectType(PREVIEW_OBJECTTYPE_CUBE);
        }
        else
        {
            SetObjectType(PREVIEW_OBJECTTYPE_SPHERE);
        }
	}
}

void Svx3DPreviewControl::SetObjectType(sal_uInt16 nType)
{
	if( mnObjectType != nType || !mp3DObj)
	{
		SfxItemSet aSet(mpModel->GetItemPool(), SDRATTR_START, SDRATTR_END, 0, 0);
		mnObjectType = nType;

		if( mp3DObj )
		{
			aSet.Put(mp3DObj->GetMergedItemSet());
			mpScene->Remove3DObj( mp3DObj );
			delete mp3DObj;
			mp3DObj = NULL;
		}

		switch( nType )
		{
			case PREVIEW_OBJECTTYPE_SPHERE:
			{
				mp3DObj = new E3dSphereObj(
					mp3DView->Get3DDefaultAttributes(),
					basegfx::B3DPoint( 0, 0, 0 ),
					basegfx::B3DVector( 5000, 5000, 5000 ));
			}
			break;

			case PREVIEW_OBJECTTYPE_CUBE:
			{
				mp3DObj = new E3dCubeObj(
					mp3DView->Get3DDefaultAttributes(),
					basegfx::B3DPoint( -2500, -2500, -2500 ),
					basegfx::B3DVector( 5000, 5000, 5000 ));
			}
			break;
		}

		mpScene->Insert3DObj( mp3DObj );
		mp3DObj->SetMergedItemSet(aSet);

        Resize();
	}
}

SfxItemSet Svx3DPreviewControl::Get3DAttributes() const
{
	return mp3DObj->GetMergedItemSet();
}

void Svx3DPreviewControl::Set3DAttributes( const SfxItemSet& rAttr )
{
	mp3DObj->SetMergedItemSet(rAttr, true);
	Resize();
}

//////////////////////////////////////////////////////////////////////////////

#define RADIUS_LAMP_PREVIEW_SIZE	(4500.0)
#define RADIUS_LAMP_SMALL			(600.0)
#define RADIUS_LAMP_BIG				(1000.0)
#define NO_LIGHT_SELECTED           (0xffffffff)
#define MAX_NUMBER_LIGHTS              (8)

Svx3DLightControl::Svx3DLightControl(Window* pParent, const ResId& rResId)
:   Svx3DPreviewControl(pParent, rResId),
    maUserInteractiveChangeCallback(),
    maUserSelectionChangeCallback(),
    maChangeCallback(),
    maSelectionChangeCallback(),
    maSelectedLight(NO_LIGHT_SELECTED),
	mpExpansionObject(0),
	mpLampBottomObject(0),
	mpLampShaftObject(0),
	maLightObjects(MAX_NUMBER_LIGHTS, (E3dObject*)0),
	mfRotateX(-20.0),
	mfRotateY(45.0),
	mfRotateZ(0.0),
	maActionStartPoint(),
	mnInteractionStartDistance(5 * 5 * 2),
	mfSaveActionStartHor(0.0),
	mfSaveActionStartVer(0.0),
	mfSaveActionStartRotZ(0.0),
	mbMouseMoved(false),
    mbGeometrySelected(false)
{
	Construct2();
}

Svx3DLightControl::Svx3DLightControl(Window* pParent, WinBits nStyle)
:   Svx3DPreviewControl(pParent, nStyle),
    maUserInteractiveChangeCallback(),
    maUserSelectionChangeCallback(),
    maChangeCallback(),
    maSelectionChangeCallback(),
    maSelectedLight(NO_LIGHT_SELECTED),
	mpExpansionObject(0),
	mpLampBottomObject(0),
	mpLampShaftObject(0),
	maLightObjects(MAX_NUMBER_LIGHTS, (E3dObject*)0),
	mfRotateX(-20.0),
	mfRotateY(45.0),
	mfRotateZ(0.0),
	maActionStartPoint(),
	mnInteractionStartDistance(5 * 5 * 2),
	mfSaveActionStartHor(0.0),
	mfSaveActionStartVer(0.0),
	mfSaveActionStartRotZ(0.0),
	mbMouseMoved(false),
    mbGeometrySelected(false)
{
	Construct2();
}

Svx3DLightControl::~Svx3DLightControl()
{
	// SdrObjects like mpExpansionObject and mpLampBottomObject/mpLampShaftObject get deleted
	// with deletion of the DrawingLayer and model
}

void Svx3DLightControl::Construct2()
{
	{
		// hide all page stuff, use control background (normally gray)
		const Color aDialogColor(Application::GetSettings().GetStyleSettings().GetDialogColor());
		mp3DView->SetPageVisible(false);
		mp3DView->SetApplicationBackgroundColor(aDialogColor);
		mp3DView->SetApplicationDocumentColor(aDialogColor);
	}

	{
		// create invisible expansion object
		const double fMaxExpansion(RADIUS_LAMP_BIG + RADIUS_LAMP_PREVIEW_SIZE);
		mpExpansionObject = new E3dCubeObj(
			mp3DView->Get3DDefaultAttributes(),
			basegfx::B3DPoint(-fMaxExpansion, -fMaxExpansion, -fMaxExpansion),
			basegfx::B3DVector(2.0 * fMaxExpansion, 2.0 * fMaxExpansion, 2.0 * fMaxExpansion));
		mpScene->Insert3DObj( mpExpansionObject );
		SfxItemSet aSet(mpModel->GetItemPool());
		aSet.Put( XLineStyleItem( XLINE_NONE ) );
		aSet.Put( XFillStyleItem( XFILL_NONE ) );
		mpExpansionObject->SetMergedItemSet(aSet);
	}

	{
		// create lamp control object (Yellow lined object)
		// base circle
		const basegfx::B2DPolygon a2DCircle(basegfx::tools::createPolygonFromCircle(basegfx::B2DPoint(0.0, 0.0), RADIUS_LAMP_PREVIEW_SIZE));
		basegfx::B3DPolygon a3DCircle(basegfx::tools::createB3DPolygonFromB2DPolygon(a2DCircle));
		basegfx::B3DHomMatrix aTransform;

		aTransform.rotate(F_PI2, 0.0, 0.0);
		aTransform.translate(0.0, -RADIUS_LAMP_PREVIEW_SIZE, 0.0);
		a3DCircle.transform(aTransform);

		// create object for it
		mpLampBottomObject = new E3dPolygonObj(
			mp3DView->Get3DDefaultAttributes(),
			basegfx::B3DPolyPolygon(a3DCircle),
			true);
		mpScene->Insert3DObj( mpLampBottomObject );

		// half circle with stand
		basegfx::B2DPolygon a2DHalfCircle;
		a2DHalfCircle.append(basegfx::B2DPoint(RADIUS_LAMP_PREVIEW_SIZE, 0.0));
		a2DHalfCircle.append(basegfx::B2DPoint(RADIUS_LAMP_PREVIEW_SIZE, -RADIUS_LAMP_PREVIEW_SIZE));
		a2DHalfCircle.append(basegfx::tools::createPolygonFromEllipseSegment(
			basegfx::B2DPoint(0.0, 0.0), RADIUS_LAMP_PREVIEW_SIZE, RADIUS_LAMP_PREVIEW_SIZE, F_2PI - F_PI2, F_PI2));
		basegfx::B3DPolygon a3DHalfCircle(basegfx::tools::createB3DPolygonFromB2DPolygon(a2DHalfCircle));

		// create object for it
		mpLampShaftObject = new E3dPolygonObj(
			mp3DView->Get3DDefaultAttributes(),
			basegfx::B3DPolyPolygon(a3DHalfCircle),
			true);
		mpScene->Insert3DObj( mpLampShaftObject );

		// initially invisible
		SfxItemSet aSet(mpModel->GetItemPool());
		aSet.Put( XLineStyleItem( XLINE_NONE ) );
		aSet.Put( XFillStyleItem( XFILL_NONE ) );
		
		mpLampBottomObject->SetMergedItemSet(aSet);
		mpLampShaftObject->SetMergedItemSet(aSet);
	}

	{
		// change camera settings
		Camera3D& rCamera  = (Camera3D&) mpScene->GetCamera();
		const basegfx::B3DRange& rVolume = mpScene->GetBoundVolume();
		double fW = rVolume.getWidth();
		double fH = rVolume.getHeight();
		double fCamZ = rVolume.getMaxZ() + ((fW + fH) / 2.0);

		rCamera.SetAutoAdjustProjection(sal_False);
		rCamera.SetViewWindow(- fW / 2, - fH / 2, fW, fH);
		basegfx::B3DPoint aLookAt;
		double fDefaultCamPosZ = mp3DView->GetDefaultCamPosZ();
		basegfx::B3DPoint aCamPos(0.0, 0.0, fCamZ < fDefaultCamPosZ ? fDefaultCamPosZ : fCamZ);
		rCamera.SetPosAndLookAt(aCamPos, aLookAt);
		double fDefaultCamFocal = mp3DView->GetDefaultCamFocal();
		rCamera.SetFocalLength(fDefaultCamFocal);
		rCamera.SetDefaults(basegfx::B3DPoint(0.0, 0.0, fDefaultCamPosZ), aLookAt, fDefaultCamFocal);

		mpScene->SetCamera( rCamera );

		basegfx::B3DHomMatrix aNeutral;
		mpScene->SetTransform(aNeutral);
	}

	// invalidate SnapRects of objects
	mpScene->SetRectsDirty();
}

void Svx3DLightControl::ConstructLightObjects()
{
	for(sal_uInt32 a(0); a < MAX_NUMBER_LIGHTS; a++)
	{
		// get rid of evtl. existing light object
		if(maLightObjects[a])
		{
			mpScene->Remove3DObj(maLightObjects[a]);
			delete maLightObjects[a];
			maLightObjects[a] = 0;
		}

		if(GetLightOnOff(a))
		{
			const bool bIsSelectedLight(a == maSelectedLight);
			basegfx::B3DVector aDirection(GetLightDirection(a));
			aDirection.normalize();
			aDirection *= RADIUS_LAMP_PREVIEW_SIZE;

			const double fLampSize(bIsSelectedLight ? RADIUS_LAMP_BIG : RADIUS_LAMP_SMALL);
			E3dObject* pNewLight = new E3dSphereObj(
				mp3DView->Get3DDefaultAttributes(),
				basegfx::B3DPoint( 0, 0, 0 ),
				basegfx::B3DVector( fLampSize, fLampSize, fLampSize));
			mpScene->Insert3DObj(pNewLight);

			basegfx::B3DHomMatrix aTransform;
			aTransform.translate(aDirection.getX(), aDirection.getY(), aDirection.getZ());
			pNewLight->SetTransform(aTransform);

			SfxItemSet aSet(mpModel->GetItemPool());
			aSet.Put( XLineStyleItem( XLINE_NONE ) );
			aSet.Put( XFillStyleItem( XFILL_SOLID ) );
			aSet.Put( XFillColorItem(String(), GetLightColor(a)));
			pNewLight->SetMergedItemSet(aSet);
			
			maLightObjects[a] = pNewLight;
		}
	}
}

void Svx3DLightControl::AdaptToSelectedLight()
{
	if(NO_LIGHT_SELECTED == maSelectedLight)
	{
		// make mpLampBottomObject/mpLampShaftObject invisible
		SfxItemSet aSet(mpModel->GetItemPool());
		aSet.Put( XLineStyleItem( XLINE_NONE ) );
		aSet.Put( XFillStyleItem( XFILL_NONE ) );
		mpLampBottomObject->SetMergedItemSet(aSet);
		mpLampShaftObject->SetMergedItemSet(aSet);
	}
	else
	{
		basegfx::B3DVector aDirection(GetLightDirection(maSelectedLight));
		aDirection.normalize();

		// make mpLampBottomObject/mpLampShaftObject visible (yellow hairline)
		SfxItemSet aSet(mpModel->GetItemPool());
		aSet.Put( XLineStyleItem( XLINE_SOLID ) );
		aSet.Put( XLineColorItem(String(), COL_YELLOW));
		aSet.Put( XLineWidthItem(0));
		aSet.Put( XFillStyleItem( XFILL_NONE ) );
		mpLampBottomObject->SetMergedItemSet(aSet);
		mpLampShaftObject->SetMergedItemSet(aSet);

		// adapt transformation of mpLampShaftObject
		basegfx::B3DHomMatrix aTransform;
		double fRotateY(0.0);

		if(!basegfx::fTools::equalZero(aDirection.getZ()) || !basegfx::fTools::equalZero(aDirection.getX()))
		{
			fRotateY = atan2(-aDirection.getZ(), aDirection.getX());
		}

		aTransform.rotate(0.0, fRotateY, 0.0);
		mpLampShaftObject->SetTransform(aTransform);

		// adapt transformation of selected light
		E3dObject* pSelectedLight = maLightObjects[sal_Int32(maSelectedLight)];

		if(pSelectedLight)
		{
			aTransform.identity();
			aTransform.translate(
				aDirection.getX() * RADIUS_LAMP_PREVIEW_SIZE, 
				aDirection.getY() * RADIUS_LAMP_PREVIEW_SIZE, 
				aDirection.getZ() * RADIUS_LAMP_PREVIEW_SIZE);
			pSelectedLight->SetTransform(aTransform);
		}
	}
}

void Svx3DLightControl::TrySelection(Point aPosPixel)
{
	if(mpScene)
	{
		const Point aPosLogic(PixelToLogic(aPosPixel));
		const basegfx::B2DPoint aPoint(aPosLogic.X(), aPosLogic.Y());
		std::vector< const E3dCompoundObject* > aResult;
		getAllHit3DObjectsSortedFrontToBack(aPoint, *mpScene, aResult);

		if(!aResult.empty())
		{
			// exclude expansion object which will be part of
            // the hits. It's invisible, but for HitTest, it's included
			const E3dCompoundObject* pResult = 0;
            
            for(sal_uInt32 b(0); !pResult && b < aResult.size(); b++)
            {
                if(aResult[b] && aResult[b] != mpExpansionObject)
                {
                    pResult = aResult[b];
                }
            }

			if(pResult == mp3DObj)
			{
				if(!mbGeometrySelected)
				{
					mbGeometrySelected = true;
					maSelectedLight = NO_LIGHT_SELECTED;
					ConstructLightObjects();
					AdaptToSelectedLight();
					Invalidate();
					
					if(maSelectionChangeCallback.IsSet())
					{
						maSelectionChangeCallback.Call(this);
					}
				}
			}
			else
			{
				sal_uInt32 aNewSelectedLight(NO_LIGHT_SELECTED);

				for(sal_uInt32 a(0); a < MAX_NUMBER_LIGHTS; a++)
				{
					if(maLightObjects[a] && maLightObjects[a] == pResult)
					{
						aNewSelectedLight = a;
					}
				}

				if(aNewSelectedLight != maSelectedLight)
				{
					SelectLight(aNewSelectedLight);
		
					if(maSelectionChangeCallback.IsSet())
					{
						maSelectionChangeCallback.Call(this);
					}
				}
			}
		}
	}
}

void Svx3DLightControl::Paint(const Rectangle& rRect)
{
    Svx3DPreviewControl::Paint(rRect);
}

void Svx3DLightControl::MouseButtonDown( const MouseEvent& rMEvt )
{
	bool bCallParent(true);

	// switch state
	if(rMEvt.IsLeft())
	{
		if(IsSelectionValid() || mbGeometrySelected)
		{
			mbMouseMoved = false;
			bCallParent = false;
			maActionStartPoint = rMEvt.GetPosPixel();
			StartTracking();
		}
		else
		{
			// Einfacher Click ohne viel Bewegen, versuche eine
			// Selektion
			TrySelection(rMEvt.GetPosPixel());
			bCallParent = false;
		}
	}

	// call parent
	if(bCallParent)
	{
		Svx3DPreviewControl::MouseButtonDown(rMEvt);
	}
}

void Svx3DLightControl::Tracking( const TrackingEvent& rTEvt )
{
	if(rTEvt.IsTrackingEnded())
	{
		if(rTEvt.IsTrackingCanceled())
		{
			if(mbMouseMoved)
			{
				// interrupt tracking
				mbMouseMoved = false;

				if(mbGeometrySelected)
				{
					SetRotation(mfSaveActionStartVer, mfSaveActionStartHor, mfSaveActionStartRotZ);
				}
				else
				{
					SetPosition(mfSaveActionStartHor, mfSaveActionStartVer);
				}
				
				if(maChangeCallback.IsSet())
				{
					maChangeCallback.Call(this);
				}
			}
		}
		else
		{
			const MouseEvent& rMEvt = rTEvt.GetMouseEvent();
			
			if(mbMouseMoved)
			{
				// was change dinteractively
			}
			else
			{
				// simple click without much movement, try selection
				TrySelection(rMEvt.GetPosPixel());
			}
		}
	}
	else
	{
		const MouseEvent& rMEvt = rTEvt.GetMouseEvent();
		Point aDeltaPos = rMEvt.GetPosPixel() - maActionStartPoint;

		if(!mbMouseMoved)
		{
			if(sal_Int32(aDeltaPos.X() * aDeltaPos.X() + aDeltaPos.Y() * aDeltaPos.Y()) > mnInteractionStartDistance)
			{
				if(mbGeometrySelected)
				{
					GetRotation(mfSaveActionStartVer, mfSaveActionStartHor, mfSaveActionStartRotZ);
				}
				else
				{
					// intercation start, save values
					GetPosition(mfSaveActionStartHor, mfSaveActionStartVer);
				}

				mbMouseMoved = true;
			}
		}

		if(mbMouseMoved)
		{
			if(mbGeometrySelected)
			{
				double fNewRotX = mfSaveActionStartVer - ((double)aDeltaPos.Y() * F_PI180);
				double fNewRotY = mfSaveActionStartHor + ((double)aDeltaPos.X() * F_PI180);

				// cut horizontal
				while(fNewRotY < 0.0)
				{
					fNewRotY += F_2PI;
				}
				
				while(fNewRotY >= F_2PI)
				{
					fNewRotY -= F_2PI;
				}

				// cut vertical
				if(fNewRotX < -F_PI2)
				{
					fNewRotX = -F_PI2;
				}
				
				if(fNewRotX > F_PI2)
				{
					fNewRotX = F_PI2;
				}

				SetRotation(fNewRotX, fNewRotY, mfSaveActionStartRotZ);

				if(maChangeCallback.IsSet())
				{
					maChangeCallback.Call(this);
				}
			}
			else
			{
				// interaction in progress
				double fNewPosHor = mfSaveActionStartHor + ((double)aDeltaPos.X());
				double fNewPosVer = mfSaveActionStartVer - ((double)aDeltaPos.Y());

				// cut horizontal
				while(fNewPosHor < 0.0)
				{
					fNewPosHor += 360.0;
				}

				while(fNewPosHor >= 360.0)
				{
					fNewPosHor -= 360.0;
				}

				// cut vertical
				if(fNewPosVer < -90.0)
				{
					fNewPosVer = -90.0;
				}

				if(fNewPosVer > 90.0)
				{
					fNewPosVer = 90.0;
				}

				SetPosition(fNewPosHor, fNewPosVer);

				if(maChangeCallback.IsSet())
				{
					maChangeCallback.Call(this);
				}
			}
		}
	}
}

void Svx3DLightControl::Resize()
{
	// set size of page
	const Size aSize(PixelToLogic(GetSizePixel()));
	mpFmPage->SetSize(aSize);

	// set position and size of scene
	mpScene->SetSnapRect(Rectangle(Point(0, 0), aSize));
}

void Svx3DLightControl::SetObjectType(sal_uInt16 nType)
{
	// call parent
	Svx3DPreviewControl::SetObjectType(nType);

	// apply object rotation
	if(mp3DObj)
	{
		basegfx::B3DHomMatrix aObjectRotation;
		aObjectRotation.rotate(mfRotateX, mfRotateY, mfRotateZ);
		mp3DObj->SetTransform(aObjectRotation);
	}
}

bool Svx3DLightControl::IsSelectionValid()
{
	if((NO_LIGHT_SELECTED != maSelectedLight) && (GetLightOnOff(maSelectedLight)))
	{
		return true;
	}

	return false;
}

void Svx3DLightControl::GetPosition(double& rHor, double& rVer)
{
	if(IsSelectionValid())
	{
		basegfx::B3DVector aDirection(GetLightDirection(maSelectedLight));
		aDirection.normalize();
		rHor = atan2(-aDirection.getX(), -aDirection.getZ()) + F_PI; // 0..2PI
		rVer = atan2(aDirection.getY(), aDirection.getXZLength()); // -PI2..PI2
		rHor /= F_PI180; // 0..360.0
		rVer /= F_PI180; // -90.0..90.0
	}
	if(IsGeometrySelected())
	{
		rHor = mfRotateY / F_PI180; // 0..360.0
		rVer = mfRotateX / F_PI180; // -90.0..90.0
	}
}

void Svx3DLightControl::SetPosition(double fHor, double fVer)
{
	if(IsSelectionValid())
	{
		// set selected light's direction
		fHor = (fHor * F_PI180) - F_PI; // -PI..PI
		fVer *= F_PI180; // -PI2..PI2
		basegfx::B3DVector aDirection(cos(fVer) * -sin(fHor), sin(fVer), cos(fVer) * -cos(fHor));
		aDirection.normalize();

		if(!aDirection.equal(GetLightDirection(maSelectedLight)))
		{
			// set changed light direction at SdrScene
			SfxItemSet aSet(mpModel->GetItemPool());

            switch(maSelectedLight)
            {
                case 0: aSet.Put(Svx3DLightDirection1Item(aDirection)); break;
                case 1: aSet.Put(Svx3DLightDirection2Item(aDirection)); break;
                case 2: aSet.Put(Svx3DLightDirection3Item(aDirection)); break;
                case 3: aSet.Put(Svx3DLightDirection4Item(aDirection)); break;
                case 4: aSet.Put(Svx3DLightDirection5Item(aDirection)); break;
                case 5: aSet.Put(Svx3DLightDirection6Item(aDirection)); break;
                case 6: aSet.Put(Svx3DLightDirection7Item(aDirection)); break;
                default:
                case 7: aSet.Put(Svx3DLightDirection8Item(aDirection)); break;
            }

            mpScene->SetMergedItemSet(aSet);

			// correct 3D light's and LampFrame's geometries
			AdaptToSelectedLight();
			Invalidate();
		}
	}
	if(IsGeometrySelected())
	{
		if(mfRotateX != fVer || mfRotateY != fHor)
		{
			mfRotateX = fVer * F_PI180;
			mfRotateY = fHor * F_PI180;

			if(mp3DObj)
			{
				basegfx::B3DHomMatrix aObjectRotation;
				aObjectRotation.rotate(mfRotateX, mfRotateY, mfRotateZ);
				mp3DObj->SetTransform(aObjectRotation);

				Invalidate();
			}
		}
	}
}

void Svx3DLightControl::SetRotation(double fRotX, double fRotY, double fRotZ)
{
	if(IsGeometrySelected())
	{
		if(fRotX != mfRotateX || fRotY != mfRotateY || fRotZ != mfRotateZ)
		{
			mfRotateX = fRotX;
			mfRotateY = fRotY;
			mfRotateZ = fRotZ;

			if(mp3DObj)
			{
				basegfx::B3DHomMatrix aObjectRotation;
				aObjectRotation.rotate(mfRotateX, mfRotateY, mfRotateZ);
				mp3DObj->SetTransform(aObjectRotation);

				Invalidate();
			}
		}
	}
}

void Svx3DLightControl::GetRotation(double& rRotX, double& rRotY, double& rRotZ)
{
	rRotX = mfRotateX;
	rRotY = mfRotateY;
	rRotZ = mfRotateZ;
}

void Svx3DLightControl::Set3DAttributes( const SfxItemSet& rAttr )
{
    // call parent
    Svx3DPreviewControl::Set3DAttributes(rAttr);

    if(maSelectedLight != NO_LIGHT_SELECTED && !GetLightOnOff(maSelectedLight))
    {
        // selected light is no more active, select new one
		maSelectedLight = NO_LIGHT_SELECTED;
    }

    // local updates
	ConstructLightObjects();
	AdaptToSelectedLight();
	Invalidate();
}

void Svx3DLightControl::SelectLight(sal_uInt32 nLightNumber)
{
    if(nLightNumber > 7)
    {
        nLightNumber = NO_LIGHT_SELECTED;
    }

	if(NO_LIGHT_SELECTED != nLightNumber)
	{
		if(!GetLightOnOff(nLightNumber))
		{
			nLightNumber = NO_LIGHT_SELECTED;
		}
	}

	if(nLightNumber != maSelectedLight)
	{
		maSelectedLight = nLightNumber;
		mbGeometrySelected = false;
		ConstructLightObjects();
		AdaptToSelectedLight();
		Invalidate();
	}
}

bool Svx3DLightControl::GetLightOnOff(sal_uInt32 nNum) const
{
    if(nNum <= 7)
    {
        const SfxItemSet aLightItemSet(Get3DAttributes());

        switch(nNum)
        {
            case 0 : return ((const Svx3DLightOnOff1Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_1)).GetValue();
            case 1 : return ((const Svx3DLightOnOff2Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_2)).GetValue();
            case 2 : return ((const Svx3DLightOnOff3Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_3)).GetValue();
            case 3 : return ((const Svx3DLightOnOff4Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_4)).GetValue();
            case 4 : return ((const Svx3DLightOnOff5Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_5)).GetValue();
            case 5 : return ((const Svx3DLightOnOff6Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_6)).GetValue();
            case 6 : return ((const Svx3DLightOnOff7Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_7)).GetValue();
            case 7 : return ((const Svx3DLightOnOff8Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_8)).GetValue();
        }
    }

    return false;
}

Color Svx3DLightControl::GetLightColor(sal_uInt32 nNum) const
{
    if(nNum <= 7)
    {
        const SfxItemSet aLightItemSet(Get3DAttributes());

        switch(nNum)
        {
            case 0 : return ((const Svx3DLightcolor1Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_1)).GetValue();
            case 1 : return ((const Svx3DLightcolor2Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_2)).GetValue();
            case 2 : return ((const Svx3DLightcolor3Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_3)).GetValue();
            case 3 : return ((const Svx3DLightcolor4Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_4)).GetValue();
            case 4 : return ((const Svx3DLightcolor5Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_5)).GetValue();
            case 5 : return ((const Svx3DLightcolor6Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_6)).GetValue();
            case 6 : return ((const Svx3DLightcolor7Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_7)).GetValue();
            case 7 : return ((const Svx3DLightcolor8Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_8)).GetValue();
        }
    }

    return Color(COL_BLACK);
}

basegfx::B3DVector Svx3DLightControl::GetLightDirection(sal_uInt32 nNum) const
{
    if(nNum <= 7)
    {
        const SfxItemSet aLightItemSet(Get3DAttributes());

        switch(nNum)
        {
            case 0 : return ((const Svx3DLightDirection1Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_1)).GetValue();
            case 1 : return ((const Svx3DLightDirection2Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_2)).GetValue();
            case 2 : return ((const Svx3DLightDirection3Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_3)).GetValue();
            case 3 : return ((const Svx3DLightDirection4Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_4)).GetValue();
            case 4 : return ((const Svx3DLightDirection5Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_5)).GetValue();
            case 5 : return ((const Svx3DLightDirection6Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_6)).GetValue();
            case 6 : return ((const Svx3DLightDirection7Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_7)).GetValue();
            case 7 : return ((const Svx3DLightDirection8Item&)aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_8)).GetValue();
        }
    }

    return basegfx::B3DVector();
}

//////////////////////////////////////////////////////////////////////////////

SvxLightCtl3D::SvxLightCtl3D( Window* pParent, const ResId& rResId)
:	Control(pParent, rResId),
	maLightControl(this, 0),
	maHorScroller(this, WB_HORZ | WB_DRAG),
	maVerScroller(this, WB_VERT | WB_DRAG),
	maSwitcher(this, 0)
{
	// init members
	Init();
}

SvxLightCtl3D::SvxLightCtl3D( Window* pParent, WinBits nStyle )
:	Control(pParent, nStyle),
	maLightControl(this, 0),
	maHorScroller(this, WB_HORZ | WB_DRAG),
	maVerScroller(this, WB_VERT | WB_DRAG),
	maSwitcher(this, 0)
{
	// init members
	Init();
}

void SvxLightCtl3D::Init()
{
	// #i58240# set HelpIDs for scrollbars and switcher
	maHorScroller.SetHelpId(HID_CTRL3D_HSCROLL);
	maVerScroller.SetHelpId(HID_CTRL3D_VSCROLL);
	maSwitcher.SetHelpId(HID_CTRL3D_SWITCHER);
	maSwitcher.SetAccessibleName(String(SVX_RES(STR_SWITCH)));

	// Light preview
	maLightControl.Show();
	maLightControl.SetChangeCallback( LINK(this, SvxLightCtl3D, InternalInteractiveChange) );
	maLightControl.SetSelectionChangeCallback( LINK(this, SvxLightCtl3D, InternalSelectionChange) );

	// Horiz Scrollbar
	maHorScroller.Show();
	maHorScroller.SetRange(Range(0, 36000));
	maHorScroller.SetLineSize(100);
	maHorScroller.SetPageSize(1000);
	maHorScroller.SetScrollHdl( LINK(this, SvxLightCtl3D, ScrollBarMove) );

	// Vert Scrollbar
	maVerScroller.Show();
	maVerScroller.SetRange(Range(0, 18000));
	maVerScroller.SetLineSize(100);
	maVerScroller.SetPageSize(1000);
	maVerScroller.SetScrollHdl( LINK(this, SvxLightCtl3D, ScrollBarMove) );

	// Switch Button
	maSwitcher.Show();
	maSwitcher.SetClickHdl( LINK(this, SvxLightCtl3D, ButtonPress) );

	// check selection
	CheckSelection();

	// new layout
	NewLayout();
}

SvxLightCtl3D::~SvxLightCtl3D()
{
}

void SvxLightCtl3D::Resize()
{
	// call parent
	Control::Resize();

	// new layout
	NewLayout();
}

void SvxLightCtl3D::NewLayout()
{
	// Layout members
	const Size aSize(GetOutputSizePixel());
	const sal_Int32 nScrollSize(maHorScroller.GetSizePixel().Height());

	// Preview control
	Point aPoint(0, 0);
	Size aDestSize(aSize.Width() - nScrollSize, aSize.Height() - nScrollSize);
	maLightControl.SetPosSizePixel(aPoint, aDestSize);

	// hor scrollbar
	aPoint.Y() = aSize.Height() - nScrollSize;
	aDestSize.Height() = nScrollSize;
	maHorScroller.SetPosSizePixel(aPoint, aDestSize);

	// vert scrollbar
	aPoint.X() = aSize.Width() - nScrollSize;
	aPoint.Y() = 0;
	aDestSize.Width() = nScrollSize;
	aDestSize.Height() = aSize.Height() - nScrollSize;
	maVerScroller.SetPosSizePixel(aPoint, aDestSize);

	// button
	aPoint.Y() = aSize.Height() - nScrollSize;
	aDestSize.Height() = nScrollSize;
	maSwitcher.SetPosSizePixel(aPoint, aDestSize);
}

void SvxLightCtl3D::CheckSelection()
{
	const bool bSelectionValid(maLightControl.IsSelectionValid() || maLightControl.IsGeometrySelected());
	maHorScroller.Enable(bSelectionValid);
	maVerScroller.Enable(bSelectionValid);

	if(bSelectionValid)
	{
		double fHor, fVer;
		maLightControl.GetPosition(fHor, fVer);
		maHorScroller.SetThumbPos( sal_Int32(fHor * 100.0) );
		maVerScroller.SetThumbPos( 18000 - sal_Int32((fVer + 90.0) * 100.0) );
	}
}

void SvxLightCtl3D::move( double fDeltaHor, double fDeltaVer )
{
	double fHor, fVer;

	maLightControl.GetPosition(fHor, fVer);
	fHor += fDeltaHor;
	fVer += fDeltaVer;

	if( fVer > 90.0 )
		return;

	if ( fVer < -90.0 )
		return;

	maLightControl.SetPosition(fHor, fVer);
	maHorScroller.SetThumbPos( sal_Int32(fHor * 100.0) );
	maVerScroller.SetThumbPos( 18000 - sal_Int32((fVer + 90.0) * 100.0) );

	if(maUserInteractiveChangeCallback.IsSet())
    {
		maUserInteractiveChangeCallback.Call(this);
    }
}

void SvxLightCtl3D::KeyInput( const KeyEvent& rKEvt )
{
    const KeyCode aCode(rKEvt.GetKeyCode());

    if( aCode.GetModifier() )
    {
        Control::KeyInput( rKEvt );
        return;
    }

    switch ( aCode.GetCode() )
    {
        case KEY_SPACE:
        {
            break;
        }
        case KEY_LEFT:
        {
            move(  -4.0,  0.0 ); // #i58242# changed move direction in X
            break;
        }
        case KEY_RIGHT:
        {
            move( 4.0,  0.0 ); // #i58242# changed move direction in X
            break;
        }
        case KEY_UP:
        {
            move(  0.0,  4.0 );
            break;
        }
        case KEY_DOWN:
        {
            move(  0.0, -4.0 );
            break;
        }
        case KEY_PAGEUP:
		{
            sal_Int32 nLight(maLightControl.GetSelectedLight() - 1);

			while((nLight >= 0) && !maLightControl.GetLightOnOff(nLight))
            {
				nLight--;
            }

            if(nLight < 0)
            {
                nLight = 7;
			    
                while((nLight >= 0) && !maLightControl.GetLightOnOff(nLight))
                {
				    nLight--;
                }
            }

            if(nLight >= 0)
            {
				maLightControl.SelectLight(nLight);
				CheckSelection();

				if(maUserSelectionChangeCallback.IsSet())
                {
					maUserSelectionChangeCallback.Call(this);
                }
            }

            break;
		}
        case KEY_PAGEDOWN:
		{
            sal_Int32 nLight(maLightControl.GetSelectedLight() - 1);

            while(nLight <= 7 && !maLightControl.GetLightOnOff(nLight))
            {
                nLight++;
            }

            if(nLight > 7)
            {
                nLight = 0;

                while(nLight <= 7 && !maLightControl.GetLightOnOff(nLight))
                {
                    nLight++;
                }
            }

            if(nLight <= 7)
            {
				maLightControl.SelectLight(nLight);
				CheckSelection();

				if(maUserSelectionChangeCallback.IsSet())
                {
					maUserSelectionChangeCallback.Call(this);
                }
            }

            break;
		}
		default:
        {
	        Control::KeyInput( rKEvt );
			break;
        }
    }
}

void SvxLightCtl3D::GetFocus()
{
	Control::GetFocus();

	if(HasFocus() && IsEnabled())
	{
		CheckSelection();

		Size aFocusSize = maLightControl.GetOutputSizePixel();

		aFocusSize.Width() -= 4;
		aFocusSize.Height() -= 4;

		Rectangle aFocusRect( Point( 2, 2 ), aFocusSize );

		aFocusRect = maLightControl.PixelToLogic( aFocusRect );

		maLightControl.ShowFocus( aFocusRect );
	}
}

void SvxLightCtl3D::LoseFocus()
{
	Control::LoseFocus();

	maLightControl.HideFocus();
}

IMPL_LINK( SvxLightCtl3D, ScrollBarMove, void*, EMPTYARG)
{
	const sal_Int32 nHor(maHorScroller.GetThumbPos());
	const sal_Int32 nVer(maVerScroller.GetThumbPos());

	maLightControl.SetPosition(
		((double)nHor) / 100.0,
		((double)((18000 - nVer) - 9000)) / 100.0);

	if(maUserInteractiveChangeCallback.IsSet())
    {
		maUserInteractiveChangeCallback.Call(this);
    }

	return 0;
}

IMPL_LINK( SvxLightCtl3D, ButtonPress, void*, EMPTYARG)
{
    if(PREVIEW_OBJECTTYPE_SPHERE == GetSvx3DLightControl().GetObjectType())
    {
        GetSvx3DLightControl().SetObjectType(PREVIEW_OBJECTTYPE_CUBE);
    }
    else
    {
        GetSvx3DLightControl().SetObjectType(PREVIEW_OBJECTTYPE_SPHERE);
    }

	return 0;
}

IMPL_LINK( SvxLightCtl3D, InternalInteractiveChange, void*, EMPTYARG)
{
	double fHor, fVer;

	maLightControl.GetPosition(fHor, fVer);
	maHorScroller.SetThumbPos( sal_Int32(fHor * 100.0) );
	maVerScroller.SetThumbPos( 18000 - sal_Int32((fVer + 90.0) * 100.0) );

	if(maUserInteractiveChangeCallback.IsSet())
    {
		maUserInteractiveChangeCallback.Call(this);
    }

	return 0;
}

IMPL_LINK( SvxLightCtl3D, InternalSelectionChange, void*, EMPTYARG)
{
	CheckSelection();

	if(maUserSelectionChangeCallback.IsSet())
    {
		maUserSelectionChangeCallback.Call(this);
    }

	return 0;
}

//////////////////////////////////////////////////////////////////////////////
// eof
