/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2008 by Sun Microsystems, Inc.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * $RCSfile: gradtrns.cxx,v $
 * $Revision: 1.7.226.1 $
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

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

#include "gradtrns.hxx"
#include <svx/svdobj.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <vcl/salbtype.hxx>		// FRound

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

void GradTransformer::GradToVec(GradTransGradient& rG, GradTransVector& rV, const SdrObject* pObj)
{
	// handle start color
	rV.aCol1 = rG.aGradient.GetStartColor();
	if(100 != rG.aGradient.GetStartIntens())
	{
		const double fFact((double)rG.aGradient.GetStartIntens() / 100.0);
        rV.aCol1 = Color(rV.aCol1.getBColor() * fFact);
	}

	// handle end color
	rV.aCol2 = rG.aGradient.GetEndColor();
	if(100 != rG.aGradient.GetEndIntens())
	{
		const double fFact((double)rG.aGradient.GetEndIntens() / 100.0);
        rV.aCol2 = Color(rV.aCol2.getBColor() * fFact);
	}

	// calc the basic positions
	const Rectangle aObjectSnapRectangle(pObj->GetSnapRect());
	const basegfx::B2DRange aRange(aObjectSnapRectangle.Left(), aObjectSnapRectangle.Top(), aObjectSnapRectangle.Right(), aObjectSnapRectangle.Bottom());
	const basegfx::B2DPoint aCenter(aRange.getCenter());
	basegfx::B2DPoint aStartPos, aEndPos;

	switch(rG.aGradient.GetGradientStyle())
	{
		case XGRAD_LINEAR :
		{
			aStartPos = basegfx::B2DPoint(aCenter.getX(), aRange.getMinY());
			aEndPos = basegfx::B2DPoint(aCenter.getX(), aRange.getMaximum().getY());

			if(rG.aGradient.GetBorder())
			{
				basegfx::B2DVector aFullVec(aStartPos - aEndPos);
				const double fLen = (aFullVec.getLength() * (100.0 - (double)rG.aGradient.GetBorder())) / 100.0;
				aFullVec.normalize();
				aStartPos = aEndPos + (aFullVec * fLen);
			}

			if(rG.aGradient.GetAngle())
			{
				const double fAngle = (double)rG.aGradient.GetAngle() * (F_PI180 / 10.0);
				basegfx::B2DHomMatrix aTransformation;

				aTransformation.translate(-aCenter.getX(), -aCenter.getY());
				aTransformation.rotate(-fAngle);
				aTransformation.translate(aCenter.getX(), aCenter.getY());
				
				aStartPos *= aTransformation;
				aEndPos *= aTransformation;
			}
			break;
		}
		case XGRAD_AXIAL :
		{
			aStartPos = aCenter;
			aEndPos = basegfx::B2DPoint(aCenter.getX(), aRange.getMaximum().getY());

			if(rG.aGradient.GetBorder())
			{
				basegfx::B2DVector aFullVec(aEndPos - aStartPos);
				const double fLen = (aFullVec.getLength() * (100.0 - (double)rG.aGradient.GetBorder())) / 100.0;
				aFullVec.normalize();
				aEndPos = aStartPos + (aFullVec * fLen);
			}
			
			if(rG.aGradient.GetAngle())
			{
				const double fAngle = (double)rG.aGradient.GetAngle() * (F_PI180 / 10.0);
				basegfx::B2DHomMatrix aTransformation;

				aTransformation.translate(-aCenter.getX(), -aCenter.getY());
				aTransformation.rotate(-fAngle);
				aTransformation.translate(aCenter.getX(), aCenter.getY());
				
				aStartPos *= aTransformation;
				aEndPos *= aTransformation;
			}
			break;
		}
		case XGRAD_RADIAL :
		case XGRAD_SQUARE :
		{
			aStartPos = basegfx::B2DPoint(aRange.getMinX(), aRange.getMaximum().getY());
			aEndPos = basegfx::B2DPoint(aRange.getMinX(), aRange.getMinY());

			if(rG.aGradient.GetBorder())
			{
				basegfx::B2DVector aFullVec(aStartPos - aEndPos);
				const double fLen = (aFullVec.getLength() * (100.0 - (double)rG.aGradient.GetBorder())) / 100.0;
				aFullVec.normalize();
				aStartPos = aEndPos + (aFullVec * fLen);
			}
			
			if(rG.aGradient.GetAngle())
			{
				const double fAngle = (double)rG.aGradient.GetAngle() * (F_PI180 / 10.0);
				basegfx::B2DHomMatrix aTransformation;

				aTransformation.translate(-aEndPos.getX(), -aEndPos.getY());
				aTransformation.rotate(-fAngle);
				aTransformation.translate(aEndPos.getX(), aEndPos.getY());

				aStartPos *= aTransformation;
				aEndPos *= aTransformation;
			}
			
			if(rG.aGradient.GetXOffset() || rG.aGradient.GetYOffset())
			{
				basegfx::B2DPoint aOffset(
					(aRange.getWidth() * rG.aGradient.GetXOffset()) / 100.0,
					(aRange.getHeight() * rG.aGradient.GetYOffset()) / 100.0);

				aStartPos += aOffset;
				aEndPos += aOffset;
			}

			break;
		}
		case XGRAD_ELLIPTICAL :
		case XGRAD_RECT :
		{
			aStartPos = basegfx::B2DPoint(aRange.getMinX(), aCenter.getY());
			aEndPos = basegfx::B2DPoint(aRange.getMinX(), aRange.getMinY());

			if(rG.aGradient.GetBorder())
			{
				basegfx::B2DVector aFullVec(aStartPos - aEndPos);
				const double fLen = (aFullVec.getLength() * (100.0 - (double)rG.aGradient.GetBorder())) / 100.0;
				aFullVec.normalize();
				aStartPos = aEndPos + (aFullVec * fLen);
			}
			
			if(rG.aGradient.GetAngle())
			{
				const double fAngle = (double)rG.aGradient.GetAngle() * (F_PI180 / 10.0);
				basegfx::B2DHomMatrix aTransformation;

				aTransformation.translate(-aEndPos.getX(), -aEndPos.getY());
				aTransformation.rotate(-fAngle);
				aTransformation.translate(aEndPos.getX(), aEndPos.getY());

				aStartPos *= aTransformation;
				aEndPos *= aTransformation;
			}
			
			if(rG.aGradient.GetXOffset() || rG.aGradient.GetYOffset())
			{
				basegfx::B2DPoint aOffset(
					(aRange.getWidth() * rG.aGradient.GetXOffset()) / 100.0,
					(aRange.getHeight() * rG.aGradient.GetYOffset()) / 100.0);

				aStartPos += aOffset;
				aEndPos += aOffset;
			}

			break;
		}
	}

	// set values for vector positions now
	rV.maPositionA = aStartPos;
	rV.maPositionB = aEndPos;
}

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

