diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sc/source/core/tool/detfunc.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/core/tool/detfunc.cxx')
-rw-r--r-- | sc/source/core/tool/detfunc.cxx | 1755 |
1 files changed, 1755 insertions, 0 deletions
diff --git a/sc/source/core/tool/detfunc.cxx b/sc/source/core/tool/detfunc.cxx new file mode 100644 index 000000000..9e700f411 --- /dev/null +++ b/sc/source/core/tool/detfunc.cxx @@ -0,0 +1,1755 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +#include <scitems.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/eeitem.hxx> +#include <formula/errorcodes.hxx> +#include <o3tl/unit_conversion.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/sdtagitm.hxx> +#include <svx/sxcecitm.hxx> +#include <svl/whiter.hxx> +#include <osl/diagnose.h> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> + +#include <attrib.hxx> +#include <detfunc.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <drwlayer.hxx> +#include <userdat.hxx> +#include <validat.hxx> +#include <formulacell.hxx> +#include <docpool.hxx> +#include <patattr.hxx> +#include <scmod.hxx> +#include <postit.hxx> +#include <reftokenhelper.hxx> +#include <formulaiter.hxx> +#include <cellvalue.hxx> + +#include <vector> +#include <memory> + +using ::std::vector; +using namespace com::sun::star; + +namespace { + +enum DetInsertResult { // return-values for inserting in one 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; //TODO: individually ? + sal_uInt16 nMaxLevel; + +public: + explicit 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; } +}; + +namespace { + +class ScCommentData +{ +public: + ScCommentData( ScDocument& rDoc, SdrModel* pModel ); + + SfxItemSet& GetCaptionSet() { return aCaptionSet; } + void UpdateCaptionSet( const SfxItemSet& rItemSet ); + +private: + SfxItemSet aCaptionSet; +}; + +} + +Color ScDetectiveFunc::nArrowColor = Color(0); +Color ScDetectiveFunc::nErrorColor = Color(0); +Color ScDetectiveFunc::nCommentColor = Color(0); +bool ScDetectiveFunc::bColorsInitialized = false; + +static bool lcl_HasThickLine( const SdrObject& rObj ) +{ + // thin lines get width 0 -> everything greater 0 is a thick line + + return rObj.GetMergedItem(XATTR_LINEWIDTH).GetValue() > 0; +} + +ScDetectiveData::ScDetectiveData( SdrModel* pModel ) : + aBoxSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + aArrowSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + aToTabSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + aFromTabSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + aCircleSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + nMaxLevel(0) +{ + + aBoxSet.Put( XLineColorItem( OUString(), ScDetectiveFunc::GetArrowColor() ) ); + aBoxSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) ); + + // create default line endings (like XLineEndList::Create) + // to be independent from the configured line endings + + 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::utils::createPolygonFromEllipse(basegfx::B2DPoint(0.0, 0.0), 100.0, 100.0)); + aCircle.setClosed(true); + + const OUString aName; + + aArrowSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aCircle) ) ); + aArrowSet.Put( XLineStartWidthItem( 200 ) ); + aArrowSet.Put( XLineStartCenterItem( true ) ); + aArrowSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) ); + aArrowSet.Put( XLineEndWidthItem( 200 ) ); + aArrowSet.Put( XLineEndCenterItem( false ) ); + + aToTabSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aCircle) ) ); + aToTabSet.Put( XLineStartWidthItem( 200 ) ); + aToTabSet.Put( XLineStartCenterItem( true ) ); + aToTabSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aSquare) ) ); + aToTabSet.Put( XLineEndWidthItem( 300 ) ); + aToTabSet.Put( XLineEndCenterItem( false ) ); + + aFromTabSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aSquare) ) ); + aFromTabSet.Put( XLineStartWidthItem( 300 ) ); + aFromTabSet.Put( XLineStartCenterItem( true ) ); + aFromTabSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) ); + aFromTabSet.Put( XLineEndWidthItem( 200 ) ); + aFromTabSet.Put( XLineEndCenterItem( false ) ); + + aCircleSet.Put( XLineColorItem( OUString(), ScDetectiveFunc::GetErrorColor() ) ); + aCircleSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) ); + aCircleSet.Put( XLineWidthItem( 55 ) ); // 54 = 1 Pixel +} + +ScCommentData::ScCommentData( ScDocument& rDoc, SdrModel* pModel ) : + aCaptionSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END, EE_ITEMS_START, EE_ITEMS_END> ) +{ + 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); + + aCaptionSet.Put( XLineStartItem( OUString(), basegfx::B2DPolyPolygon(aTriangle))); + aCaptionSet.Put( XLineStartWidthItem( 200 ) ); + aCaptionSet.Put( XLineStartCenterItem( false ) ); + aCaptionSet.Put( XFillStyleItem( drawing::FillStyle_SOLID ) ); + Color aYellow( ScDetectiveFunc::GetCommentColor() ); + aCaptionSet.Put( XFillColorItem( OUString(), 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( makeSdrShadowItem( false ) ); + aCaptionSet.Put( makeSdrShadowXDistItem( 100 ) ); + aCaptionSet.Put( makeSdrShadowYDistItem( 100 ) ); + + // text attributes + aCaptionSet.Put( makeSdrTextLeftDistItem( 100 ) ); + aCaptionSet.Put( makeSdrTextRightDistItem( 100 ) ); + aCaptionSet.Put( makeSdrTextUpperDistItem( 100 ) ); + aCaptionSet.Put( makeSdrTextLowerDistItem( 100 ) ); + + aCaptionSet.Put( makeSdrTextAutoGrowWidthItem( false ) ); + aCaptionSet.Put( makeSdrTextAutoGrowHeightItem( true ) ); + + // do use the default cell style, so the user has a chance to + // modify the font for the annotations + 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( SdrCaptionEscDir::BestFit) ); +} + +void ScCommentData::UpdateCaptionSet( const SfxItemSet& rItemSet ) +{ + SfxWhichIter aWhichIter( rItemSet ); + const SfxPoolItem* pPoolItem = nullptr; + + for( sal_uInt16 nWhich = aWhichIter.FirstWhich(); nWhich > 0; nWhich = aWhichIter.NextWhich() ) + { + if(aWhichIter.GetItemState(false, &pPoolItem) == SfxItemState::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() +{ + rDoc.SetStreamValid(nTab, false); +} + +static 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; +} + +bool ScDetectiveFunc::HasError( const ScRange& rRange, ScAddress& rErrPos ) +{ + rErrPos = rRange.aStart; + FormulaError nError = FormulaError::NONE; + + ScCellIterator aIter( rDoc, rRange); + for (bool bHasCell = aIter.first(); bHasCell; bHasCell = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + nError = aIter.getFormulaCell()->GetErrCode(); + if (nError != FormulaError::NONE) + rErrPos = aIter.GetPos(); + } + + return (nError != FormulaError::NONE); +} + +Point ScDetectiveFunc::GetDrawPos( SCCOL nCol, SCROW nRow, DrawPosMode eMode ) const +{ + OSL_ENSURE( rDoc.ValidColRow( nCol, nRow ), "ScDetectiveFunc::GetDrawPos - invalid cell address" ); + nCol = rDoc.SanitizeCol( nCol ); + nRow = rDoc.SanitizeRow( nRow ); + + Point aPos; + + switch( eMode ) + { + case DrawPosMode::TopLeft: + break; + case DrawPosMode::BottomRight: + ++nCol; + ++nRow; + break; + case DrawPosMode::DetectiveArrow: + aPos.AdjustX(rDoc.GetColWidth( nCol, nTab ) / 4 ); + aPos.AdjustY(rDoc.GetRowHeight( nRow, nTab ) / 2 ); + break; + } + + for ( SCCOL i = 0; i < nCol; ++i ) + aPos.AdjustX(rDoc.GetColWidth( i, nTab ) ); + aPos.AdjustY(rDoc.GetRowHeight( 0, nRow - 1, nTab ) ); + + aPos.setX(o3tl::convert(aPos.X(), o3tl::Length::twip, o3tl::Length::mm100)); + aPos.setY(o3tl::convert(aPos.Y(), o3tl::Length::twip, o3tl::Length::mm100)); + + if ( rDoc.IsNegativePage( nTab ) ) + aPos.setX( aPos.X() * -1 ); + + return aPos; +} + +tools::Rectangle ScDetectiveFunc::GetDrawRect( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const +{ + tools::Rectangle aRect( + GetDrawPos( ::std::min( nCol1, nCol2 ), ::std::min( nRow1, nRow2 ), DrawPosMode::TopLeft ), + GetDrawPos( ::std::max( nCol1, nCol2 ), ::std::max( nRow1, nRow2 ), DrawPosMode::BottomRight ) ); + aRect.Justify(); // reorder left/right in RTL sheets + return aRect; +} + +tools::Rectangle ScDetectiveFunc::GetDrawRect( SCCOL nCol, SCROW nRow ) const +{ + return GetDrawRect( nCol, nRow, nCol, nRow ); +} + +static bool lcl_IsOtherTab( const basegfx::B2DPolyPolygon& rPolyPolygon ) +{ + // test if rPolygon is the line end for "other table" (rectangle) + if(1 == rPolyPolygon.count()) + { + const basegfx::B2DPolygon& aSubPoly(rPolyPolygon.getB2DPolygon(0)); + + // #i73305# circle consists of 4 segments, too, distinguishable from square by + // the use of control points + if(4 == aSubPoly.count() && aSubPoly.isClosed() && !aSubPoly.areControlPointsUsed()) + { + return true; + } + } + + return false; +} + +bool ScDetectiveFunc::HasArrow( const ScAddress& rStart, + SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab ) +{ + bool bStartAlien = ( rStart.Tab() != nTab ); + bool bEndAlien = ( nEndTab != nTab ); + + if (bStartAlien && bEndAlien) + { + OSL_FAIL("bStartAlien && bEndAlien"); + return true; + } + + tools::Rectangle aStartRect; + tools::Rectangle aEndRect; + if (!bStartAlien) + aStartRect = GetDrawRect( rStart.Col(), rStart.Row() ); + if (!bEndAlien) + aEndRect = GetDrawRect( nEndCol, nEndRow ); + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + bool bFound = false; + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject && !bFound) + { + if ( pObject->GetLayer()==SC_LAYER_INTERN && + pObject->IsPolyObj() && pObject->GetPointCount()==2 ) + { + const SfxItemSet& rSet = pObject->GetMergedItemSet(); + + bool bObjStartAlien = + lcl_IsOtherTab( rSet.Get(XATTR_LINESTART).GetLineStartValue() ); + bool bObjEndAlien = + lcl_IsOtherTab( rSet.Get(XATTR_LINEEND).GetLineEndValue() ); + + bool bStartHit = bStartAlien ? bObjStartAlien : + ( !bObjStartAlien && aStartRect.Contains(pObject->GetPoint(0)) ); + bool bEndHit = bEndAlien ? bObjEndAlien : + ( !bObjEndAlien && aEndRect.Contains(pObject->GetPoint(1)) ); + + if ( bStartHit && bEndHit ) + bFound = true; + } + pObject = aIter.Next(); + } + + return bFound; +} + +bool ScDetectiveFunc::IsNonAlienArrow( const SdrObject* pObject ) +{ + if ( pObject->GetLayer()==SC_LAYER_INTERN && + pObject->IsPolyObj() && pObject->GetPointCount()==2 ) + { + const SfxItemSet& rSet = pObject->GetMergedItemSet(); + + bool bObjStartAlien = + lcl_IsOtherTab( rSet.Get(XATTR_LINESTART).GetLineStartValue() ); + bool bObjEndAlien = + lcl_IsOtherTab( rSet.Get(XATTR_LINEEND).GetLineEndValue() ); + + return !bObjStartAlien && !bObjEndAlien; + } + + return false; +} + +// InsertXXX: called from DrawEntry/DrawAlienEntry and InsertObject + +void ScDetectiveFunc::InsertArrow( SCCOL nCol, SCROW nRow, + SCCOL nRefStartCol, SCROW nRefStartRow, + SCCOL nRefEndCol, SCROW nRefEndRow, + bool bFromOtherTab, bool bRed, + ScDetectiveData& rData ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + + bool bArea = ( nRefStartCol != nRefEndCol || nRefStartRow != nRefEndRow ); + if (bArea && !bFromOtherTab) + { + // insert the rectangle before the arrow - this is relied on in FindFrameForObject + + tools::Rectangle aRect = GetDrawRect( nRefStartCol, nRefStartRow, nRefEndCol, nRefEndRow ); + SdrRectObj* pBox = new SdrRectObj( + *pModel, + aRect); + + pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet()); + + pBox->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pBox ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pBox ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox, true ); + pData->maStart.Set( nRefStartCol, nRefStartRow, nTab); + pData->maEnd.Set( nRefEndCol, nRefEndRow, nTab); + } + + Point aStartPos = GetDrawPos( nRefStartCol, nRefStartRow, DrawPosMode::DetectiveArrow ); + Point aEndPos = GetDrawPos( nCol, nRow, DrawPosMode::DetectiveArrow ); + + if (bFromOtherTab) + { + bool bNegativePage = rDoc.IsNegativePage( nTab ); + tools::Long nPageSign = bNegativePage ? -1 : 1; + + aStartPos = Point( aEndPos.X() - 1000 * nPageSign, aEndPos.Y() - 1000 ); + if (aStartPos.X() * nPageSign < 0) + aStartPos.AdjustX(2000 * nPageSign ); + if (aStartPos.Y() < 0) + aStartPos.AdjustY(2000 ); + } + + SfxItemSet& rAttrSet = bFromOtherTab ? rData.GetFromTabSet() : rData.GetArrowSet(); + + if (bArea && !bFromOtherTab) + rAttrSet.Put( XLineWidthItem( 50 ) ); // range + else + rAttrSet.Put( XLineWidthItem( 0 ) ); // single reference + + Color nColor = ( bRed ? GetErrorColor() : GetArrowColor() ); + rAttrSet.Put( XLineColorItem( OUString(), nColor ) ); + + basegfx::B2DPolygon aTempPoly; + aTempPoly.append(basegfx::B2DPoint(aStartPos.X(), aStartPos.Y())); + aTempPoly.append(basegfx::B2DPoint(aEndPos.X(), aEndPos.Y())); + SdrPathObj* pArrow = new SdrPathObj( + *pModel, + SdrObjKind::Line, + basegfx::B2DPolyPolygon(aTempPoly)); + pArrow->NbcSetLogicRect(tools::Rectangle::Justify(aStartPos,aEndPos)); //TODO: needed ??? + pArrow->SetMergedItemSetAndBroadcast(rAttrSet); + + pArrow->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pArrow ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pArrow ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData(pArrow, true); + if (bFromOtherTab) + pData->maStart.SetInvalid(); + else + pData->maStart.Set( nRefStartCol, nRefStartRow, nTab); + + pData->maEnd.Set( nCol, nRow, nTab); + pData->meType = ScDrawObjData::DetectiveArrow; + + Modified(); +} + +void ScDetectiveFunc::InsertToOtherTab( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, bool bRed, + ScDetectiveData& rData ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + + bool bArea = ( nStartCol != nEndCol || nStartRow != nEndRow ); + if (bArea) + { + tools::Rectangle aRect = GetDrawRect( nStartCol, nStartRow, nEndCol, nEndRow ); + SdrRectObj* pBox = new SdrRectObj( + *pModel, + aRect); + + pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet()); + + pBox->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pBox ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pBox ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox, true ); + pData->maStart.Set( nStartCol, nStartRow, nTab); + pData->maEnd.Set( nEndCol, nEndRow, nTab); + } + + bool bNegativePage = rDoc.IsNegativePage( nTab ); + tools::Long nPageSign = bNegativePage ? -1 : 1; + + Point aStartPos = GetDrawPos( nStartCol, nStartRow, DrawPosMode::DetectiveArrow ); + Point aEndPos( aStartPos.X() + 1000 * nPageSign, aStartPos.Y() - 1000 ); + if (aEndPos.Y() < 0) + aEndPos.AdjustY(2000 ); + + SfxItemSet& rAttrSet = rData.GetToTabSet(); + if (bArea) + rAttrSet.Put( XLineWidthItem( 50 ) ); // range + else + rAttrSet.Put( XLineWidthItem( 0 ) ); // single reference + + Color nColor = ( bRed ? GetErrorColor() : GetArrowColor() ); + rAttrSet.Put( XLineColorItem( OUString(), nColor ) ); + + basegfx::B2DPolygon aTempPoly; + aTempPoly.append(basegfx::B2DPoint(aStartPos.X(), aStartPos.Y())); + aTempPoly.append(basegfx::B2DPoint(aEndPos.X(), aEndPos.Y())); + SdrPathObj* pArrow = new SdrPathObj( + *pModel, + SdrObjKind::Line, + basegfx::B2DPolyPolygon(aTempPoly)); + pArrow->NbcSetLogicRect(tools::Rectangle::Justify(aStartPos,aEndPos)); //TODO: needed ??? + + pArrow->SetMergedItemSetAndBroadcast(rAttrSet); + + pArrow->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pArrow ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pArrow ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData( pArrow, true ); + pData->maStart.Set( nStartCol, nStartRow, nTab); + pData->maEnd.SetInvalid(); + + Modified(); +} + +// DrawEntry: formula from this spreadsheet, +// reference on this or other +// DrawAlienEntry: formula from other spreadsheet, +// reference on this + +// return FALSE: there was already an arrow + +bool ScDetectiveFunc::DrawEntry( SCCOL nCol, SCROW nRow, + const ScRange& rRef, + ScDetectiveData& rData ) +{ + if ( HasArrow( rRef.aStart, nCol, nRow, nTab ) ) + return false; + + ScAddress aErrorPos; + bool bError = HasError( rRef, aErrorPos ); + bool bAlien = ( rRef.aEnd.Tab() < nTab || rRef.aStart.Tab() > nTab ); + + InsertArrow( nCol, nRow, + rRef.aStart.Col(), rRef.aStart.Row(), + rRef.aEnd.Col(), rRef.aEnd.Row(), + bAlien, bError, rData ); + return true; +} + +bool ScDetectiveFunc::DrawAlienEntry( const ScRange& rRef, + ScDetectiveData& rData ) +{ + if ( HasArrow( rRef.aStart, 0, 0, nTab+1 ) ) + return false; + + ScAddress aErrorPos; + bool bError = HasError( rRef, aErrorPos ); + + InsertToOtherTab( rRef.aStart.Col(), rRef.aStart.Row(), + rRef.aEnd.Col(), rRef.aEnd.Row(), + bError, rData ); + return true; +} + +void ScDetectiveFunc::DrawCircle( SCCOL nCol, SCROW nRow, ScDetectiveData& rData ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + + tools::Rectangle aRect = ScDrawLayer::GetCellRect(rDoc, ScAddress(nCol, nRow, nTab), true); + aRect.AdjustLeft( -250 ); + aRect.AdjustRight(250 ); + aRect.AdjustTop( -70 ); + aRect.AdjustBottom(70 ); + + SdrCircObj* pCircle = new SdrCircObj( + *pModel, + SdrCircKind::Full, + aRect); + SfxItemSet& rAttrSet = rData.GetCircleSet(); + + pCircle->SetMergedItemSetAndBroadcast(rAttrSet); + + pCircle->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pCircle ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pCircle ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData( pCircle, true ); + pData->maStart.Set( nCol, nRow, nTab); + pData->maEnd.SetInvalid(); + pData->meType = ScDrawObjData::ValidationCircle; + + Modified(); +} + +void ScDetectiveFunc::DeleteArrowsAt( SCCOL nCol, SCROW nRow, bool bDestPnt ) +{ + tools::Rectangle aRect = GetDrawRect( nCol, nRow ); + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + if (!nObjCount) + return; + + size_t nDelCount = 0; + std::unique_ptr<SdrObject*[]> ppObj(new SdrObject*[nObjCount]); + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetLayer()==SC_LAYER_INTERN && + pObject->IsPolyObj() && pObject->GetPointCount()==2 ) + { + if (aRect.Contains(pObject->GetPoint(bDestPnt ? 1 : 0))) // start/destinationpoint + ppObj[nDelCount++] = pObject; + } + + pObject = aIter.Next(); + } + + const bool bRecording = pModel->IsRecording(); + + if (bRecording) + { + for (size_t i=1; i<=nDelCount; ++i) + pModel->AddCalcUndo(std::make_unique<SdrUndoDelObj>(*ppObj[nDelCount-i])); + } + + for (size_t i=1; i<=nDelCount; ++i) + { + // remove the object from the drawing page, delete if undo is disabled + SdrObject* pObj = pPage->RemoveObject(ppObj[nDelCount-i]->GetOrdNum()); + if( !bRecording ) + SdrObject::Free( pObj ); + } + + ppObj.reset(); + + Modified(); +} + + // delete box around reference + +#define SC_DET_TOLERANCE 50 + +static bool RectIsPoints( const tools::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 ) +{ + tools::Rectangle aCornerRect = GetDrawRect( nCol1, nRow1, nCol2, nRow2 ); + Point aStartCorner = aCornerRect.TopLeft(); + Point aEndCorner = aCornerRect.BottomRight(); + tools::Rectangle aObjRect; + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + if (!nObjCount) + return; + + size_t nDelCount = 0; + std::unique_ptr<SdrObject*[]> ppObj(new SdrObject*[nObjCount]); + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetLayer() == SC_LAYER_INTERN ) + if ( auto pSdrRectObj = dynamic_cast< const SdrRectObj* >(pObject) ) + { + aObjRect = pSdrRectObj->GetLogicRect(); + aObjRect.Justify(); + if ( RectIsPoints( aObjRect, aStartCorner, aEndCorner ) ) + ppObj[nDelCount++] = pObject; + } + + pObject = aIter.Next(); + } + + for (size_t i=1; i<=nDelCount; ++i) + pModel->AddCalcUndo( std::make_unique<SdrUndoRemoveObj>( *ppObj[nDelCount-i] ) ); + + for (size_t i=1; i<=nDelCount; ++i) + pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() ); + + ppObj.reset(); + + Modified(); +} + +sal_uInt16 ScDetectiveFunc::InsertPredLevelArea( const ScRange& rRef, + ScDetectiveData& rData, sal_uInt16 nLevel ) +{ + sal_uInt16 nResult = DET_INS_EMPTY; + + ScCellIterator aIter( rDoc, rRef); + for (bool bHasCell = aIter.first(); bHasCell; bHasCell = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + const ScAddress& rPos = aIter.GetPos(); + switch (InsertPredLevel(rPos.Col(), rPos.Row(), 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; + default: + ; + } + } + + return nResult; +} + +sal_uInt16 ScDetectiveFunc::InsertPredLevel( SCCOL nCol, SCROW nRow, ScDetectiveData& rData, + sal_uInt16 nLevel ) +{ + ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, nTab)); + if (aCell.meType != CELLTYPE_FORMULA) + return DET_INS_EMPTY; + + ScFormulaCell* pFCell = aCell.mpFormula; + if (pFCell->IsRunning()) + return DET_INS_CIRCULAR; + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + sal_uInt16 nResult = DET_INS_EMPTY; + + ScDetectiveRefIter aIter(rDoc, pFCell); + ScRange aRef; + while ( aIter.GetNextRef( aRef ) ) + { + if (DrawEntry( nCol, nRow, aRef, rData )) + { + nResult = DET_INS_INSERTED; // insert new arrow + } + else + { + // continue + + if ( nLevel < rData.GetMaxLevel() ) + { + sal_uInt16 nSubResult; + 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: no change + } + } + else // nMaxLevel reached + if (nResult != DET_INS_INSERTED) + nResult = DET_INS_CONTINUE; + } + } + + pFCell->SetRunning(false); + + return nResult; +} + +sal_uInt16 ScDetectiveFunc::FindPredLevelArea( const ScRange& rRef, + sal_uInt16 nLevel, sal_uInt16 nDeleteLevel ) +{ + sal_uInt16 nResult = nLevel; + + ScCellIterator aCellIter( rDoc, rRef); + for (bool bHasCell = aCellIter.first(); bHasCell; bHasCell = aCellIter.next()) + { + if (aCellIter.getType() != CELLTYPE_FORMULA) + continue; + + sal_uInt16 nTemp = FindPredLevel(aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), nLevel, nDeleteLevel); + if (nTemp > nResult) + nResult = nTemp; + } + + return nResult; +} + + // nDeleteLevel != 0 -> delete + +sal_uInt16 ScDetectiveFunc::FindPredLevel( SCCOL nCol, SCROW nRow, sal_uInt16 nLevel, sal_uInt16 nDeleteLevel ) +{ + OSL_ENSURE( nLevel<1000, "Level" ); + + ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, nTab)); + if (aCell.meType != CELLTYPE_FORMULA) + return nLevel; + + ScFormulaCell* pFCell = aCell.mpFormula; + if (pFCell->IsRunning()) + return nLevel; + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + sal_uInt16 nResult = nLevel; + bool bDelete = ( nDeleteLevel && nLevel == nDeleteLevel-1 ); + + if ( bDelete ) + { + DeleteArrowsAt( nCol, nRow, true ); // arrows, that are pointing here + } + + ScDetectiveRefIter aIter(rDoc, pFCell); + ScRange aRef; + while ( aIter.GetNextRef( aRef) ) + { + bool bArea = ( aRef.aStart != aRef.aEnd ); + + if ( bDelete ) // delete frame ? + { + if (bArea) + { + DeleteBox( aRef.aStart.Col(), aRef.aStart.Row(), aRef.aEnd.Col(), aRef.aEnd.Row() ); + } + } + else // continue searching + { + 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(false); + + return nResult; +} + +sal_uInt16 ScDetectiveFunc::InsertErrorLevel( SCCOL nCol, SCROW nRow, ScDetectiveData& rData, + sal_uInt16 nLevel ) +{ + ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, nTab)); + if (aCell.meType != CELLTYPE_FORMULA) + return DET_INS_EMPTY; + + ScFormulaCell* pFCell = aCell.mpFormula; + if (pFCell->IsRunning()) + return DET_INS_CIRCULAR; + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + sal_uInt16 nResult = DET_INS_EMPTY; + + ScDetectiveRefIter aIter(rDoc, pFCell); + ScRange aRef; + ScAddress aErrorPos; + bool bHasError = false; + while ( aIter.GetNextRef( aRef ) ) + { + if (HasError( aRef, aErrorPos )) + { + bHasError = true; + if (DrawEntry( nCol, nRow, ScRange( aErrorPos), rData )) + nResult = DET_INS_INSERTED; + + if ( nLevel < rData.GetMaxLevel() ) // hits most of the time + { + if (InsertErrorLevel( aErrorPos.Col(), aErrorPos.Row(), + rData, nLevel+1 ) == DET_INS_INSERTED) + nResult = DET_INS_INSERTED; + } + } + } + + pFCell->SetRunning(false); + + // leaves ? + 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 ) +{ + // over the entire document. + + sal_uInt16 nResult = DET_INS_EMPTY; + ScCellIterator aCellIter(rDoc, ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB)); // all sheets + for (bool bHas = aCellIter.first(); bHas; bHas = aCellIter.next()) + { + if (aCellIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = aCellIter.getFormulaCell(); + bool bRunning = pFCell->IsRunning(); + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + ScDetectiveRefIter aIter(rDoc, pFCell); + 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() )) + { + bool bAlien = ( aCellIter.GetPos().Tab() != nTab ); + bool bDrawRet; + if (bAlien) + bDrawRet = DrawAlienEntry( aRef, rData ); + else + bDrawRet = DrawEntry( aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), + aRef, rData ); + if (bDrawRet) + { + nResult = DET_INS_INSERTED; // insert new arrow + } + else + { + if (bRunning) + { + if (nResult == DET_INS_EMPTY) + nResult = DET_INS_CIRCULAR; + } + else + { + + if ( nLevel < rData.GetMaxLevel() ) + { + sal_uInt16 nSubResult = InsertSuccLevel( + aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), + aCellIter.GetPos().Col(), aCellIter.GetPos().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: leave unchanged + } + } + else // nMaxLevel reached + if (nResult != DET_INS_INSERTED) + nResult = DET_INS_CONTINUE; + } + } + } + } + } + pFCell->SetRunning(bRunning); + } + + return nResult; +} + +sal_uInt16 ScDetectiveFunc::FindSuccLevel( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + sal_uInt16 nLevel, sal_uInt16 nDeleteLevel ) +{ + OSL_ENSURE( nLevel<1000, "Level" ); + + sal_uInt16 nResult = nLevel; + bool bDelete = ( nDeleteLevel && nLevel == nDeleteLevel-1 ); + + ScCellIterator aCellIter( rDoc, ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab) ); + for (bool bHas = aCellIter.first(); bHas; bHas = aCellIter.next()) + { + if (aCellIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = aCellIter.getFormulaCell(); + bool bRunning = pFCell->IsRunning(); + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + ScDetectiveRefIter aIter(rDoc, pFCell); + 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 ) // arrows, that are starting here + { + 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(), false ); + } + else if ( !bRunning && + HasArrow( aRef.aStart, + aCellIter.GetPos().Col(),aCellIter.GetPos().Row(),aCellIter.GetPos().Tab() ) ) + { + sal_uInt16 nTemp = FindSuccLevel( aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), + aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), + nLevel+1, nDeleteLevel ); + if (nTemp > nResult) + nResult = nTemp; + } + } + } + } + + pFCell->SetRunning(bRunning); + } + + return nResult; +} + +bool ScDetectiveFunc::ShowPred( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return 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 ); +} + +bool ScDetectiveFunc::ShowSucc( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return 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 ); +} + +bool ScDetectiveFunc::ShowError( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + ScRange aRange( nCol, nRow, nTab ); + ScAddress aErrPos; + if ( !HasError( aRange,aErrPos ) ) + return false; + + ScDetectiveData aData( pModel ); + + aData.SetMaxLevel( 1000 ); + sal_uInt16 nResult = InsertErrorLevel( nCol, nRow, aData, 0 ); + + return ( nResult == DET_INS_INSERTED ); +} + +bool ScDetectiveFunc::DeleteSucc( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + sal_uInt16 nLevelCount = FindSuccLevel( nCol, nRow, nCol, nRow, 0, 0 ); + if ( nLevelCount ) + FindSuccLevel( nCol, nRow, nCol, nRow, 0, nLevelCount ); // delete + + return ( nLevelCount != 0 ); +} + +bool ScDetectiveFunc::DeletePred( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + sal_uInt16 nLevelCount = FindPredLevel( nCol, nRow, 0, 0 ); + if ( nLevelCount ) + FindPredLevel( nCol, nRow, 0, nLevelCount ); // delete + + return ( nLevelCount != 0 ); +} + +bool ScDetectiveFunc::DeleteCirclesAt( SCCOL nCol, SCROW nRow ) +{ + tools::Rectangle aRect = ScDrawLayer::GetCellRect(rDoc, ScAddress(nCol, nRow, nTab), true); + aRect.AdjustLeft(-250); + aRect.AdjustRight(250); + aRect.AdjustTop(-70); + aRect.AdjustBottom(70); + + Point aStartCorner = aRect.TopLeft(); + Point aEndCorner = aRect.BottomRight(); + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage, "Page ?"); + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + size_t nDelCount = 0; + if (nObjCount) + { + std::unique_ptr<SdrObject*[]> ppObj(new SdrObject*[nObjCount]); + + SdrObjListIter aIter(pPage, SdrIterMode::Flat); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if (pObject->GetLayer() == SC_LAYER_INTERN) + if (auto pSdrCircObj = dynamic_cast<const SdrCircObj*>(pObject) ) + { + tools::Rectangle aObjRect = pSdrCircObj->GetLogicRect(); + if (RectIsPoints(aObjRect, aStartCorner, aEndCorner)) + ppObj[nDelCount++] = pObject; + } + + pObject = aIter.Next(); + } + + for (size_t i = 1; i <= nDelCount; ++i) + pModel->AddCalcUndo(std::make_unique<SdrUndoRemoveObj>(*ppObj[nDelCount - i])); + + for (size_t i = 1; i <= nDelCount; ++i) + pPage->RemoveObject(ppObj[nDelCount - i]->GetOrdNum()); + + ppObj.reset(); + + Modified(); + } + + return (nDelCount != 0); +} + +bool ScDetectiveFunc::DeleteAll( ScDetectiveDelete eWhat ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + pPage->RecalcObjOrdNums(); + + size_t nDelCount = 0; + const size_t nObjCount = pPage->GetObjCount(); + if (nObjCount) + { + std::unique_ptr<SdrObject*[]> ppObj(new SdrObject*[nObjCount]); + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetLayer() == SC_LAYER_INTERN ) + { + bool bDoThis = true; + bool bCircle = ( dynamic_cast<const SdrCircObj*>( pObject) != nullptr ); + bool bCaption = ScDrawLayer::IsNoteCaption( pObject ); + if ( eWhat == ScDetectiveDelete::Detective ) // detective, from menu + bDoThis = !bCaption; // also circles + else if ( eWhat == ScDetectiveDelete::Circles ) // circles, if new created + bDoThis = bCircle; + else if ( eWhat == ScDetectiveDelete::Arrows ) // DetectiveRefresh + bDoThis = !bCaption && !bCircle; // don't include circles + else + { + OSL_FAIL("what?"); + } + if ( bDoThis ) + ppObj[nDelCount++] = pObject; + } + + pObject = aIter.Next(); + } + + for (size_t i=1; i<=nDelCount; ++i) + pModel->AddCalcUndo( std::make_unique<SdrUndoRemoveObj>( *ppObj[nDelCount-i] ) ); + + for (size_t i=1; i<=nDelCount; ++i) + pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() ); + + ppObj.reset(); + + Modified(); + } + + return ( nDelCount != 0 ); +} + +bool ScDetectiveFunc::MarkInvalid(bool& rOverflow) +{ + rOverflow = false; + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + bool bDeleted = DeleteAll( ScDetectiveDelete::Circles ); // just circles + + ScDetectiveData aData( pModel ); + tools::Long nInsCount = 0; + + // search for valid places + ScDocAttrIterator aAttrIter( rDoc, nTab, 0,0,rDoc.MaxCol(),rDoc.MaxRow() ); + SCCOL nCol; + SCROW nRow1; + SCROW nRow2; + const ScPatternAttr* pPattern = aAttrIter.GetNext( nCol, nRow1, nRow2 ); + while ( pPattern && nInsCount < SC_DET_MAXCIRCLE ) + { + sal_uLong nIndex = pPattern->GetItem(ATTR_VALIDDATA).GetValue(); + if (nIndex) + { + const ScValidationData* pData = rDoc.GetValidationEntry( nIndex ); + if ( pData ) + { + // pass cells in this area + + bool bMarkEmpty = !pData->IsIgnoreBlank(); + SCROW nNextRow = nRow1; + SCROW nRow; + ScCellIterator aCellIter( rDoc, ScRange(nCol, nRow1, nTab, nCol, nRow2, nTab) ); + for (bool bHas = aCellIter.first(); bHas && nInsCount < SC_DET_MAXCIRCLE; bHas = aCellIter.next()) + { + SCROW nCellRow = aCellIter.GetPos().Row(); + if ( bMarkEmpty ) + for ( nRow = nNextRow; nRow < nCellRow && nInsCount < SC_DET_MAXCIRCLE; nRow++ ) + { + if(!pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped()) + DrawCircle( nCol, nRow, aData ); + ++nInsCount; + } + ScRefCellValue aCell = aCellIter.getRefCellValue(); + if (!pData->IsDataValid(aCell, aCellIter.GetPos())) + { + if(!pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped()) + DrawCircle( nCol, nCellRow, aData ); + ++nInsCount; + } + nNextRow = nCellRow + 1; + } + if ( bMarkEmpty ) + for ( nRow = nNextRow; nRow <= nRow2 && nInsCount < SC_DET_MAXCIRCLE; nRow++ ) + { + if(!pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped()) + DrawCircle(nCol, nRow, aData); + ++nInsCount; + } + } + } + + pPattern = aAttrIter.GetNext( nCol, nRow1, nRow2 ); + } + + if ( nInsCount >= SC_DET_MAXCIRCLE ) + rOverflow = true; + + return ( bDeleted || nInsCount != 0 ); +} + +void ScDetectiveFunc::GetAllPreds(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + vector<ScTokenRef>& rRefTokens) +{ + ScCellIterator aIter(rDoc, ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab)); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = aIter.getFormulaCell(); + ScDetectiveRefIter aRefIter(rDoc, pFCell); + for (formula::FormulaToken* p = aRefIter.GetNextRefToken(); p; p = aRefIter.GetNextRefToken()) + { + ScTokenRef pRef(p->Clone()); + ScRefTokenHelper::join(&rDoc, rRefTokens, pRef, aIter.GetPos()); + } + } +} + +void ScDetectiveFunc::GetAllSuccs(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + vector<ScTokenRef>& rRefTokens) +{ + vector<ScTokenRef> aSrcRange; + aSrcRange.push_back( + ScRefTokenHelper::createRefToken(rDoc, ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab))); + + ScCellIterator aIter(rDoc, ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab)); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = aIter.getFormulaCell(); + ScDetectiveRefIter aRefIter(rDoc, pFCell); + for (formula::FormulaToken* p = aRefIter.GetNextRefToken(); p; p = aRefIter.GetNextRefToken()) + { + const ScAddress& aPos = aIter.GetPos(); + ScTokenRef pRef(p->Clone()); + if (ScRefTokenHelper::intersects(&rDoc, aSrcRange, pRef, aPos)) + { + // This address is absolute. + pRef = ScRefTokenHelper::createRefToken(rDoc, aPos); + ScRefTokenHelper::join(&rDoc, rRefTokens, pRef, ScAddress()); + } + } + } +} + +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 ) + { + SdrPage* pPage = pModel->GetPage( static_cast< sal_uInt16 >( nObjTab ) ); + OSL_ENSURE( pPage, "Page ?" ); + if( pPage ) + { + SdrObjListIter aIter( pPage, SdrIterMode::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... + OSL_ENSURE( pNote && (pNote->GetCaption() == pObject), "ScDetectiveFunc::UpdateAllComments - invalid cell note" ); + if( pNote ) + { + ScCommentData aData( rDoc, pModel ); + SfxItemSet aAttrColorSet = pObject->GetMergedItemSet(); + aAttrColorSet.Put( XFillColorItem( OUString(), 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 = rDoc.GetDrawLayer(); + if (!pModel) + return; + + for( SCTAB nObjTab = 0, nTabCount = rDoc.GetTableCount(); nObjTab < nTabCount; ++nObjTab ) + { + SdrPage* pPage = pModel->GetPage( static_cast< sal_uInt16 >( nObjTab ) ); + OSL_ENSURE( pPage, "Page ?" ); + if( pPage ) + { + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + for( SdrObject* pObject = aIter.Next(); pObject; pObject = aIter.Next() ) + { + if ( pObject->GetLayer() == SC_LAYER_INTERN ) + { + bool bArrow = false; + bool bError = false; + + ScAddress aPos; + ScRange aSource; + 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 = true; + else + bArrow = 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 = true; + else + bArrow = true; + } + else if ( eType == SC_DETOBJ_CIRCLE ) + { + // circles (error marks) are always red + + bError = true; + } + else if ( eType == SC_DETOBJ_NONE ) + { + // frame for area reference has no ObjType, always gets arrow color + + if ( dynamic_cast<const SdrRectObj*>( pObject) != nullptr && dynamic_cast<const SdrCaptionObj*>( pObject) == nullptr ) + { + bArrow = true; + } + } + + if ( bArrow || bError ) + { + Color nColor = ( bError ? GetErrorColor() : GetArrowColor() ); + pObject->SetMergedItem( XLineColorItem( OUString(), nColor ) ); + + // repaint only + pObject->ActionChanged(); + } + } + } + } + } +} + +void ScDetectiveFunc::FindFrameForObject( const 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 = rDoc.GetDrawLayer(); + if (!pModel) return; + + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (!pPage) return; + + // test if the object is a direct page member + if( !(pObject && pObject->getSdrPageFromSdrObject() && (pObject->getSdrPageFromSdrObject() == pObject->getParentSdrObjListFromSdrObject()->getSdrPageFromSdrObjList())) ) + return; + + // Is there a previous object? + const size_t nOrdNum = pObject->GetOrdNum(); + + if(nOrdNum <= 0) + return; + + SdrObject* pPrevObj = pPage->GetObj(nOrdNum - 1); + + if ( pPrevObj && pPrevObj->GetLayer() == SC_LAYER_INTERN && dynamic_cast<const SdrRectObj*>( pPrevObj) != nullptr ) + { + 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; + } + } +} + +ScDetectiveObjType ScDetectiveFunc::GetDetectiveObjectType( SdrObject* pObject, SCTAB nObjTab, + ScAddress& rPosition, ScRange& rSource, bool& rRedLine ) +{ + rRedLine = 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 + } + + Color nObjColor = pObject->GetMergedItem(XATTR_LINECOLOR).GetColorValue(); + if ( nObjColor == GetErrorColor() && nObjColor != GetArrowColor() ) + rRedLine = true; + } + else if (dynamic_cast<const SdrCircObj*>(pObject) != nullptr) + { + if (bValidStart) + { + // cell position is returned in rPosition + rPosition = pData->maStart; + eType = SC_DETOBJ_CIRCLE; + } + } + else if (dynamic_cast<const SdrRectObj*>(pObject) != nullptr) + { + if (bValidStart) + { + // cell position is returned in rPosition + rPosition = pData->maStart; + eType = SC_DETOBJ_RECTANGLE; + } + } + } + } + + return eType; +} + +void ScDetectiveFunc::InsertObject( ScDetectiveObjType eType, + const ScAddress& rPosition, const ScRange& rSource, + bool bRedLine ) +{ + ScDrawLayer* pModel = rDoc.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 + } + } +} + +Color ScDetectiveFunc::GetArrowColor() +{ + if (!bColorsInitialized) + InitializeColors(); + return nArrowColor; +} + +Color ScDetectiveFunc::GetErrorColor() +{ + if (!bColorsInitialized) + InitializeColors(); + return nErrorColor; +} + +Color ScDetectiveFunc::GetCommentColor() +{ + if (!bColorsInitialized) + InitializeColors(); + return nCommentColor; +} + +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 = true; +} + +bool ScDetectiveFunc::IsColorsInitialized() +{ + return bColorsInitialized; +} + +void ScDetectiveFunc::AppendChangTrackNoteSeparator(OUString &rDisplay) +{ + rDisplay += "\n--------\n"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |