/**************************************************************
 * 
 * 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_sc.hxx"

// INCLUDE ---------------------------------------------------------------

#include "scitems.hxx"
#include <svtools/colorcfg.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/outlobj.hxx>
#include <svx/sdshitm.hxx>
#include <svx/sdsxyitm.hxx>
#include <svx/sdtditm.hxx>
#include <svx/svditer.hxx>
#include <svx/svdocapt.hxx>
#include <svx/svdocirc.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdorect.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdundo.hxx>
#include <svx/xfillit0.hxx>
#include <svx/xflclit.hxx>
#include <svx/xlnclit.hxx>
#include <svx/xlnedcit.hxx>
#include <svx/xlnedit.hxx>
#include <svx/xlnedwit.hxx>
#include <svx/xlnstcit.hxx>
#include <svx/xlnstit.hxx>
#include <svx/xlnstwit.hxx>
#include <svx/xlnwtit.hxx>
#include <svx/xtable.hxx>
#include <editeng/outliner.hxx>
#include <editeng/editobj.hxx>
#include <svx/sxcecitm.hxx>
#include <svl/whiter.hxx>
#include <editeng/writingmodeitem.hxx>

#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>

#include "detfunc.hxx"
#include "document.hxx"
#include "dociter.hxx"
#include "drwlayer.hxx"
#include "userdat.hxx"
#include "validat.hxx"
#include "cell.hxx"
#include "docpool.hxx"
#include "patattr.hxx"
#include "attrib.hxx"
#include "scmod.hxx"
#include "postit.hxx"

//------------------------------------------------------------------------

// #99319# line ends are now created with an empty name.
// The checkForUniqueItem method then finds a unique name for the item's value.
#define SC_LINEEND_NAME		EMPTY_STRING

//------------------------------------------------------------------------

enum DetInsertResult {				// Return-Werte beim Einfuegen in einen Level
			DET_INS_CONTINUE,
			DET_INS_INSERTED,
			DET_INS_EMPTY,
			DET_INS_CIRCULAR };


//------------------------------------------------------------------------

class ScDetectiveData
{
private:
	SfxItemSet	aBoxSet;
	SfxItemSet	aArrowSet;
	SfxItemSet	aToTabSet;
	SfxItemSet	aFromTabSet;
	SfxItemSet	aCircleSet;			//! einzeln ?
	sal_uInt16		nMaxLevel;

public:
				ScDetectiveData( SdrModel* pModel );

	SfxItemSet&	GetBoxSet()		{ return aBoxSet; }
	SfxItemSet&	GetArrowSet()	{ return aArrowSet; }
	SfxItemSet&	GetToTabSet()	{ return aToTabSet; }
	SfxItemSet&	GetFromTabSet()	{ return aFromTabSet; }
	SfxItemSet&	GetCircleSet()	{ return aCircleSet; }

	void		SetMaxLevel( sal_uInt16 nVal )		{ nMaxLevel = nVal; }
	sal_uInt16		GetMaxLevel() const				{ return nMaxLevel; }
};

class ScCommentData
{
public:
				        ScCommentData( ScDocument& rDoc, SdrModel* pModel );

	SfxItemSet&	        GetCaptionSet()	{ return aCaptionSet; }
	void	            UpdateCaptionSet( const SfxItemSet& rItemSet );

private:
	SfxItemSet	        aCaptionSet;
};

//------------------------------------------------------------------------

ColorData ScDetectiveFunc::nArrowColor = 0;
ColorData ScDetectiveFunc::nErrorColor = 0;
ColorData ScDetectiveFunc::nCommentColor = 0;
sal_Bool ScDetectiveFunc::bColorsInitialized = sal_False;

//------------------------------------------------------------------------

sal_Bool lcl_HasThickLine( SdrObject& rObj )
{
	// thin lines get width 0 -> everything greater 0 is a thick line

	return ( ((const XLineWidthItem&)rObj.GetMergedItem(XATTR_LINEWIDTH)).GetValue() > 0 );
}

//------------------------------------------------------------------------

ScDetectiveData::ScDetectiveData( SdrModel* pModel ) :
	aBoxSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END ),
	aArrowSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END ),
	aToTabSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END ),
	aFromTabSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END ),
	aCircleSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END )
{
	nMaxLevel = 0;

	aBoxSet.Put( XLineColorItem( EMPTY_STRING, Color( ScDetectiveFunc::GetArrowColor() ) ) );
	aBoxSet.Put( XFillStyleItem( XFILL_NONE ) );

	//	#66479# Standard-Linienenden (wie aus XLineEndList::Create) selber zusammenbasteln,
	//	um von den konfigurierten Linienenden unabhaengig zu sein

	basegfx::B2DPolygon aTriangle;
	aTriangle.append(basegfx::B2DPoint(10.0, 0.0));
	aTriangle.append(basegfx::B2DPoint(0.0, 30.0));
	aTriangle.append(basegfx::B2DPoint(20.0, 30.0));
	aTriangle.setClosed(true);

	basegfx::B2DPolygon aSquare;
	aSquare.append(basegfx::B2DPoint(0.0, 0.0));
	aSquare.append(basegfx::B2DPoint(10.0, 0.0));
	aSquare.append(basegfx::B2DPoint(10.0, 10.0));
	aSquare.append(basegfx::B2DPoint(0.0, 10.0));
	aSquare.setClosed(true);

	basegfx::B2DPolygon aCircle(basegfx::tools::createPolygonFromEllipse(basegfx::B2DPoint(0.0, 0.0), 100.0, 100.0));
	aCircle.setClosed(true);

	String aName = SC_LINEEND_NAME;

	aArrowSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aCircle) ) );
	aArrowSet.Put( XLineStartWidthItem( 200 ) );
	aArrowSet.Put( XLineStartCenterItem( sal_True ) );
	aArrowSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) );
	aArrowSet.Put( XLineEndWidthItem( 200 ) );
	aArrowSet.Put( XLineEndCenterItem( sal_False ) );

	aToTabSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aCircle) ) );
	aToTabSet.Put( XLineStartWidthItem( 200 ) );
	aToTabSet.Put( XLineStartCenterItem( sal_True ) );
	aToTabSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aSquare) ) );
	aToTabSet.Put( XLineEndWidthItem( 300 ) );
	aToTabSet.Put( XLineEndCenterItem( sal_False ) );

	aFromTabSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aSquare) ) );
	aFromTabSet.Put( XLineStartWidthItem( 300 ) );
	aFromTabSet.Put( XLineStartCenterItem( sal_True ) );
	aFromTabSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) );
	aFromTabSet.Put( XLineEndWidthItem( 200 ) );
	aFromTabSet.Put( XLineEndCenterItem( sal_False ) );

	aCircleSet.Put( XLineColorItem( String(), Color( ScDetectiveFunc::GetErrorColor() ) ) );
	aCircleSet.Put( XFillStyleItem( XFILL_NONE ) );
	sal_uInt16 nWidth = 55;		// 54 = 1 Pixel
	aCircleSet.Put( XLineWidthItem( nWidth ) );
}

ScCommentData::ScCommentData( ScDocument& rDoc, SdrModel* pModel ) :
	aCaptionSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END, EE_ITEMS_START, EE_ITEMS_END, 0, 0 )
{
	basegfx::B2DPolygon aTriangle;
	aTriangle.append(basegfx::B2DPoint(10.0, 0.0));
	aTriangle.append(basegfx::B2DPoint(0.0, 30.0));
	aTriangle.append(basegfx::B2DPoint(20.0, 30.0));
	aTriangle.setClosed(true);

	String aName = SC_LINEEND_NAME;

	aCaptionSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) );
	aCaptionSet.Put( XLineStartWidthItem( 200 ) );
	aCaptionSet.Put( XLineStartCenterItem( sal_False ) );
	aCaptionSet.Put( XFillStyleItem( XFILL_SOLID ) );
	Color aYellow( ScDetectiveFunc::GetCommentColor() );
	aCaptionSet.Put( XFillColorItem( String(), aYellow ) );

	//	shadow
	//	SdrShadowItem has sal_False, instead the shadow is set for the rectangle
	//	only with SetSpecialTextBoxShadow when the object is created
	//	(item must be set to adjust objects from older files)
	aCaptionSet.Put( SdrShadowItem( sal_False ) );
	aCaptionSet.Put( SdrShadowXDistItem( 100 ) );
	aCaptionSet.Put( SdrShadowYDistItem( 100 ) );

	//	text attributes
	aCaptionSet.Put( SdrTextLeftDistItem( 100 ) );
	aCaptionSet.Put( SdrTextRightDistItem( 100 ) );
	aCaptionSet.Put( SdrTextUpperDistItem( 100 ) );
	aCaptionSet.Put( SdrTextLowerDistItem( 100 ) );

    aCaptionSet.Put( SdrTextAutoGrowWidthItem( sal_False ) );
    aCaptionSet.Put( SdrTextAutoGrowHeightItem( sal_True ) );

	//	#78943# do use the default cell style, so the user has a chance to
	//	modify the font for the annotations
	((const ScPatternAttr&)rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN)).
		FillEditItemSet( &aCaptionSet );

    // support the best position for the tail connector now that
    // that notes can be resized and repositioned.
    aCaptionSet.Put( SdrCaptionEscDirItem( SDRCAPT_ESCBESTFIT) );
}

void ScCommentData::UpdateCaptionSet( const SfxItemSet& rItemSet )
{
    SfxWhichIter aWhichIter( rItemSet );
    const SfxPoolItem* pPoolItem = 0;

    for( sal_uInt16 nWhich = aWhichIter.FirstWhich(); nWhich > 0; nWhich = aWhichIter.NextWhich() )
    {
        if(rItemSet.GetItemState(nWhich, sal_False, &pPoolItem) == SFX_ITEM_SET)
        {
            switch(nWhich)
            {
                case SDRATTR_SHADOW:
                    // use existing Caption default - appears that setting this
                    // to true screws up the tail appearance. See also comment
                    // for default setting above.
                break;
    	        case SDRATTR_SHADOWXDIST:
	                // use existing Caption default - svx sets a value of 35
                    // but default 100 gives a better appearance.
                break;
                case SDRATTR_SHADOWYDIST:
                    // use existing Caption default - svx sets a value of 35
                    // but default 100 gives a better appearance.
                break;

                default:
                    aCaptionSet.Put(*pPoolItem);
           }
        }
    }
}

//------------------------------------------------------------------------

void ScDetectiveFunc::Modified()
{
    if (pDoc->IsStreamValid(nTab))
        pDoc->SetStreamValid(nTab, sal_False);
}

inline sal_Bool Intersect( SCCOL nStartCol1, SCROW nStartRow1, SCCOL nEndCol1, SCROW nEndRow1,
						SCCOL nStartCol2, SCROW nStartRow2, SCCOL nEndCol2, SCROW nEndRow2 )
{
	return nEndCol1 >= nStartCol2 && nEndCol2 >= nStartCol1 &&
			nEndRow1 >= nStartRow2 && nEndRow2 >= nStartRow1;
}

sal_Bool ScDetectiveFunc::HasError( const ScRange& rRange, ScAddress& rErrPos )
{
	rErrPos = rRange.aStart;
	sal_uInt16 nError = 0;

	ScCellIterator aCellIter( pDoc, rRange);
	ScBaseCell* pCell = aCellIter.GetFirst();
	while (pCell)
	{
		if (pCell->GetCellType() == CELLTYPE_FORMULA)
		{
			nError = ((ScFormulaCell*)pCell)->GetErrCode();
			if (nError)
				rErrPos.Set( aCellIter.GetCol(), aCellIter.GetRow(), aCellIter.GetTab() );
		}
		pCell = aCellIter.GetNext();
	}

	return (nError != 0);
}

Point ScDetectiveFunc::GetDrawPos( SCCOL nCol, SCROW nRow, DrawPosMode eMode ) const
{
    DBG_ASSERT( ValidColRow( nCol, nRow ), "ScDetectiveFunc::GetDrawPos - invalid cell address" );
    SanitizeCol( nCol );
    SanitizeRow( nRow );

    Point aPos;

    switch( eMode )
    {
        case DRAWPOS_TOPLEFT:
        break;
        case DRAWPOS_BOTTOMRIGHT:
            ++nCol;
            ++nRow;
        break;
        case DRAWPOS_DETARROW:
			aPos.X() += pDoc->GetColWidth( nCol, nTab ) / 4;
			aPos.Y() += pDoc->GetRowHeight( nRow, nTab ) / 2;
        break;
        case DRAWPOS_CAPTIONLEFT:
            aPos.X() += 6;
        break;
        case DRAWPOS_CAPTIONRIGHT:
        {
            // find right end of passed cell position
            const ScMergeAttr* pMerge = static_cast< const ScMergeAttr* >( pDoc->GetAttr( nCol, nRow, nTab, ATTR_MERGE ) );
            if ( pMerge->GetColMerge() > 1 )
                nCol = nCol + pMerge->GetColMerge();
            else
                ++nCol;
            aPos.X() -= 6;
        }
        break;
    }

	for ( SCCOL i = 0; i < nCol; ++i )
		aPos.X() += pDoc->GetColWidth( i, nTab );
    aPos.Y() += pDoc->GetRowHeight( 0, nRow - 1, nTab );

	aPos.X() = static_cast< long >( aPos.X() * HMM_PER_TWIPS );
	aPos.Y() = static_cast< long >( aPos.Y() * HMM_PER_TWIPS );

	if ( pDoc->IsNegativePage( nTab ) )
		aPos.X() *= -1;

	return aPos;
}

Rectangle ScDetectiveFunc::GetDrawRect( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const
{
    Rectangle aRect(
        GetDrawPos( ::std::min( nCol1, nCol2 ), ::std::min( nRow1, nRow2 ), DRAWPOS_TOPLEFT ),
        GetDrawPos( ::std::max( nCol1, nCol2 ), ::std::max( nRow1, nRow2 ), DRAWPOS_BOTTOMRIGHT ) );
    aRect.Justify();    // reorder left/right in RTL sheets
    return aRect;
}

Rectangle ScDetectiveFunc::GetDrawRect( SCCOL nCol, SCROW nRow ) const
{
    return GetDrawRect( nCol, nRow, nCol, nRow );
}

sal_Bool lcl_IsOtherTab( const basegfx::B2DPolyPolygon& rPolyPolygon )
{
	//	test if rPolygon is the line end for "other table" (rectangle)
	if(1L == rPolyPolygon.count())
	{
		const basegfx::B2DPolygon aSubPoly(rPolyPolygon.getB2DPolygon(0L));

        // #i73305# circle consists of 4 segments, too, distinguishable from square by
        // the use of control points
        if(4L == aSubPoly.count() && aSubPoly.isClosed() && !aSubPoly.areControlPointsUsed())
		{
			return true;
		}
	}

	return false;
}

sal_Bool ScDetectiveFunc::HasArrow( const ScAddress& rStart,
									SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab )
{
	sal_Bool bStartAlien = ( rStart.Tab() != nTab );
	sal_Bool bEndAlien   = ( nEndTab != nTab );

	if (bStartAlien && bEndAlien)
	{
		DBG_ERROR("bStartAlien && bEndAlien");
		return sal_True;
	}

	Rectangle aStartRect;
	Rectangle aEndRect;
	if (!bStartAlien)
		aStartRect = GetDrawRect( rStart.Col(), rStart.Row() );
	if (!bEndAlien)
		aEndRect = GetDrawRect( nEndCol, nEndRow );

	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
	DBG_ASSERT(pPage,"Page ?");

	sal_Bool bFound = sal_False;
	SdrObjListIter aIter( *pPage, IM_FLAT );
	SdrObject* pObject = aIter.Next();
	while (pObject && !bFound)
	{
		if ( pObject->GetLayer()==SC_LAYER_INTERN &&
				pObject->IsPolyObj() && pObject->GetPointCount()==2 )
		{
			const SfxItemSet& rSet = pObject->GetMergedItemSet();

			sal_Bool bObjStartAlien =
				lcl_IsOtherTab( ((const XLineStartItem&)rSet.Get(XATTR_LINESTART)).GetLineStartValue() );
			sal_Bool bObjEndAlien =
				lcl_IsOtherTab( ((const XLineEndItem&)rSet.Get(XATTR_LINEEND)).GetLineEndValue() );

			sal_Bool bStartHit = bStartAlien ? bObjStartAlien :
								( !bObjStartAlien && aStartRect.IsInside(pObject->GetPoint(0)) );
			sal_Bool bEndHit = bEndAlien ? bObjEndAlien :
								( !bObjEndAlien && aEndRect.IsInside(pObject->GetPoint(1)) );

			if ( bStartHit && bEndHit )
				bFound = sal_True;
		}
		pObject = aIter.Next();
	}

	return bFound;
}

sal_Bool ScDetectiveFunc::IsNonAlienArrow( SdrObject* pObject )			// static
{
	if ( pObject->GetLayer()==SC_LAYER_INTERN &&
			pObject->IsPolyObj() && pObject->GetPointCount()==2 )
	{
		const SfxItemSet& rSet = pObject->GetMergedItemSet();

		sal_Bool bObjStartAlien =
			lcl_IsOtherTab( ((const XLineStartItem&)rSet.Get(XATTR_LINESTART)).GetLineStartValue() );
		sal_Bool bObjEndAlien =
			lcl_IsOtherTab( ((const XLineEndItem&)rSet.Get(XATTR_LINEEND)).GetLineEndValue() );

		return !bObjStartAlien && !bObjEndAlien;
	}

	return sal_False;
}

//------------------------------------------------------------------------

//	InsertXXX: called from DrawEntry/DrawAlienEntry and InsertObject

sal_Bool ScDetectiveFunc::InsertArrow( SCCOL nCol, SCROW nRow,
								SCCOL nRefStartCol, SCROW nRefStartRow,
								SCCOL nRefEndCol, SCROW nRefEndRow,
								sal_Bool bFromOtherTab, sal_Bool bRed,
								ScDetectiveData& rData )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));

	sal_Bool bArea = ( nRefStartCol != nRefEndCol || nRefStartRow != nRefEndRow );
	if (bArea && !bFromOtherTab)
	{
		// insert the rectangle before the arrow - this is relied on in FindFrameForObject

		Rectangle aRect = GetDrawRect( nRefStartCol, nRefStartRow, nRefEndCol, nRefEndRow );
		SdrRectObj* pBox = new SdrRectObj( aRect );

		pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet());

		ScDrawLayer::SetAnchor( pBox, SCA_CELL );
		pBox->SetLayer( SC_LAYER_INTERN );
		pPage->InsertObject( pBox );
	        pModel->AddCalcUndo< SdrUndoInsertObj >( *pBox );

		ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox, sal_True );
		pData->maStart.Set( nRefStartCol, nRefStartRow, nTab);
		pData->maEnd.Set( nRefEndCol, nRefEndRow, nTab);
	}

	Point aStartPos	= GetDrawPos( nRefStartCol, nRefStartRow, DRAWPOS_DETARROW );
	Point aEndPos = GetDrawPos( nCol, nRow, DRAWPOS_DETARROW );

	if (bFromOtherTab)
	{
		sal_Bool bNegativePage = pDoc->IsNegativePage( nTab );
		long nPageSign = bNegativePage ? -1 : 1;

		aStartPos = Point( aEndPos.X() - 1000 * nPageSign, aEndPos.Y() - 1000 );
		if (aStartPos.X() * nPageSign < 0)
			aStartPos.X() += 2000 * nPageSign;
		if (aStartPos.Y() < 0)
			aStartPos.Y() += 2000;
	}

	SfxItemSet& rAttrSet = bFromOtherTab ? rData.GetFromTabSet() : rData.GetArrowSet();

	if (bArea && !bFromOtherTab)
		rAttrSet.Put( XLineWidthItem( 50 ) );				// Bereich
	else
		rAttrSet.Put( XLineWidthItem( 0 ) );				// einzelne Referenz

	ColorData nColorData = ( bRed ? GetErrorColor() : GetArrowColor() );
	rAttrSet.Put( XLineColorItem( String(), Color( nColorData ) ) );

	basegfx::B2DPolygon aTempPoly;
	aTempPoly.append(basegfx::B2DPoint(aStartPos.X(), aStartPos.Y()));
	aTempPoly.append(basegfx::B2DPoint(aEndPos.X(), aEndPos.Y()));
	SdrPathObj* pArrow = new SdrPathObj(OBJ_LINE, basegfx::B2DPolyPolygon(aTempPoly));
	pArrow->NbcSetLogicRect(Rectangle(aStartPos,aEndPos));	//! noetig ???
	pArrow->SetMergedItemSetAndBroadcast(rAttrSet);

	ScDrawLayer::SetAnchor( pArrow, SCA_CELL );
	pArrow->SetLayer( SC_LAYER_INTERN );
	pPage->InsertObject( pArrow );
	    pModel->AddCalcUndo< SdrUndoInsertObj >( *pArrow );

	ScDrawObjData* pData = ScDrawLayer::GetObjData( pArrow, sal_True );
	if (bFromOtherTab)
		pData->maStart.SetInvalid();
	else
		pData->maStart.Set( nRefStartCol, nRefStartRow, nTab);

	pData->maEnd.Set( nCol, nRow, nTab);

    Modified();
	return sal_True;
}

sal_Bool ScDetectiveFunc::InsertToOtherTab( SCCOL nStartCol, SCROW nStartRow,
								SCCOL nEndCol, SCROW nEndRow, sal_Bool bRed,
								ScDetectiveData& rData )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));

	sal_Bool bArea = ( nStartCol != nEndCol || nStartRow != nEndRow );
	if (bArea)
	{
		Rectangle aRect = GetDrawRect( nStartCol, nStartRow, nEndCol, nEndRow );
		SdrRectObj* pBox = new SdrRectObj( aRect );

		pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet());

		ScDrawLayer::SetAnchor( pBox, SCA_CELL );
		pBox->SetLayer( SC_LAYER_INTERN );
		pPage->InsertObject( pBox );
	        pModel->AddCalcUndo< SdrUndoInsertObj >( *pBox );

		ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox, sal_True );
		pData->maStart.Set( nStartCol, nStartRow, nTab);
		pData->maEnd.Set( nEndCol, nEndRow, nTab);
	}

	sal_Bool bNegativePage = pDoc->IsNegativePage( nTab );
	long nPageSign = bNegativePage ? -1 : 1;

	Point aStartPos	= GetDrawPos( nStartCol, nStartRow, DRAWPOS_DETARROW );
	Point aEndPos   = Point( aStartPos.X() + 1000 * nPageSign, aStartPos.Y() - 1000 );
	if (aEndPos.Y() < 0)
		aEndPos.Y() += 2000;

	SfxItemSet& rAttrSet = rData.GetToTabSet();
	if (bArea)
		rAttrSet.Put( XLineWidthItem( 50 ) );				// Bereich
	else
		rAttrSet.Put( XLineWidthItem( 0 ) );				// einzelne Referenz

	ColorData nColorData = ( bRed ? GetErrorColor() : GetArrowColor() );
	rAttrSet.Put( XLineColorItem( String(), Color( nColorData ) ) );

	basegfx::B2DPolygon aTempPoly;
	aTempPoly.append(basegfx::B2DPoint(aStartPos.X(), aStartPos.Y()));
	aTempPoly.append(basegfx::B2DPoint(aEndPos.X(), aEndPos.Y()));
	SdrPathObj* pArrow = new SdrPathObj(OBJ_LINE, basegfx::B2DPolyPolygon(aTempPoly));
	pArrow->NbcSetLogicRect(Rectangle(aStartPos,aEndPos));	//! noetig ???

	pArrow->SetMergedItemSetAndBroadcast(rAttrSet);

	ScDrawLayer::SetAnchor( pArrow, SCA_CELL );
	pArrow->SetLayer( SC_LAYER_INTERN );
	pPage->InsertObject( pArrow );
	    pModel->AddCalcUndo< SdrUndoInsertObj >( *pArrow );

	ScDrawObjData* pData = ScDrawLayer::GetObjData( pArrow, sal_True );
	pData->maStart.Set( nStartCol, nStartRow, nTab);
	pData->maEnd.SetInvalid();

    Modified();
	return sal_True;
}

//------------------------------------------------------------------------

//	DrawEntry:		Formel auf dieser Tabelle,
//					Referenz auf dieser oder anderer
//	DrawAlienEntry:	Formel auf anderer Tabelle,
//					Referenz auf dieser

//		return FALSE: da war schon ein Pfeil

sal_Bool ScDetectiveFunc::DrawEntry( SCCOL nCol, SCROW nRow,
									const ScRange& rRef,
									ScDetectiveData& rData )
{
	if ( HasArrow( rRef.aStart, nCol, nRow, nTab ) )
		return sal_False;

	ScAddress aErrorPos;
	sal_Bool bError = HasError( rRef, aErrorPos );
	sal_Bool bAlien = ( rRef.aEnd.Tab() < nTab || rRef.aStart.Tab() > nTab );

	return InsertArrow( nCol, nRow,
						rRef.aStart.Col(), rRef.aStart.Row(),
						rRef.aEnd.Col(), rRef.aEnd.Row(),
						bAlien, bError, rData );
}

sal_Bool ScDetectiveFunc::DrawAlienEntry( const ScRange& rRef,
										ScDetectiveData& rData )
{
	if ( HasArrow( rRef.aStart, 0, 0, nTab+1 ) )
		return sal_False;

	ScAddress aErrorPos;
	sal_Bool bError = HasError( rRef, aErrorPos );

	return InsertToOtherTab( rRef.aStart.Col(), rRef.aStart.Row(),
								rRef.aEnd.Col(), rRef.aEnd.Row(),
								bError, rData );
}

void ScDetectiveFunc::DrawCircle( SCCOL nCol, SCROW nRow, ScDetectiveData& rData )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));

	Rectangle aRect = GetDrawRect( nCol, nRow );
	aRect.Left()	-= 250;
	aRect.Right()	+= 250;
	aRect.Top()		-= 70;
	aRect.Bottom()	+= 70;

	SdrCircObj* pCircle = new SdrCircObj( OBJ_CIRC, aRect );
	SfxItemSet& rAttrSet = rData.GetCircleSet();

	pCircle->SetMergedItemSetAndBroadcast(rAttrSet);

	ScDrawLayer::SetAnchor( pCircle, SCA_CELL );
	pCircle->SetLayer( SC_LAYER_INTERN );
	pPage->InsertObject( pCircle );
	    pModel->AddCalcUndo< SdrUndoInsertObj >( *pCircle );

	ScDrawObjData* pData = ScDrawLayer::GetObjData( pCircle, sal_True );
	pData->maStart.Set( nCol, nRow, nTab);
	pData->maEnd.SetInvalid();

    Modified();
}

void ScDetectiveFunc::DeleteArrowsAt( SCCOL nCol, SCROW nRow, sal_Bool bDestPnt )
{
	Rectangle aRect = GetDrawRect( nCol, nRow );

	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
	DBG_ASSERT(pPage,"Page ?");

	pPage->RecalcObjOrdNums();

	long	nDelCount = 0;
	sal_uLong	nObjCount = pPage->GetObjCount();
	if (nObjCount)
	{
		SdrObject** ppObj = new SdrObject*[nObjCount];

		SdrObjListIter aIter( *pPage, IM_FLAT );
		SdrObject* pObject = aIter.Next();
		while (pObject)
		{
			if ( pObject->GetLayer()==SC_LAYER_INTERN &&
					pObject->IsPolyObj() && pObject->GetPointCount()==2 )
			{
				if (aRect.IsInside(pObject->GetPoint(bDestPnt)))			// Start/Zielpunkt
					ppObj[nDelCount++] = pObject;
			}

			pObject = aIter.Next();
		}

		long i;
		for (i=1; i<=nDelCount; i++)
            pModel->AddCalcUndo< SdrUndoRemoveObj >( *ppObj[nDelCount-i] );

		for (i=1; i<=nDelCount; i++)
			pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() );

		delete[] ppObj;

        Modified();
	}
}

		//		Box um Referenz loeschen

#define SC_DET_TOLERANCE	50

inline sal_Bool RectIsPoints( const Rectangle& rRect, const Point& rStart, const Point& rEnd )
{
	return rRect.Left()   >= rStart.X() - SC_DET_TOLERANCE
		&& rRect.Left()   <= rStart.X() + SC_DET_TOLERANCE
		&& rRect.Right()  >= rEnd.X()   - SC_DET_TOLERANCE
		&& rRect.Right()  <= rEnd.X()   + SC_DET_TOLERANCE
		&& rRect.Top()    >= rStart.Y() - SC_DET_TOLERANCE
		&& rRect.Top()    <= rStart.Y() + SC_DET_TOLERANCE
		&& rRect.Bottom() >= rEnd.Y()   - SC_DET_TOLERANCE
		&& rRect.Bottom() <= rEnd.Y()   + SC_DET_TOLERANCE;
}

#undef SC_DET_TOLERANCE

void ScDetectiveFunc::DeleteBox( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{
/*	String aStr;
	aStr += nCol1;
	aStr += '/';
	aStr += nRow1;
	aStr += '/';
	aStr += nCol2;
	aStr += '/';
	aStr += nRow2;
	InfoBox(0,aStr).Execute();
*/

	Rectangle aCornerRect = GetDrawRect( nCol1, nRow1, nCol2, nRow2 );
	Point aStartCorner = aCornerRect.TopLeft();
	Point aEndCorner = aCornerRect.BottomRight();
	Rectangle aObjRect;

	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
	DBG_ASSERT(pPage,"Page ?");

	pPage->RecalcObjOrdNums();

	long	nDelCount = 0;
	sal_uLong	nObjCount = pPage->GetObjCount();
	if (nObjCount)
	{
		SdrObject** ppObj = new SdrObject*[nObjCount];

		SdrObjListIter aIter( *pPage, IM_FLAT );
		SdrObject* pObject = aIter.Next();
		while (pObject)
		{
			if ( pObject->GetLayer() == SC_LAYER_INTERN &&
					pObject->Type() == TYPE(SdrRectObj) )
			{
				aObjRect = ((SdrRectObj*)pObject)->GetLogicRect();
				aObjRect.Justify();
				if ( RectIsPoints( aObjRect, aStartCorner, aEndCorner ) )
					ppObj[nDelCount++] = pObject;
			}

			pObject = aIter.Next();
		}

		long i;
		for (i=1; i<=nDelCount; i++)
	            pModel->AddCalcUndo< SdrUndoRemoveObj >( *ppObj[nDelCount-i] );

		for (i=1; i<=nDelCount; i++)
			pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() );

		delete[] ppObj;

        Modified();
	}
}

//------------------------------------------------------------------------

sal_uInt16 ScDetectiveFunc::InsertPredLevelArea( const ScRange& rRef,
										ScDetectiveData& rData, sal_uInt16 nLevel )
{
	sal_uInt16 nResult = DET_INS_EMPTY;

	ScCellIterator aCellIter( pDoc, rRef);
	ScBaseCell* pCell = aCellIter.GetFirst();
	while (pCell)
	{
		if (pCell->GetCellType() == CELLTYPE_FORMULA)
			switch( InsertPredLevel( aCellIter.GetCol(), aCellIter.GetRow(), rData, nLevel ) )
			{
				case DET_INS_INSERTED:
					nResult = DET_INS_INSERTED;
					break;
				case DET_INS_CONTINUE:
					if (nResult != DET_INS_INSERTED)
						nResult = DET_INS_CONTINUE;
					break;
				case DET_INS_CIRCULAR:
					if (nResult == DET_INS_EMPTY)
						nResult = DET_INS_CIRCULAR;
					break;
			}

		pCell = aCellIter.GetNext();
	}

	return nResult;
}

sal_uInt16 ScDetectiveFunc::InsertPredLevel( SCCOL nCol, SCROW nRow, ScDetectiveData& rData,
											sal_uInt16 nLevel )
{
	ScBaseCell* pCell;
	pDoc->GetCell( nCol, nRow, nTab, pCell );
	if (!pCell)
		return DET_INS_EMPTY;
	if (pCell->GetCellType() != CELLTYPE_FORMULA)
		return DET_INS_EMPTY;

	ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
	if (pFCell->IsRunning())
		return DET_INS_CIRCULAR;

	if (pFCell->GetDirty())
		pFCell->Interpret();				// nach SetRunning geht's nicht mehr!
	pFCell->SetRunning(sal_True);

	sal_uInt16 nResult = DET_INS_EMPTY;

	ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
    ScRange aRef;
	while ( aIter.GetNextRef( aRef ) )
	{
		if (DrawEntry( nCol, nRow, aRef, rData ))
		{
			nResult = DET_INS_INSERTED;			//	neuer Pfeil eingetragen
		}
		else
		{
			//	weiterverfolgen

			if ( nLevel < rData.GetMaxLevel() )
			{
				sal_uInt16 nSubResult;
				sal_Bool bArea = (aRef.aStart != aRef.aEnd);
				if (bArea)
					nSubResult = InsertPredLevelArea( aRef, rData, nLevel+1 );
				else
					nSubResult = InsertPredLevel( aRef.aStart.Col(), aRef.aStart.Row(),
													rData, nLevel+1 );

				switch (nSubResult)
				{
					case DET_INS_INSERTED:
						nResult = DET_INS_INSERTED;
						break;
					case DET_INS_CONTINUE:
						if (nResult != DET_INS_INSERTED)
							nResult = DET_INS_CONTINUE;
						break;
					case DET_INS_CIRCULAR:
						if (nResult == DET_INS_EMPTY)
							nResult = DET_INS_CIRCULAR;
						break;
					// DET_INS_EMPTY: unveraendert lassen
				}
			}
			else									//	nMaxLevel erreicht
				if (nResult != DET_INS_INSERTED)
					nResult = DET_INS_CONTINUE;
		}
	}

	pFCell->SetRunning(sal_False);

	return nResult;
}

sal_uInt16 ScDetectiveFunc::FindPredLevelArea( const ScRange& rRef,
												sal_uInt16 nLevel, sal_uInt16 nDeleteLevel )
{
	sal_uInt16 nResult = nLevel;

	ScCellIterator aCellIter( pDoc, rRef);
	ScBaseCell* pCell = aCellIter.GetFirst();
	while (pCell)
	{
		if (pCell->GetCellType() == CELLTYPE_FORMULA)
		{
			sal_uInt16 nTemp = FindPredLevel( aCellIter.GetCol(), aCellIter.GetRow(), nLevel, nDeleteLevel );
			if (nTemp > nResult)
				nResult = nTemp;
		}
		pCell = aCellIter.GetNext();
	}

	return nResult;
}

											//	nDeleteLevel != 0	-> loeschen

sal_uInt16 ScDetectiveFunc::FindPredLevel( SCCOL nCol, SCROW nRow, sal_uInt16 nLevel, sal_uInt16 nDeleteLevel )
{
	DBG_ASSERT( nLevel<1000, "Level" );

	ScBaseCell* pCell;
	pDoc->GetCell( nCol, nRow, nTab, pCell );
	if (!pCell)
		return nLevel;
	if (pCell->GetCellType() != CELLTYPE_FORMULA)
		return nLevel;

	ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
	if (pFCell->IsRunning())
		return nLevel;

	if (pFCell->GetDirty())
		pFCell->Interpret();				// nach SetRunning geht's nicht mehr!
	pFCell->SetRunning(sal_True);

	sal_uInt16 nResult = nLevel;
	sal_Bool bDelete = ( nDeleteLevel && nLevel == nDeleteLevel-1 );

	if ( bDelete )
	{
		DeleteArrowsAt( nCol, nRow, sal_True );					// Pfeile, die hierher zeigen
	}

	ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
    ScRange aRef;
	while ( aIter.GetNextRef( aRef) )
	{
		sal_Bool bArea = ( aRef.aStart != aRef.aEnd );

		if ( bDelete )					// Rahmen loeschen ?
		{
			if (bArea)
			{
				DeleteBox( aRef.aStart.Col(), aRef.aStart.Row(), aRef.aEnd.Col(), aRef.aEnd.Row() );
			}
		}
		else							// weitersuchen
		{
			if ( HasArrow( aRef.aStart, nCol,nRow,nTab ) )
			{
				sal_uInt16 nTemp;
				if (bArea)
					nTemp = FindPredLevelArea( aRef, nLevel+1, nDeleteLevel );
				else
					nTemp = FindPredLevel( aRef.aStart.Col(),aRef.aStart.Row(),
														nLevel+1, nDeleteLevel );
				if (nTemp > nResult)
					nResult = nTemp;
			}
		}
	}

	pFCell->SetRunning(sal_False);

	return nResult;
}