void GradTransformer::VecToGrad(GradTransVector& rV, GradTransGradient& rG, GradTransGradient& rGOld, const SdrObject* pObj, 
	sal_Bool bMoveSingle, sal_Bool bMoveFirst)
{
	// fill old gradient to new gradient to have a base
	rG = rGOld;

	// handle color changes
	if(rV.aCol1 != rGOld.aGradient.GetStartColor())
	{
		rG.aGradient.SetStartColor(rV.aCol1);
		rG.aGradient.SetStartIntens(100);
	}
	if(rV.aCol2 != rGOld.aGradient.GetEndColor())
	{
		rG.aGradient.SetEndColor(rV.aCol2);
		rG.aGradient.SetEndIntens(100);
	}

	// calc the basic positions
	const Rectangle aObjectSnapRectangle(pObj->GetSnapRect());
	const basegfx::B2DRange aRange(aObjectSnapRectangle.Left(), aObjectSnapRectangle.Top(), aObjectSnapRectangle.Right(), aObjectSnapRectangle.Bottom());
	const basegfx::B2DPoint aCenter(aRange.getCenter());
	basegfx::B2DPoint aStartPos(rV.maPositionA);
	basegfx::B2DPoint aEndPos(rV.maPositionB);

	switch(rG.aGradient.GetGradientStyle())
	{
		case XGRAD_LINEAR :
		{
			if(!bMoveSingle || (bMoveSingle && !bMoveFirst))
			{
				basegfx::B2DVector aFullVec(aEndPos - aStartPos);

				if(bMoveSingle)
				{
					aFullVec = aEndPos - aCenter;
				}
			
				aFullVec.normalize();
				
				double fNewFullAngle(atan2(aFullVec.getY(), aFullVec.getX()));
				fNewFullAngle /= F_PI180;
				fNewFullAngle *= -10.0;
				fNewFullAngle += 900.0;

				// clip
				while(fNewFullAngle < 0.0)
				{
					fNewFullAngle += 3600.0;
				}

				while(fNewFullAngle >= 3600.0)
				{
					fNewFullAngle -= 3600.0;
				}

				// to int and set
				sal_Int32 nNewAngle = FRound(fNewFullAngle);

				if(nNewAngle != rGOld.aGradient.GetAngle())
				{
					rG.aGradient.SetAngle(nNewAngle);
				}
			}

			if(!bMoveSingle || (bMoveSingle && bMoveFirst))
			{
				const basegfx::B2DVector aFullVec(aEndPos - aStartPos);
				const basegfx::B2DPoint aBottomLeft(aRange.getMinX(), aRange.getMaximum().getY());
				const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY());
				const basegfx::B2DVector aOldVec(aBottomLeft - aTopLeft);
				const double fFullLen(aFullVec.getLength());
				const double fOldLen(aOldVec.getLength());
				const double fNewBorder((fFullLen * 100.0) / fOldLen);
				sal_Int32 nNewBorder(100L - FRound(fNewBorder));

				// clip
				if(nNewBorder < 0L)
				{
					nNewBorder = 0L;
				}

				if(nNewBorder > 100L)
				{
					nNewBorder = 100L;
				}

				// set
				if(nNewBorder != rG.aGradient.GetBorder())
				{
					rG.aGradient.SetBorder((sal_uInt16)nNewBorder);
				}
			}

			break;
		}
		case XGRAD_AXIAL :
		{
			if(!bMoveSingle || (bMoveSingle && !bMoveFirst))
			{
				basegfx::B2DVector aFullVec(aEndPos - aCenter);
				const basegfx::B2DVector aOldVec(basegfx::B2DPoint(aCenter.getX(), aRange.getMaximum().getY()) - aCenter);
				const double fFullLen(aFullVec.getLength());
				const double fOldLen(aOldVec.getLength());
				const double fNewBorder((fFullLen * 100.0) / fOldLen);
				sal_Int32 nNewBorder = 100 - FRound(fNewBorder);

				// clip
				if(nNewBorder < 0L)
				{
					nNewBorder = 0L;
				}

				if(nNewBorder > 100L)
				{
					nNewBorder = 100L;
				}

				// set
				if(nNewBorder != rG.aGradient.GetBorder())
				{
					rG.aGradient.SetBorder((sal_uInt16)nNewBorder);
				}
				
				aFullVec.normalize();
				double fNewFullAngle(atan2(aFullVec.getY(), aFullVec.getX()));
				fNewFullAngle /= F_PI180;
				fNewFullAngle *= -10.0;
				fNewFullAngle += 900.0;

				// clip
				while(fNewFullAngle < 0.0)
				{
					fNewFullAngle += 3600.0;
				}

				while(fNewFullAngle >= 3600.0)
				{
					fNewFullAngle -= 3600.0;
				}

				// to int and set
				const sal_Int32 nNewAngle(FRound(fNewFullAngle));

				if(nNewAngle != rGOld.aGradient.GetAngle())
				{
					rG.aGradient.SetAngle(nNewAngle);
				}
			}

			break;
		}
		case XGRAD_RADIAL :
		case XGRAD_SQUARE :
		{
			if(!bMoveSingle || (bMoveSingle && !bMoveFirst))
			{
				const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY());
				const basegfx::B2DPoint aOffset(aEndPos - aTopLeft);
				sal_Int32 nNewXOffset(FRound((aOffset.getX() * 100.0) / aRange.getWidth()));
				sal_Int32 nNewYOffset(FRound((aOffset.getY() * 100.0) / aRange.getHeight()));

				// clip
				if(nNewXOffset < 0L)
				{
					nNewXOffset = 0L;
				}

				if(nNewXOffset > 100L)
				{
					nNewXOffset = 100L;
				}

				if(nNewYOffset < 0L)
				{
					nNewYOffset = 0L;
				}

				if(nNewYOffset > 100L)
				{
					nNewYOffset = 100L;
				}

				rG.aGradient.SetXOffset((sal_uInt16)nNewXOffset);
				rG.aGradient.SetYOffset((sal_uInt16)nNewYOffset);

				aStartPos -= aOffset;
				aEndPos -= aOffset;
			}

			if(!bMoveSingle || (bMoveSingle && bMoveFirst))
			{
				basegfx::B2DVector aFullVec(aStartPos - aEndPos);
				const basegfx::B2DPoint aBottomLeft(aRange.getMinX(), aRange.getMaximum().getY());
				const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY());
				const basegfx::B2DVector aOldVec(aBottomLeft - aTopLeft);
				const double fFullLen(aFullVec.getLength());
				const double fOldLen(aOldVec.getLength());
				const double fNewBorder((fFullLen * 100.0) / fOldLen);
				sal_Int32 nNewBorder(100L - FRound(fNewBorder));

				// clip
				if(nNewBorder < 0L)
				{
					nNewBorder = 0L;
				}

				if(nNewBorder > 100L)
				{
					nNewBorder = 100L;
				}

				// set
				if(nNewBorder != rG.aGradient.GetBorder())
				{
					rG.aGradient.SetBorder((sal_uInt16)nNewBorder);
				}

				// angle is not definitely necessary for these modes, but it makes
				// controlling more fun for the user
				aFullVec.normalize();
				double fNewFullAngle(atan2(aFullVec.getY(), aFullVec.getX()));
				fNewFullAngle /= F_PI180;
				fNewFullAngle *= -10.0;
				fNewFullAngle += 900.0;

				// clip
				while(fNewFullAngle < 0.0)
				{
					fNewFullAngle += 3600.0;
				}

				while(fNewFullAngle >= 3600.0)
				{
					fNewFullAngle -= 3600.0;
				}

				// to int and set
				const sal_Int32 nNewAngle(FRound(fNewFullAngle));

				if(nNewAngle != rGOld.aGradient.GetAngle())
				{
					rG.aGradient.SetAngle(nNewAngle);
				}
			}

			break;
		}
		case XGRAD_ELLIPTICAL :
		case XGRAD_RECT :
		{
			if(!bMoveSingle || (bMoveSingle && !bMoveFirst))
			{
				const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY());
				const basegfx::B2DPoint aOffset(aEndPos - aTopLeft);
				sal_Int32 nNewXOffset(FRound((aOffset.getX() * 100.0) / aRange.getWidth()));
				sal_Int32 nNewYOffset(FRound((aOffset.getY() * 100.0) / aRange.getHeight()));

				// clip
				if(nNewXOffset < 0L)
				{
					nNewXOffset = 0L;
				}

				if(nNewXOffset > 100L)
				{
					nNewXOffset = 100L;
				}

				if(nNewYOffset < 0L)
				{
					nNewYOffset = 0L;
				}

				if(nNewYOffset > 100L)
				{
					nNewYOffset = 100L;
				}

				rG.aGradient.SetXOffset((sal_uInt16)nNewXOffset);
				rG.aGradient.SetYOffset((sal_uInt16)nNewYOffset);

				aStartPos -= aOffset;
				aEndPos -= aOffset;
			}

			if(!bMoveSingle || (bMoveSingle && bMoveFirst))
			{
				basegfx::B2DVector aFullVec(aStartPos - aEndPos);
				const basegfx::B2DPoint aTopLeft(aRange.getMinX(), aRange.getMinY());
				const basegfx::B2DPoint aCenterLeft(aRange.getMinX(), aRange.getHeight());
				const basegfx::B2DVector aOldVec(aCenterLeft - aTopLeft);
				const double fFullLen(aFullVec.getLength());
				const double fOldLen(aOldVec.getLength());
				const double fNewBorder((fFullLen * 100.0) / fOldLen);
				sal_Int32 nNewBorder(100L - FRound(fNewBorder));

				// clip
				if(nNewBorder < 0L)
				{
					nNewBorder = 0L;
				}

				if(nNewBorder > 100L)
				{
					nNewBorder = 100L;
				}

				// set
				if(nNewBorder != rG.aGradient.GetBorder())
				{
					rG.aGradient.SetBorder((sal_uInt16)nNewBorder);
				}

				// angle is not definitely necessary for these modes, but it makes
				// controlling more fun for the user
				aFullVec.normalize();
				double fNewFullAngle(atan2(aFullVec.getY(), aFullVec.getX()));
				fNewFullAngle /= F_PI180;
				fNewFullAngle *= -10.0;
				fNewFullAngle += 900.0;

				// clip
				while(fNewFullAngle < 0.0)
				{
					fNewFullAngle += 3600.0;
				}

				while(fNewFullAngle >= 3600.0)
				{
					fNewFullAngle -= 3600.0;
				}

				// to int and set
				const sal_Int32 nNewAngle(FRound(fNewFullAngle));

				if(nNewAngle != rGOld.aGradient.GetAngle())
				{
					rG.aGradient.SetAngle(nNewAngle);
				}
			}

			break;
		}
	}
}

// eof