//------------------------------------------------------------------------

sal_uInt16 ScDetectiveFunc::InsertErrorLevel( SCCOL nCol, SCROW nRow, ScDetectiveData& rData,
											sal_uInt16 nLevel )
{
	ScBaseCell* pCell;
	pDoc->GetCell( nCol, nRow, nTab, pCell );
	if (!pCell)
		return DET_INS_EMPTY;
	if (pCell->GetCellType() != CELLTYPE_FORMULA)
		return DET_INS_EMPTY;

	ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
	if (pFCell->IsRunning())
		return DET_INS_CIRCULAR;

	if (pFCell->GetDirty())
		pFCell->Interpret();				// nach SetRunning geht's nicht mehr!
	pFCell->SetRunning(sal_True);

	sal_uInt16 nResult = DET_INS_EMPTY;

	ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
    ScRange aRef;
	ScAddress aErrorPos;
	sal_Bool bHasError = sal_False;
	while ( aIter.GetNextRef( aRef ) )
	{
		if (HasError( aRef, aErrorPos ))
		{
			bHasError = sal_True;
			if (DrawEntry( nCol, nRow, ScRange( aErrorPos), rData ))
				nResult = DET_INS_INSERTED;

			//	und weiterverfolgen

			if ( nLevel < rData.GetMaxLevel() )			// praktisch immer
			{
				if (InsertErrorLevel( aErrorPos.Col(), aErrorPos.Row(),
														rData, nLevel+1 ) == DET_INS_INSERTED)
					nResult = DET_INS_INSERTED;
			}
		}
	}

	pFCell->SetRunning(sal_False);

													// Blaetter ?
	if (!bHasError)
		if (InsertPredLevel( nCol, nRow, rData, rData.GetMaxLevel() ) == DET_INS_INSERTED)
			nResult = DET_INS_INSERTED;

	return nResult;
}

//------------------------------------------------------------------------

sal_uInt16 ScDetectiveFunc::InsertSuccLevel( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
										ScDetectiveData& rData, sal_uInt16 nLevel )
{
	//	ueber ganzes Dokument

	sal_uInt16 nResult = DET_INS_EMPTY;
//	ScCellIterator aCellIter( pDoc, 0,0, nTab, MAXCOL,MAXROW, nTab );
	ScCellIterator aCellIter( pDoc, 0,0,0, MAXCOL,MAXROW,MAXTAB );			// alle Tabellen
	ScBaseCell* pCell = aCellIter.GetFirst();
	while (pCell)
	{
		if (pCell->GetCellType() == CELLTYPE_FORMULA)
		{
			ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
			sal_Bool bRunning = pFCell->IsRunning();

			if (pFCell->GetDirty())
				pFCell->Interpret();				// nach SetRunning geht's nicht mehr!
			pFCell->SetRunning(sal_True);

			ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
            ScRange aRef;
			while ( aIter.GetNextRef( aRef) )
			{
				if (aRef.aStart.Tab() <= nTab && aRef.aEnd.Tab() >= nTab)
				{
					if (Intersect( nCol1,nRow1,nCol2,nRow2,
							aRef.aStart.Col(),aRef.aStart.Row(),
							aRef.aEnd.Col(),aRef.aEnd.Row() ))
					{
						sal_Bool bAlien = ( aCellIter.GetTab() != nTab );
						sal_Bool bDrawRet;
						if (bAlien)
							bDrawRet = DrawAlienEntry( aRef, rData );
						else
							bDrawRet = DrawEntry( aCellIter.GetCol(), aCellIter.GetRow(),
													aRef, rData );
						if (bDrawRet)
						{
							nResult = DET_INS_INSERTED;			//	neuer Pfeil eingetragen
						}
						else
						{
							if (bRunning)
							{
								if (nResult == DET_INS_EMPTY)
									nResult = DET_INS_CIRCULAR;
							}
							else
							{
										//	weiterverfolgen

								if ( nLevel < rData.GetMaxLevel() )
								{
									sal_uInt16 nSubResult = InsertSuccLevel(
															aCellIter.GetCol(), aCellIter.GetRow(),
															aCellIter.GetCol(), aCellIter.GetRow(),
															rData, nLevel+1 );
									switch (nSubResult)
									{
										case DET_INS_INSERTED:
											nResult = DET_INS_INSERTED;
											break;
										case DET_INS_CONTINUE:
											if (nResult != DET_INS_INSERTED)
												nResult = DET_INS_CONTINUE;
											break;
										case DET_INS_CIRCULAR:
											if (nResult == DET_INS_EMPTY)
												nResult = DET_INS_CIRCULAR;
											break;
										// DET_INS_EMPTY: unveraendert lassen
									}
								}
								else									//	nMaxLevel erreicht
									if (nResult != DET_INS_INSERTED)
										nResult = DET_INS_CONTINUE;
							}
						}
					}
				}
			}
			pFCell->SetRunning(bRunning);
		}
		pCell = aCellIter.GetNext();
	}

	return nResult;
}

sal_uInt16 ScDetectiveFunc::FindSuccLevel( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
										sal_uInt16 nLevel, sal_uInt16 nDeleteLevel )
{
	DBG_ASSERT( nLevel<1000, "Level" );

	sal_uInt16 nResult = nLevel;
	sal_Bool bDelete = ( nDeleteLevel && nLevel == nDeleteLevel-1 );

	ScCellIterator aCellIter( pDoc, 0,0, nTab, MAXCOL,MAXROW, nTab );
	ScBaseCell* pCell = aCellIter.GetFirst();
	while (pCell)
	{
		if (pCell->GetCellType() == CELLTYPE_FORMULA)
		{
			ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
			sal_Bool bRunning = pFCell->IsRunning();

			if (pFCell->GetDirty())
				pFCell->Interpret();				// nach SetRunning geht's nicht mehr!
			pFCell->SetRunning(sal_True);

			ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
            ScRange aRef;
			while ( aIter.GetNextRef( aRef) )
			{
				if (aRef.aStart.Tab() <= nTab && aRef.aEnd.Tab() >= nTab)
				{
					if (Intersect( nCol1,nRow1,nCol2,nRow2,
							aRef.aStart.Col(),aRef.aStart.Row(),
							aRef.aEnd.Col(),aRef.aEnd.Row() ))
					{
						if ( bDelete )							// Pfeile, die hier anfangen
						{
							if (aRef.aStart != aRef.aEnd)
							{
								DeleteBox( aRef.aStart.Col(), aRef.aStart.Row(),
												aRef.aEnd.Col(), aRef.aEnd.Row() );
							}
							DeleteArrowsAt( aRef.aStart.Col(), aRef.aStart.Row(), sal_False );
						}
						else if ( !bRunning &&
								HasArrow( aRef.aStart,
											aCellIter.GetCol(),aCellIter.GetRow(),aCellIter.GetTab() ) )
						{
							sal_uInt16 nTemp = FindSuccLevel( aCellIter.GetCol(), aCellIter.GetRow(),
															aCellIter.GetCol(), aCellIter.GetRow(),
															nLevel+1, nDeleteLevel );
							if (nTemp > nResult)
								nResult = nTemp;
						}
					}
				}
			}

			pFCell->SetRunning(bRunning);
		}
		pCell = aCellIter.GetNext();
	}

	return nResult;
}


//
//	--------------------------------------------------------------------------------
//

sal_Bool ScDetectiveFunc::ShowPred( SCCOL nCol, SCROW nRow )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel)
		return sal_False;

	ScDetectiveData aData( pModel );

	sal_uInt16 nMaxLevel = 0;
	sal_uInt16 nResult = DET_INS_CONTINUE;
	while (nResult == DET_INS_CONTINUE && nMaxLevel < 1000)
	{
		aData.SetMaxLevel( nMaxLevel );
		nResult = InsertPredLevel( nCol, nRow, aData, 0 );
		++nMaxLevel;
	}

	return ( nResult == DET_INS_INSERTED );
}

sal_Bool ScDetectiveFunc::ShowSucc( SCCOL nCol, SCROW nRow )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel)
		return sal_False;

	ScDetectiveData aData( pModel );

	sal_uInt16 nMaxLevel = 0;
	sal_uInt16 nResult = DET_INS_CONTINUE;
	while (nResult == DET_INS_CONTINUE && nMaxLevel < 1000)
	{
		aData.SetMaxLevel( nMaxLevel );
		nResult = InsertSuccLevel( nCol, nRow, nCol, nRow, aData, 0 );
		++nMaxLevel;
	}

	return ( nResult == DET_INS_INSERTED );
}

sal_Bool ScDetectiveFunc::ShowError( SCCOL nCol, SCROW nRow )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel)
		return sal_False;

	ScRange aRange( nCol, nRow, nTab );
	ScAddress aErrPos;
	if ( !HasError( aRange,aErrPos ) )
		return sal_False;

	ScDetectiveData aData( pModel );

	aData.SetMaxLevel( 1000 );
	sal_uInt16 nResult = InsertErrorLevel( nCol, nRow, aData, 0 );

	return ( nResult == DET_INS_INSERTED );
}

sal_Bool ScDetectiveFunc::DeleteSucc( SCCOL nCol, SCROW nRow )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel)
		return sal_False;

	sal_uInt16 nLevelCount = FindSuccLevel( nCol, nRow, nCol, nRow, 0, 0 );
	if ( nLevelCount )
		FindSuccLevel( nCol, nRow, nCol, nRow, 0, nLevelCount );			// loeschen

	return ( nLevelCount != 0 );
}

sal_Bool ScDetectiveFunc::DeletePred( SCCOL nCol, SCROW nRow )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel)
		return sal_False;

	sal_uInt16 nLevelCount = FindPredLevel( nCol, nRow, 0, 0 );
	if ( nLevelCount )
		FindPredLevel( nCol, nRow, 0, nLevelCount );			// loeschen

	return ( nLevelCount != 0 );
}

sal_Bool ScDetectiveFunc::DeleteAll( ScDetectiveDelete eWhat )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel)
		return sal_False;

	SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
	DBG_ASSERT(pPage,"Page ?");

	pPage->RecalcObjOrdNums();

	long	nDelCount = 0;
	sal_uLong	nObjCount = pPage->GetObjCount();
	if (nObjCount)
	{
		SdrObject** ppObj = new SdrObject*[nObjCount];

		SdrObjListIter aIter( *pPage, IM_FLAT );
		SdrObject* pObject = aIter.Next();
		while (pObject)
		{
			if ( pObject->GetLayer() == SC_LAYER_INTERN )
			{
				sal_Bool bDoThis = sal_True;
				if ( eWhat != SC_DET_ALL )
				{
					sal_Bool bCircle = ( pObject->ISA(SdrCircObj) );
                    sal_Bool bCaption = ScDrawLayer::IsNoteCaption( pObject );
					if ( eWhat == SC_DET_DETECTIVE )		// Detektiv, aus Menue
						bDoThis = !bCaption;				// auch Kreise
					else if ( eWhat == SC_DET_CIRCLES )		// Kreise, wenn neue erzeugt werden
						bDoThis = bCircle;
					else if ( eWhat == SC_DET_ARROWS )		// DetectiveRefresh
						bDoThis = !bCaption && !bCircle;	// don't include circles
					else
					{
						DBG_ERROR("wat?");
					}
				}
				if ( bDoThis )
					ppObj[nDelCount++] = pObject;
			}

			pObject = aIter.Next();
		}

		long i;
		for (i=1; i<=nDelCount; i++)
	            pModel->AddCalcUndo< SdrUndoRemoveObj >( *ppObj[nDelCount-i] );

		for (i=1; i<=nDelCount; i++)
			pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() );

		delete[] ppObj;

        Modified();
	}

	return ( nDelCount != 0 );
}

sal_Bool ScDetectiveFunc::MarkInvalid(sal_Bool& rOverflow)
{
	rOverflow = sal_False;
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel)
		return sal_False;

	sal_Bool bDeleted = DeleteAll( SC_DET_CIRCLES );		// nur die Kreise

	ScDetectiveData aData( pModel );
	long nInsCount = 0;

	//	Stellen suchen, wo Gueltigkeit definiert ist

	ScDocAttrIterator aAttrIter( pDoc, nTab, 0,0,MAXCOL,MAXROW );
	SCCOL nCol;
	SCROW nRow1;
	SCROW nRow2;
	const ScPatternAttr* pPattern = aAttrIter.GetNext( nCol, nRow1, nRow2 );
	while ( pPattern && nInsCount < SC_DET_MAXCIRCLE )
	{
		sal_uLong nIndex = ((const SfxUInt32Item&)pPattern->GetItem(ATTR_VALIDDATA)).GetValue();
		if (nIndex)
		{
			const ScValidationData*	pData = pDoc->GetValidationEntry( nIndex );
			if ( pData )
			{
				//	Zellen in dem Bereich durchgehen

				sal_Bool bMarkEmpty = !pData->IsIgnoreBlank();
				SCROW nNextRow = nRow1;
				SCROW nRow;
				ScCellIterator aCellIter( pDoc, nCol,nRow1,nTab, nCol,nRow2,nTab );
				ScBaseCell* pCell = aCellIter.GetFirst();
				while ( pCell && nInsCount < SC_DET_MAXCIRCLE )
				{
					SCROW nCellRow = aCellIter.GetRow();
					if ( bMarkEmpty )
						for ( nRow = nNextRow; nRow < nCellRow && nInsCount < SC_DET_MAXCIRCLE; nRow++ )
						{
							DrawCircle( nCol, nRow, aData );
							++nInsCount;
						}
					if ( !pData->IsDataValid( pCell, ScAddress( nCol, nCellRow, nTab ) ) )
					{
						DrawCircle( nCol, nCellRow, aData );
						++nInsCount;
					}
					nNextRow = nCellRow + 1;
					pCell = aCellIter.GetNext();
				}
				if ( bMarkEmpty )
					for ( nRow = nNextRow; nRow <= nRow2 && nInsCount < SC_DET_MAXCIRCLE; nRow++ )
					{
						DrawCircle( nCol, nRow, aData );
						++nInsCount;
					}
			}
		}

		pPattern = aAttrIter.GetNext( nCol, nRow1, nRow2 );
	}

	if ( nInsCount >= SC_DET_MAXCIRCLE )
		rOverflow = sal_True;

	return ( bDeleted || nInsCount != 0 );
}

void ScDetectiveFunc::UpdateAllComments( ScDocument& rDoc )
{
	//	for all caption objects, update attributes and SpecialTextBoxShadow flag
	//	(on all tables - nTab is ignored!)

	//	no undo actions, this is refreshed after undo

    ScDrawLayer* pModel = rDoc.GetDrawLayer();
	if (!pModel)
		return;

    for( SCTAB nObjTab = 0, nTabCount = rDoc.GetTableCount(); nObjTab < nTabCount; ++nObjTab )
	{
        rDoc.InitializeNoteCaptions( nObjTab );
		SdrPage* pPage = pModel->GetPage( static_cast< sal_uInt16 >( nObjTab ) );
		DBG_ASSERT( pPage, "Page ?" );
		if( pPage )
		{
			SdrObjListIter aIter( *pPage, IM_FLAT );
			for( SdrObject* pObject = aIter.Next(); pObject; pObject = aIter.Next() )
			{
				if ( ScDrawObjData* pData = ScDrawLayer::GetNoteCaptionData( pObject, nObjTab ) )
				{
                    ScPostIt* pNote = rDoc.GetNote( pData->maStart );
                    // caption should exist, we iterate over drawing objects...
                    DBG_ASSERT( pNote && (pNote->GetCaption() == pObject), "ScDetectiveFunc::UpdateAllComments - invalid cell note" );
                    if( pNote )
                    {
                        ScCommentData aData( rDoc, pModel );
                        SfxItemSet aAttrColorSet = pObject->GetMergedItemSet();
                        aAttrColorSet.Put( XFillColorItem( String(), GetCommentColor() ) );
                        aData.UpdateCaptionSet( aAttrColorSet );
                        pObject->SetMergedItemSetAndBroadcast( aData.GetCaptionSet() );
                        if( SdrCaptionObj* pCaption = dynamic_cast< SdrCaptionObj* >( pObject ) )
                        {
                            pCaption->SetSpecialTextBoxShadow();
                            pCaption->SetFixedTail();
                        }
                    }
    			}
			}
		}
	}
}

void ScDetectiveFunc::UpdateAllArrowColors()
{
	//	no undo actions necessary

	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel)
		return;

	for( SCTAB nObjTab = 0, nTabCount = pDoc->GetTableCount(); nObjTab < nTabCount; ++nObjTab )
	{
		SdrPage* pPage = pModel->GetPage( static_cast< sal_uInt16 >( nObjTab ) );
		DBG_ASSERT( pPage, "Page ?" );
		if( pPage )
		{
			SdrObjListIter aIter( *pPage, IM_FLAT );
			for( SdrObject* pObject = aIter.Next(); pObject; pObject = aIter.Next() )
			{
				if ( pObject->GetLayer() == SC_LAYER_INTERN )
				{
					sal_Bool bArrow = sal_False;
					sal_Bool bError = sal_False;

					ScAddress aPos;
					ScRange aSource;
					sal_Bool bDummy;
					ScDetectiveObjType eType = GetDetectiveObjectType( pObject, nObjTab, aPos, aSource, bDummy );
					if ( eType == SC_DETOBJ_ARROW || eType == SC_DETOBJ_TOOTHERTAB )
					{
						//	source is valid, determine error flag from source range

						ScAddress aErrPos;
						if ( HasError( aSource, aErrPos ) )
							bError = sal_True;
						else
							bArrow = sal_True;
					}
					else if ( eType == SC_DETOBJ_FROMOTHERTAB )
					{
						//	source range is no longer known, take error flag from formula itself
						//	(this means, if the formula has an error, all references to other tables
						//	are marked red)

						ScAddress aErrPos;
						if ( HasError( ScRange( aPos), aErrPos ) )
							bError = sal_True;
						else
							bArrow = sal_True;
					}
					else if ( eType == SC_DETOBJ_CIRCLE )
					{
						//	circles (error marks) are always red

						bError = sal_True;
					}
					else if ( eType == SC_DETOBJ_NONE )
					{
						//	frame for area reference has no ObjType, always gets arrow color

						if ( pObject->ISA( SdrRectObj ) && !pObject->ISA( SdrCaptionObj ) )
						{
							bArrow = sal_True;
						}
					}

					if ( bArrow || bError )
					{
						ColorData nColorData = ( bError ? GetErrorColor() : GetArrowColor() );
						//pObject->SendRepaintBroadcast(pObject->GetBoundRect());
						pObject->SetMergedItem( XLineColorItem( String(), Color( nColorData ) ) );

						// repaint only
						pObject->ActionChanged();
						// pObject->SendRepaintBroadcast(pObject->GetBoundRect());
					}
				}
			}
		}
	}
}

sal_Bool ScDetectiveFunc::FindFrameForObject( SdrObject* pObject, ScRange& rRange )
{
	//	find the rectangle for an arrow (always the object directly before the arrow)
	//	rRange must be initialized to the source cell of the arrow (start of area)

	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel) return sal_False;

	SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
	DBG_ASSERT(pPage,"Page ?");
	if (!pPage) return sal_False;

	// test if the object is a direct page member
	if( pObject && pObject->GetPage() && (pObject->GetPage() == pObject->GetObjList()) )
	{
		// Is there a previous object?
		const sal_uInt32 nOrdNum(pObject->GetOrdNum());

		if(nOrdNum > 0)
		{
			SdrObject* pPrevObj = pPage->GetObj(nOrdNum - 1);

			if ( pPrevObj && pPrevObj->GetLayer() == SC_LAYER_INTERN && pPrevObj->ISA(SdrRectObj) )
			{
				ScDrawObjData* pPrevData = ScDrawLayer::GetObjDataTab( pPrevObj, rRange.aStart.Tab() );
				if ( pPrevData && pPrevData->maStart.IsValid() && pPrevData->maEnd.IsValid() && (pPrevData->maStart == rRange.aStart) )
                {
                    rRange.aEnd = pPrevData->maEnd;
                    return sal_True;
                }
			}
		}
	}
	return sal_False;
}

ScDetectiveObjType ScDetectiveFunc::GetDetectiveObjectType( SdrObject* pObject, SCTAB nObjTab,
								ScAddress& rPosition, ScRange& rSource, sal_Bool& rRedLine )
{
	rRedLine = sal_False;
	ScDetectiveObjType eType = SC_DETOBJ_NONE;

	if ( pObject && pObject->GetLayer() == SC_LAYER_INTERN )
	{
        if ( ScDrawObjData* pData = ScDrawLayer::GetObjDataTab( pObject, nObjTab ) )
        {
            bool bValidStart = pData->maStart.IsValid();
            bool bValidEnd = pData->maEnd.IsValid();

            if ( pObject->IsPolyObj() && pObject->GetPointCount() == 2 )
            {
                // line object -> arrow

                if ( bValidStart )
                    eType = bValidEnd ? SC_DETOBJ_ARROW : SC_DETOBJ_TOOTHERTAB;
                else if ( bValidEnd )
                    eType = SC_DETOBJ_FROMOTHERTAB;

                if ( bValidStart )
                    rSource = pData->maStart;
                if ( bValidEnd )
                    rPosition = pData->maEnd;

                if ( bValidStart && lcl_HasThickLine( *pObject ) )
                {
                    // thick line -> look for frame before this object

                    FindFrameForObject( pObject, rSource );     // modifies rSource
                }

                ColorData nObjColor = ((const XLineColorItem&)pObject->GetMergedItem(XATTR_LINECOLOR)).GetColorValue().GetColor();
                if ( nObjColor == GetErrorColor() && nObjColor != GetArrowColor() )
                    rRedLine = sal_True;
            }
            else if ( pObject->ISA(SdrCircObj) )
            {
                if ( bValidStart )
                {
                    // cell position is returned in rPosition

                    rPosition = pData->maStart;
                    eType = SC_DETOBJ_CIRCLE;
                }
            }
        }
	}

	return eType;
}

void ScDetectiveFunc::InsertObject( ScDetectiveObjType eType,
							const ScAddress& rPosition, const ScRange& rSource,
							sal_Bool bRedLine )
{
	ScDrawLayer* pModel = pDoc->GetDrawLayer();
	if (!pModel) return;
	ScDetectiveData aData( pModel );

	switch (eType)
	{
		case SC_DETOBJ_ARROW:
		case SC_DETOBJ_FROMOTHERTAB:
			InsertArrow( rPosition.Col(), rPosition.Row(),
						 rSource.aStart.Col(), rSource.aStart.Row(),
						 rSource.aEnd.Col(), rSource.aEnd.Row(),
						 (eType == SC_DETOBJ_FROMOTHERTAB), bRedLine, aData );
			break;
		case SC_DETOBJ_TOOTHERTAB:
			InsertToOtherTab( rSource.aStart.Col(), rSource.aStart.Row(),
							  rSource.aEnd.Col(), rSource.aEnd.Row(),
							  bRedLine, aData );
			break;
		case SC_DETOBJ_CIRCLE:
			DrawCircle( rPosition.Col(), rPosition.Row(), aData );
			break;
        default:
        {
            // added to avoid warnings
        }
	}
}

// static
ColorData ScDetectiveFunc::GetArrowColor()
{
	if (!bColorsInitialized)
		InitializeColors();
	return nArrowColor;
}

// static
ColorData ScDetectiveFunc::GetErrorColor()
{
	if (!bColorsInitialized)
		InitializeColors();
	return nErrorColor;
}

// static
ColorData ScDetectiveFunc::GetCommentColor()
{
	if (!bColorsInitialized)
		InitializeColors();
	return nCommentColor;
}

// static
void ScDetectiveFunc::InitializeColors()
{
	// may be called several times to update colors from configuration

    const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig();
    nArrowColor   = rColorCfg.GetColorValue(svtools::CALCDETECTIVE).nColor;
    nErrorColor   = rColorCfg.GetColorValue(svtools::CALCDETECTIVEERROR).nColor;
    nCommentColor = rColorCfg.GetColorValue(svtools::CALCNOTESBACKGROUND).nColor;

	bColorsInitialized = sal_True;
}

// static
sal_Bool ScDetectiveFunc::IsColorsInitialized()
{
	return bColorsInitialized;
}

