xref: /trunk/main/sc/source/core/tool/detfunc.cxx (revision 1ecadb572e7010ff3b3382ad9bf179dbc6efadbb)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_sc.hxx"
30 
31 // INCLUDE ---------------------------------------------------------------
32 
33 #include "scitems.hxx"
34 #include <svtools/colorcfg.hxx>
35 #include <editeng/eeitem.hxx>
36 #include <editeng/outlobj.hxx>
37 #include <svx/sdshitm.hxx>
38 #include <svx/sdsxyitm.hxx>
39 #include <svx/sdtditm.hxx>
40 #include <svx/svditer.hxx>
41 #include <svx/svdocapt.hxx>
42 #include <svx/svdocirc.hxx>
43 #include <svx/svdopath.hxx>
44 #include <svx/svdorect.hxx>
45 #include <svx/svdpage.hxx>
46 #include <svx/svdundo.hxx>
47 #include <svx/xfillit0.hxx>
48 #include <svx/xflclit.hxx>
49 #include <svx/xlnclit.hxx>
50 #include <svx/xlnedcit.hxx>
51 #include <svx/xlnedit.hxx>
52 #include <svx/xlnedwit.hxx>
53 #include <svx/xlnstcit.hxx>
54 #include <svx/xlnstit.hxx>
55 #include <svx/xlnstwit.hxx>
56 #include <svx/xlnwtit.hxx>
57 #include <svx/xtable.hxx>
58 #include <editeng/outliner.hxx>
59 #include <editeng/editobj.hxx>
60 #include <svx/sxcecitm.hxx>
61 #include <svl/whiter.hxx>
62 #include <editeng/writingmodeitem.hxx>
63 
64 #include <basegfx/point/b2dpoint.hxx>
65 #include <basegfx/polygon/b2dpolygontools.hxx>
66 #include <basegfx/polygon/b2dpolygon.hxx>
67 
68 #include "detfunc.hxx"
69 #include "document.hxx"
70 #include "dociter.hxx"
71 #include "drwlayer.hxx"
72 #include "userdat.hxx"
73 #include "validat.hxx"
74 #include "cell.hxx"
75 #include "docpool.hxx"
76 #include "patattr.hxx"
77 #include "attrib.hxx"
78 #include "scmod.hxx"
79 #include "postit.hxx"
80 
81 //------------------------------------------------------------------------
82 
83 // #99319# line ends are now created with an empty name.
84 // The checkForUniqueItem method then finds a unique name for the item's value.
85 #define SC_LINEEND_NAME     EMPTY_STRING
86 
87 //------------------------------------------------------------------------
88 
89 enum DetInsertResult {              // Return-Werte beim Einfuegen in einen Level
90             DET_INS_CONTINUE,
91             DET_INS_INSERTED,
92             DET_INS_EMPTY,
93             DET_INS_CIRCULAR };
94 
95 
96 //------------------------------------------------------------------------
97 
98 class ScDetectiveData
99 {
100 private:
101     SfxItemSet  aBoxSet;
102     SfxItemSet  aArrowSet;
103     SfxItemSet  aToTabSet;
104     SfxItemSet  aFromTabSet;
105     SfxItemSet  aCircleSet;         //! einzeln ?
106     sal_uInt16      nMaxLevel;
107 
108 public:
109                 ScDetectiveData( SdrModel* pModel );
110 
111     SfxItemSet& GetBoxSet()     { return aBoxSet; }
112     SfxItemSet& GetArrowSet()   { return aArrowSet; }
113     SfxItemSet& GetToTabSet()   { return aToTabSet; }
114     SfxItemSet& GetFromTabSet() { return aFromTabSet; }
115     SfxItemSet& GetCircleSet()  { return aCircleSet; }
116 
117     void        SetMaxLevel( sal_uInt16 nVal )      { nMaxLevel = nVal; }
118     sal_uInt16      GetMaxLevel() const             { return nMaxLevel; }
119 };
120 
121 class ScCommentData
122 {
123 public:
124                         ScCommentData( ScDocument& rDoc, SdrModel* pModel );
125 
126     SfxItemSet&         GetCaptionSet() { return aCaptionSet; }
127     void                UpdateCaptionSet( const SfxItemSet& rItemSet );
128 
129 private:
130     SfxItemSet          aCaptionSet;
131 };
132 
133 //------------------------------------------------------------------------
134 
135 ColorData ScDetectiveFunc::nArrowColor = 0;
136 ColorData ScDetectiveFunc::nErrorColor = 0;
137 ColorData ScDetectiveFunc::nCommentColor = 0;
138 sal_Bool ScDetectiveFunc::bColorsInitialized = sal_False;
139 
140 //------------------------------------------------------------------------
141 
142 sal_Bool lcl_HasThickLine( SdrObject& rObj )
143 {
144     // thin lines get width 0 -> everything greater 0 is a thick line
145 
146     return ( ((const XLineWidthItem&)rObj.GetMergedItem(XATTR_LINEWIDTH)).GetValue() > 0 );
147 }
148 
149 //------------------------------------------------------------------------
150 
151 ScDetectiveData::ScDetectiveData( SdrModel* pModel ) :
152     aBoxSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END ),
153     aArrowSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END ),
154     aToTabSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END ),
155     aFromTabSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END ),
156     aCircleSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END )
157 {
158     nMaxLevel = 0;
159 
160     aBoxSet.Put( XLineColorItem( EMPTY_STRING, Color( ScDetectiveFunc::GetArrowColor() ) ) );
161     aBoxSet.Put( XFillStyleItem( XFILL_NONE ) );
162 
163     //  #66479# Standard-Linienenden (wie aus XLineEndList::Create) selber zusammenbasteln,
164     //  um von den konfigurierten Linienenden unabhaengig zu sein
165 
166     basegfx::B2DPolygon aTriangle;
167     aTriangle.append(basegfx::B2DPoint(10.0, 0.0));
168     aTriangle.append(basegfx::B2DPoint(0.0, 30.0));
169     aTriangle.append(basegfx::B2DPoint(20.0, 30.0));
170     aTriangle.setClosed(true);
171 
172     basegfx::B2DPolygon aSquare;
173     aSquare.append(basegfx::B2DPoint(0.0, 0.0));
174     aSquare.append(basegfx::B2DPoint(10.0, 0.0));
175     aSquare.append(basegfx::B2DPoint(10.0, 10.0));
176     aSquare.append(basegfx::B2DPoint(0.0, 10.0));
177     aSquare.setClosed(true);
178 
179     basegfx::B2DPolygon aCircle(basegfx::tools::createPolygonFromEllipse(basegfx::B2DPoint(0.0, 0.0), 100.0, 100.0));
180     aCircle.setClosed(true);
181 
182     String aName = SC_LINEEND_NAME;
183 
184     aArrowSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aCircle) ) );
185     aArrowSet.Put( XLineStartWidthItem( 200 ) );
186     aArrowSet.Put( XLineStartCenterItem( sal_True ) );
187     aArrowSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) );
188     aArrowSet.Put( XLineEndWidthItem( 200 ) );
189     aArrowSet.Put( XLineEndCenterItem( sal_False ) );
190 
191     aToTabSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aCircle) ) );
192     aToTabSet.Put( XLineStartWidthItem( 200 ) );
193     aToTabSet.Put( XLineStartCenterItem( sal_True ) );
194     aToTabSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aSquare) ) );
195     aToTabSet.Put( XLineEndWidthItem( 300 ) );
196     aToTabSet.Put( XLineEndCenterItem( sal_False ) );
197 
198     aFromTabSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aSquare) ) );
199     aFromTabSet.Put( XLineStartWidthItem( 300 ) );
200     aFromTabSet.Put( XLineStartCenterItem( sal_True ) );
201     aFromTabSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) );
202     aFromTabSet.Put( XLineEndWidthItem( 200 ) );
203     aFromTabSet.Put( XLineEndCenterItem( sal_False ) );
204 
205     aCircleSet.Put( XLineColorItem( String(), Color( ScDetectiveFunc::GetErrorColor() ) ) );
206     aCircleSet.Put( XFillStyleItem( XFILL_NONE ) );
207     sal_uInt16 nWidth = 55;     // 54 = 1 Pixel
208     aCircleSet.Put( XLineWidthItem( nWidth ) );
209 }
210 
211 ScCommentData::ScCommentData( ScDocument& rDoc, SdrModel* pModel ) :
212     aCaptionSet( pModel->GetItemPool(), SDRATTR_START, SDRATTR_END, EE_ITEMS_START, EE_ITEMS_END, 0, 0 )
213 {
214     basegfx::B2DPolygon aTriangle;
215     aTriangle.append(basegfx::B2DPoint(10.0, 0.0));
216     aTriangle.append(basegfx::B2DPoint(0.0, 30.0));
217     aTriangle.append(basegfx::B2DPoint(20.0, 30.0));
218     aTriangle.setClosed(true);
219 
220     String aName = SC_LINEEND_NAME;
221 
222     aCaptionSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) );
223     aCaptionSet.Put( XLineStartWidthItem( 200 ) );
224     aCaptionSet.Put( XLineStartCenterItem( sal_False ) );
225     aCaptionSet.Put( XFillStyleItem( XFILL_SOLID ) );
226     Color aYellow( ScDetectiveFunc::GetCommentColor() );
227     aCaptionSet.Put( XFillColorItem( String(), aYellow ) );
228 
229     //  shadow
230     //  SdrShadowItem has sal_False, instead the shadow is set for the rectangle
231     //  only with SetSpecialTextBoxShadow when the object is created
232     //  (item must be set to adjust objects from older files)
233     aCaptionSet.Put( SdrShadowItem( sal_False ) );
234     aCaptionSet.Put( SdrShadowXDistItem( 100 ) );
235     aCaptionSet.Put( SdrShadowYDistItem( 100 ) );
236 
237     //  text attributes
238     aCaptionSet.Put( SdrTextLeftDistItem( 100 ) );
239     aCaptionSet.Put( SdrTextRightDistItem( 100 ) );
240     aCaptionSet.Put( SdrTextUpperDistItem( 100 ) );
241     aCaptionSet.Put( SdrTextLowerDistItem( 100 ) );
242 
243     aCaptionSet.Put( SdrTextAutoGrowWidthItem( sal_False ) );
244     aCaptionSet.Put( SdrTextAutoGrowHeightItem( sal_True ) );
245 
246     //  #78943# do use the default cell style, so the user has a chance to
247     //  modify the font for the annotations
248     ((const ScPatternAttr&)rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN)).
249         FillEditItemSet( &aCaptionSet );
250 
251     // support the best position for the tail connector now that
252     // that notes can be resized and repositioned.
253     aCaptionSet.Put( SdrCaptionEscDirItem( SDRCAPT_ESCBESTFIT) );
254 }
255 
256 void ScCommentData::UpdateCaptionSet( const SfxItemSet& rItemSet )
257 {
258     SfxWhichIter aWhichIter( rItemSet );
259     const SfxPoolItem* pPoolItem = 0;
260 
261     for( sal_uInt16 nWhich = aWhichIter.FirstWhich(); nWhich > 0; nWhich = aWhichIter.NextWhich() )
262     {
263         if(rItemSet.GetItemState(nWhich, sal_False, &pPoolItem) == SFX_ITEM_SET)
264         {
265             switch(nWhich)
266             {
267                 case SDRATTR_SHADOW:
268                     // use existing Caption default - appears that setting this
269                     // to true screws up the tail appearance. See also comment
270                     // for default setting above.
271                 break;
272                 case SDRATTR_SHADOWXDIST:
273                     // use existing Caption default - svx sets a value of 35
274                     // but default 100 gives a better appearance.
275                 break;
276                 case SDRATTR_SHADOWYDIST:
277                     // use existing Caption default - svx sets a value of 35
278                     // but default 100 gives a better appearance.
279                 break;
280 
281                 default:
282                     aCaptionSet.Put(*pPoolItem);
283            }
284         }
285     }
286 }
287 
288 //------------------------------------------------------------------------
289 
290 void ScDetectiveFunc::Modified()
291 {
292     if (pDoc->IsStreamValid(nTab))
293         pDoc->SetStreamValid(nTab, sal_False);
294 }
295 
296 inline sal_Bool Intersect( SCCOL nStartCol1, SCROW nStartRow1, SCCOL nEndCol1, SCROW nEndRow1,
297                         SCCOL nStartCol2, SCROW nStartRow2, SCCOL nEndCol2, SCROW nEndRow2 )
298 {
299     return nEndCol1 >= nStartCol2 && nEndCol2 >= nStartCol1 &&
300             nEndRow1 >= nStartRow2 && nEndRow2 >= nStartRow1;
301 }
302 
303 sal_Bool ScDetectiveFunc::HasError( const ScRange& rRange, ScAddress& rErrPos )
304 {
305     rErrPos = rRange.aStart;
306     sal_uInt16 nError = 0;
307 
308     ScCellIterator aCellIter( pDoc, rRange);
309     ScBaseCell* pCell = aCellIter.GetFirst();
310     while (pCell)
311     {
312         if (pCell->GetCellType() == CELLTYPE_FORMULA)
313         {
314             nError = ((ScFormulaCell*)pCell)->GetErrCode();
315             if (nError)
316                 rErrPos.Set( aCellIter.GetCol(), aCellIter.GetRow(), aCellIter.GetTab() );
317         }
318         pCell = aCellIter.GetNext();
319     }
320 
321     return (nError != 0);
322 }
323 
324 Point ScDetectiveFunc::GetDrawPos( SCCOL nCol, SCROW nRow, DrawPosMode eMode ) const
325 {
326     DBG_ASSERT( ValidColRow( nCol, nRow ), "ScDetectiveFunc::GetDrawPos - invalid cell address" );
327     SanitizeCol( nCol );
328     SanitizeRow( nRow );
329 
330     Point aPos;
331 
332     switch( eMode )
333     {
334         case DRAWPOS_TOPLEFT:
335         break;
336         case DRAWPOS_BOTTOMRIGHT:
337             ++nCol;
338             ++nRow;
339         break;
340         case DRAWPOS_DETARROW:
341             aPos.X() += pDoc->GetColWidth( nCol, nTab ) / 4;
342             aPos.Y() += pDoc->GetRowHeight( nRow, nTab ) / 2;
343         break;
344         case DRAWPOS_CAPTIONLEFT:
345             aPos.X() += 6;
346         break;
347         case DRAWPOS_CAPTIONRIGHT:
348         {
349             // find right end of passed cell position
350             const ScMergeAttr* pMerge = static_cast< const ScMergeAttr* >( pDoc->GetAttr( nCol, nRow, nTab, ATTR_MERGE ) );
351             if ( pMerge->GetColMerge() > 1 )
352                 nCol = nCol + pMerge->GetColMerge();
353             else
354                 ++nCol;
355             aPos.X() -= 6;
356         }
357         break;
358     }
359 
360     for ( SCCOL i = 0; i < nCol; ++i )
361         aPos.X() += pDoc->GetColWidth( i, nTab );
362     aPos.Y() += pDoc->GetRowHeight( 0, nRow - 1, nTab );
363 
364     aPos.X() = static_cast< long >( aPos.X() * HMM_PER_TWIPS );
365     aPos.Y() = static_cast< long >( aPos.Y() * HMM_PER_TWIPS );
366 
367     if ( pDoc->IsNegativePage( nTab ) )
368         aPos.X() *= -1;
369 
370     return aPos;
371 }
372 
373 Rectangle ScDetectiveFunc::GetDrawRect( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const
374 {
375     Rectangle aRect(
376         GetDrawPos( ::std::min( nCol1, nCol2 ), ::std::min( nRow1, nRow2 ), DRAWPOS_TOPLEFT ),
377         GetDrawPos( ::std::max( nCol1, nCol2 ), ::std::max( nRow1, nRow2 ), DRAWPOS_BOTTOMRIGHT ) );
378     aRect.Justify();    // reorder left/right in RTL sheets
379     return aRect;
380 }
381 
382 Rectangle ScDetectiveFunc::GetDrawRect( SCCOL nCol, SCROW nRow ) const
383 {
384     return GetDrawRect( nCol, nRow, nCol, nRow );
385 }
386 
387 sal_Bool lcl_IsOtherTab( const basegfx::B2DPolyPolygon& rPolyPolygon )
388 {
389     //  test if rPolygon is the line end for "other table" (rectangle)
390     if(1L == rPolyPolygon.count())
391     {
392         const basegfx::B2DPolygon aSubPoly(rPolyPolygon.getB2DPolygon(0L));
393 
394         // #i73305# circle consists of 4 segments, too, distinguishable from square by
395         // the use of control points
396         if(4L == aSubPoly.count() && aSubPoly.isClosed() && !aSubPoly.areControlPointsUsed())
397         {
398             return true;
399         }
400     }
401 
402     return false;
403 }
404 
405 sal_Bool ScDetectiveFunc::HasArrow( const ScAddress& rStart,
406                                     SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab )
407 {
408     sal_Bool bStartAlien = ( rStart.Tab() != nTab );
409     sal_Bool bEndAlien   = ( nEndTab != nTab );
410 
411     if (bStartAlien && bEndAlien)
412     {
413         DBG_ERROR("bStartAlien && bEndAlien");
414         return sal_True;
415     }
416 
417     Rectangle aStartRect;
418     Rectangle aEndRect;
419     if (!bStartAlien)
420         aStartRect = GetDrawRect( rStart.Col(), rStart.Row() );
421     if (!bEndAlien)
422         aEndRect = GetDrawRect( nEndCol, nEndRow );
423 
424     ScDrawLayer* pModel = pDoc->GetDrawLayer();
425     SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
426     DBG_ASSERT(pPage,"Page ?");
427 
428     sal_Bool bFound = sal_False;
429     SdrObjListIter aIter( *pPage, IM_FLAT );
430     SdrObject* pObject = aIter.Next();
431     while (pObject && !bFound)
432     {
433         if ( pObject->GetLayer()==SC_LAYER_INTERN &&
434                 pObject->IsPolyObj() && pObject->GetPointCount()==2 )
435         {
436             const SfxItemSet& rSet = pObject->GetMergedItemSet();
437 
438             sal_Bool bObjStartAlien =
439                 lcl_IsOtherTab( ((const XLineStartItem&)rSet.Get(XATTR_LINESTART)).GetLineStartValue() );
440             sal_Bool bObjEndAlien =
441                 lcl_IsOtherTab( ((const XLineEndItem&)rSet.Get(XATTR_LINEEND)).GetLineEndValue() );
442 
443             sal_Bool bStartHit = bStartAlien ? bObjStartAlien :
444                                 ( !bObjStartAlien && aStartRect.IsInside(pObject->GetPoint(0)) );
445             sal_Bool bEndHit = bEndAlien ? bObjEndAlien :
446                                 ( !bObjEndAlien && aEndRect.IsInside(pObject->GetPoint(1)) );
447 
448             if ( bStartHit && bEndHit )
449                 bFound = sal_True;
450         }
451         pObject = aIter.Next();
452     }
453 
454     return bFound;
455 }
456 
457 sal_Bool ScDetectiveFunc::IsNonAlienArrow( SdrObject* pObject )         // static
458 {
459     if ( pObject->GetLayer()==SC_LAYER_INTERN &&
460             pObject->IsPolyObj() && pObject->GetPointCount()==2 )
461     {
462         const SfxItemSet& rSet = pObject->GetMergedItemSet();
463 
464         sal_Bool bObjStartAlien =
465             lcl_IsOtherTab( ((const XLineStartItem&)rSet.Get(XATTR_LINESTART)).GetLineStartValue() );
466         sal_Bool bObjEndAlien =
467             lcl_IsOtherTab( ((const XLineEndItem&)rSet.Get(XATTR_LINEEND)).GetLineEndValue() );
468 
469         return !bObjStartAlien && !bObjEndAlien;
470     }
471 
472     return sal_False;
473 }
474 
475 //------------------------------------------------------------------------
476 
477 //  InsertXXX: called from DrawEntry/DrawAlienEntry and InsertObject
478 
479 sal_Bool ScDetectiveFunc::InsertArrow( SCCOL nCol, SCROW nRow,
480                                 SCCOL nRefStartCol, SCROW nRefStartRow,
481                                 SCCOL nRefEndCol, SCROW nRefEndRow,
482                                 sal_Bool bFromOtherTab, sal_Bool bRed,
483                                 ScDetectiveData& rData )
484 {
485     ScDrawLayer* pModel = pDoc->GetDrawLayer();
486     SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
487 
488     sal_Bool bArea = ( nRefStartCol != nRefEndCol || nRefStartRow != nRefEndRow );
489     if (bArea && !bFromOtherTab)
490     {
491         // insert the rectangle before the arrow - this is relied on in FindFrameForObject
492 
493         Rectangle aRect = GetDrawRect( nRefStartCol, nRefStartRow, nRefEndCol, nRefEndRow );
494         SdrRectObj* pBox = new SdrRectObj( aRect );
495 
496         pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet());
497 
498         ScDrawLayer::SetAnchor( pBox, SCA_CELL );
499         pBox->SetLayer( SC_LAYER_INTERN );
500         pPage->InsertObject( pBox );
501         pModel->AddCalcUndo( new SdrUndoInsertObj( *pBox ) );
502 
503         ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox, sal_True );
504         pData->maStart.Set( nRefStartCol, nRefStartRow, nTab);
505         pData->maEnd.Set( nRefEndCol, nRefEndRow, nTab);
506     }
507 
508     Point aStartPos = GetDrawPos( nRefStartCol, nRefStartRow, DRAWPOS_DETARROW );
509     Point aEndPos = GetDrawPos( nCol, nRow, DRAWPOS_DETARROW );
510 
511     if (bFromOtherTab)
512     {
513         sal_Bool bNegativePage = pDoc->IsNegativePage( nTab );
514         long nPageSign = bNegativePage ? -1 : 1;
515 
516         aStartPos = Point( aEndPos.X() - 1000 * nPageSign, aEndPos.Y() - 1000 );
517         if (aStartPos.X() * nPageSign < 0)
518             aStartPos.X() += 2000 * nPageSign;
519         if (aStartPos.Y() < 0)
520             aStartPos.Y() += 2000;
521     }
522 
523     SfxItemSet& rAttrSet = bFromOtherTab ? rData.GetFromTabSet() : rData.GetArrowSet();
524 
525     if (bArea && !bFromOtherTab)
526         rAttrSet.Put( XLineWidthItem( 50 ) );               // Bereich
527     else
528         rAttrSet.Put( XLineWidthItem( 0 ) );                // einzelne Referenz
529 
530     ColorData nColorData = ( bRed ? GetErrorColor() : GetArrowColor() );
531     rAttrSet.Put( XLineColorItem( String(), Color( nColorData ) ) );
532 
533     basegfx::B2DPolygon aTempPoly;
534     aTempPoly.append(basegfx::B2DPoint(aStartPos.X(), aStartPos.Y()));
535     aTempPoly.append(basegfx::B2DPoint(aEndPos.X(), aEndPos.Y()));
536     SdrPathObj* pArrow = new SdrPathObj(OBJ_LINE, basegfx::B2DPolyPolygon(aTempPoly));
537     pArrow->NbcSetLogicRect(Rectangle(aStartPos,aEndPos));  //! noetig ???
538     pArrow->SetMergedItemSetAndBroadcast(rAttrSet);
539 
540     ScDrawLayer::SetAnchor( pArrow, SCA_CELL );
541     pArrow->SetLayer( SC_LAYER_INTERN );
542     pPage->InsertObject( pArrow );
543     pModel->AddCalcUndo( new SdrUndoInsertObj( *pArrow ) );
544 
545     ScDrawObjData* pData = ScDrawLayer::GetObjData( pArrow, sal_True );
546     if (bFromOtherTab)
547         pData->maStart.SetInvalid();
548     else
549         pData->maStart.Set( nRefStartCol, nRefStartRow, nTab);
550 
551     pData->maEnd.Set( nCol, nRow, nTab);
552 
553     Modified();
554     return sal_True;
555 }
556 
557 sal_Bool ScDetectiveFunc::InsertToOtherTab( SCCOL nStartCol, SCROW nStartRow,
558                                 SCCOL nEndCol, SCROW nEndRow, sal_Bool bRed,
559                                 ScDetectiveData& rData )
560 {
561     ScDrawLayer* pModel = pDoc->GetDrawLayer();
562     SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
563 
564     sal_Bool bArea = ( nStartCol != nEndCol || nStartRow != nEndRow );
565     if (bArea)
566     {
567         Rectangle aRect = GetDrawRect( nStartCol, nStartRow, nEndCol, nEndRow );
568         SdrRectObj* pBox = new SdrRectObj( aRect );
569 
570         pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet());
571 
572         ScDrawLayer::SetAnchor( pBox, SCA_CELL );
573         pBox->SetLayer( SC_LAYER_INTERN );
574         pPage->InsertObject( pBox );
575         pModel->AddCalcUndo( new SdrUndoInsertObj( *pBox ) );
576 
577         ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox, sal_True );
578         pData->maStart.Set( nStartCol, nStartRow, nTab);
579         pData->maEnd.Set( nEndCol, nEndRow, nTab);
580     }
581 
582     sal_Bool bNegativePage = pDoc->IsNegativePage( nTab );
583     long nPageSign = bNegativePage ? -1 : 1;
584 
585     Point aStartPos = GetDrawPos( nStartCol, nStartRow, DRAWPOS_DETARROW );
586     Point aEndPos   = Point( aStartPos.X() + 1000 * nPageSign, aStartPos.Y() - 1000 );
587     if (aEndPos.Y() < 0)
588         aEndPos.Y() += 2000;
589 
590     SfxItemSet& rAttrSet = rData.GetToTabSet();
591     if (bArea)
592         rAttrSet.Put( XLineWidthItem( 50 ) );               // Bereich
593     else
594         rAttrSet.Put( XLineWidthItem( 0 ) );                // einzelne Referenz
595 
596     ColorData nColorData = ( bRed ? GetErrorColor() : GetArrowColor() );
597     rAttrSet.Put( XLineColorItem( String(), Color( nColorData ) ) );
598 
599     basegfx::B2DPolygon aTempPoly;
600     aTempPoly.append(basegfx::B2DPoint(aStartPos.X(), aStartPos.Y()));
601     aTempPoly.append(basegfx::B2DPoint(aEndPos.X(), aEndPos.Y()));
602     SdrPathObj* pArrow = new SdrPathObj(OBJ_LINE, basegfx::B2DPolyPolygon(aTempPoly));
603     pArrow->NbcSetLogicRect(Rectangle(aStartPos,aEndPos));  //! noetig ???
604 
605     pArrow->SetMergedItemSetAndBroadcast(rAttrSet);
606 
607     ScDrawLayer::SetAnchor( pArrow, SCA_CELL );
608     pArrow->SetLayer( SC_LAYER_INTERN );
609     pPage->InsertObject( pArrow );
610     pModel->AddCalcUndo( new SdrUndoInsertObj( *pArrow ) );
611 
612     ScDrawObjData* pData = ScDrawLayer::GetObjData( pArrow, sal_True );
613     pData->maStart.Set( nStartCol, nStartRow, nTab);
614     pData->maEnd.SetInvalid();
615 
616     Modified();
617     return sal_True;
618 }
619 
620 //------------------------------------------------------------------------
621 
622 //  DrawEntry:      Formel auf dieser Tabelle,
623 //                  Referenz auf dieser oder anderer
624 //  DrawAlienEntry: Formel auf anderer Tabelle,
625 //                  Referenz auf dieser
626 
627 //      return FALSE: da war schon ein Pfeil
628 
629 sal_Bool ScDetectiveFunc::DrawEntry( SCCOL nCol, SCROW nRow,
630                                     const ScRange& rRef,
631                                     ScDetectiveData& rData )
632 {
633     if ( HasArrow( rRef.aStart, nCol, nRow, nTab ) )
634         return sal_False;
635 
636     ScAddress aErrorPos;
637     sal_Bool bError = HasError( rRef, aErrorPos );
638     sal_Bool bAlien = ( rRef.aEnd.Tab() < nTab || rRef.aStart.Tab() > nTab );
639 
640     return InsertArrow( nCol, nRow,
641                         rRef.aStart.Col(), rRef.aStart.Row(),
642                         rRef.aEnd.Col(), rRef.aEnd.Row(),
643                         bAlien, bError, rData );
644 }
645 
646 sal_Bool ScDetectiveFunc::DrawAlienEntry( const ScRange& rRef,
647                                         ScDetectiveData& rData )
648 {
649     if ( HasArrow( rRef.aStart, 0, 0, nTab+1 ) )
650         return sal_False;
651 
652     ScAddress aErrorPos;
653     sal_Bool bError = HasError( rRef, aErrorPos );
654 
655     return InsertToOtherTab( rRef.aStart.Col(), rRef.aStart.Row(),
656                                 rRef.aEnd.Col(), rRef.aEnd.Row(),
657                                 bError, rData );
658 }
659 
660 void ScDetectiveFunc::DrawCircle( SCCOL nCol, SCROW nRow, ScDetectiveData& rData )
661 {
662     ScDrawLayer* pModel = pDoc->GetDrawLayer();
663     SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
664 
665     Rectangle aRect = GetDrawRect( nCol, nRow );
666     aRect.Left()    -= 250;
667     aRect.Right()   += 250;
668     aRect.Top()     -= 70;
669     aRect.Bottom()  += 70;
670 
671     SdrCircObj* pCircle = new SdrCircObj( OBJ_CIRC, aRect );
672     SfxItemSet& rAttrSet = rData.GetCircleSet();
673 
674     pCircle->SetMergedItemSetAndBroadcast(rAttrSet);
675 
676     ScDrawLayer::SetAnchor( pCircle, SCA_CELL );
677     pCircle->SetLayer( SC_LAYER_INTERN );
678     pPage->InsertObject( pCircle );
679     pModel->AddCalcUndo( new SdrUndoInsertObj( *pCircle ) );
680 
681     ScDrawObjData* pData = ScDrawLayer::GetObjData( pCircle, sal_True );
682     pData->maStart.Set( nCol, nRow, nTab);
683     pData->maEnd.SetInvalid();
684 
685     Modified();
686 }
687 
688 void ScDetectiveFunc::DeleteArrowsAt( SCCOL nCol, SCROW nRow, sal_Bool bDestPnt )
689 {
690     Rectangle aRect = GetDrawRect( nCol, nRow );
691 
692     ScDrawLayer* pModel = pDoc->GetDrawLayer();
693     SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
694     DBG_ASSERT(pPage,"Page ?");
695 
696     pPage->RecalcObjOrdNums();
697 
698     long    nDelCount = 0;
699     sal_uLong   nObjCount = pPage->GetObjCount();
700     if (nObjCount)
701     {
702         SdrObject** ppObj = new SdrObject*[nObjCount];
703 
704         SdrObjListIter aIter( *pPage, IM_FLAT );
705         SdrObject* pObject = aIter.Next();
706         while (pObject)
707         {
708             if ( pObject->GetLayer()==SC_LAYER_INTERN &&
709                     pObject->IsPolyObj() && pObject->GetPointCount()==2 )
710             {
711                 if (aRect.IsInside(pObject->GetPoint(bDestPnt)))            // Start/Zielpunkt
712                     ppObj[nDelCount++] = pObject;
713             }
714 
715             pObject = aIter.Next();
716         }
717 
718         long i;
719         for (i=1; i<=nDelCount; i++)
720             pModel->AddCalcUndo( new SdrUndoRemoveObj( *ppObj[nDelCount-i] ) );
721 
722         for (i=1; i<=nDelCount; i++)
723             pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() );
724 
725         delete[] ppObj;
726 
727         Modified();
728     }
729 }
730 
731         //      Box um Referenz loeschen
732 
733 #define SC_DET_TOLERANCE    50
734 
735 inline sal_Bool RectIsPoints( const Rectangle& rRect, const Point& rStart, const Point& rEnd )
736 {
737     return rRect.Left()   >= rStart.X() - SC_DET_TOLERANCE
738         && rRect.Left()   <= rStart.X() + SC_DET_TOLERANCE
739         && rRect.Right()  >= rEnd.X()   - SC_DET_TOLERANCE
740         && rRect.Right()  <= rEnd.X()   + SC_DET_TOLERANCE
741         && rRect.Top()    >= rStart.Y() - SC_DET_TOLERANCE
742         && rRect.Top()    <= rStart.Y() + SC_DET_TOLERANCE
743         && rRect.Bottom() >= rEnd.Y()   - SC_DET_TOLERANCE
744         && rRect.Bottom() <= rEnd.Y()   + SC_DET_TOLERANCE;
745 }
746 
747 #undef SC_DET_TOLERANCE
748 
749 void ScDetectiveFunc::DeleteBox( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
750 {
751 /*  String aStr;
752     aStr += nCol1;
753     aStr += '/';
754     aStr += nRow1;
755     aStr += '/';
756     aStr += nCol2;
757     aStr += '/';
758     aStr += nRow2;
759     InfoBox(0,aStr).Execute();
760 */
761 
762     Rectangle aCornerRect = GetDrawRect( nCol1, nRow1, nCol2, nRow2 );
763     Point aStartCorner = aCornerRect.TopLeft();
764     Point aEndCorner = aCornerRect.BottomRight();
765     Rectangle aObjRect;
766 
767     ScDrawLayer* pModel = pDoc->GetDrawLayer();
768     SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
769     DBG_ASSERT(pPage,"Page ?");
770 
771     pPage->RecalcObjOrdNums();
772 
773     long    nDelCount = 0;
774     sal_uLong   nObjCount = pPage->GetObjCount();
775     if (nObjCount)
776     {
777         SdrObject** ppObj = new SdrObject*[nObjCount];
778 
779         SdrObjListIter aIter( *pPage, IM_FLAT );
780         SdrObject* pObject = aIter.Next();
781         while (pObject)
782         {
783             if ( pObject->GetLayer() == SC_LAYER_INTERN &&
784                     pObject->Type() == TYPE(SdrRectObj) )
785             {
786                 aObjRect = ((SdrRectObj*)pObject)->GetLogicRect();
787                 aObjRect.Justify();
788                 if ( RectIsPoints( aObjRect, aStartCorner, aEndCorner ) )
789                     ppObj[nDelCount++] = pObject;
790             }
791 
792             pObject = aIter.Next();
793         }
794 
795         long i;
796         for (i=1; i<=nDelCount; i++)
797             pModel->AddCalcUndo( new SdrUndoRemoveObj( *ppObj[nDelCount-i] ) );
798 
799         for (i=1; i<=nDelCount; i++)
800             pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() );
801 
802         delete[] ppObj;
803 
804         Modified();
805     }
806 }
807 
808 //------------------------------------------------------------------------
809 
810 sal_uInt16 ScDetectiveFunc::InsertPredLevelArea( const ScRange& rRef,
811                                         ScDetectiveData& rData, sal_uInt16 nLevel )
812 {
813     sal_uInt16 nResult = DET_INS_EMPTY;
814 
815     ScCellIterator aCellIter( pDoc, rRef);
816     ScBaseCell* pCell = aCellIter.GetFirst();
817     while (pCell)
818     {
819         if (pCell->GetCellType() == CELLTYPE_FORMULA)
820             switch( InsertPredLevel( aCellIter.GetCol(), aCellIter.GetRow(), rData, nLevel ) )
821             {
822                 case DET_INS_INSERTED:
823                     nResult = DET_INS_INSERTED;
824                     break;
825                 case DET_INS_CONTINUE:
826                     if (nResult != DET_INS_INSERTED)
827                         nResult = DET_INS_CONTINUE;
828                     break;
829                 case DET_INS_CIRCULAR:
830                     if (nResult == DET_INS_EMPTY)
831                         nResult = DET_INS_CIRCULAR;
832                     break;
833             }
834 
835         pCell = aCellIter.GetNext();
836     }
837 
838     return nResult;
839 }
840 
841 sal_uInt16 ScDetectiveFunc::InsertPredLevel( SCCOL nCol, SCROW nRow, ScDetectiveData& rData,
842                                             sal_uInt16 nLevel )
843 {
844     ScBaseCell* pCell;
845     pDoc->GetCell( nCol, nRow, nTab, pCell );
846     if (!pCell)
847         return DET_INS_EMPTY;
848     if (pCell->GetCellType() != CELLTYPE_FORMULA)
849         return DET_INS_EMPTY;
850 
851     ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
852     if (pFCell->IsRunning())
853         return DET_INS_CIRCULAR;
854 
855     if (pFCell->GetDirty())
856         pFCell->Interpret();                // nach SetRunning geht's nicht mehr!
857     pFCell->SetRunning(sal_True);
858 
859     sal_uInt16 nResult = DET_INS_EMPTY;
860 
861     ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
862     ScRange aRef;
863     while ( aIter.GetNextRef( aRef ) )
864     {
865         if (DrawEntry( nCol, nRow, aRef, rData ))
866         {
867             nResult = DET_INS_INSERTED;         //  neuer Pfeil eingetragen
868         }
869         else
870         {
871             //  weiterverfolgen
872 
873             if ( nLevel < rData.GetMaxLevel() )
874             {
875                 sal_uInt16 nSubResult;
876                 sal_Bool bArea = (aRef.aStart != aRef.aEnd);
877                 if (bArea)
878                     nSubResult = InsertPredLevelArea( aRef, rData, nLevel+1 );
879                 else
880                     nSubResult = InsertPredLevel( aRef.aStart.Col(), aRef.aStart.Row(),
881                                                     rData, nLevel+1 );
882 
883                 switch (nSubResult)
884                 {
885                     case DET_INS_INSERTED:
886                         nResult = DET_INS_INSERTED;
887                         break;
888                     case DET_INS_CONTINUE:
889                         if (nResult != DET_INS_INSERTED)
890                             nResult = DET_INS_CONTINUE;
891                         break;
892                     case DET_INS_CIRCULAR:
893                         if (nResult == DET_INS_EMPTY)
894                             nResult = DET_INS_CIRCULAR;
895                         break;
896                     // DET_INS_EMPTY: unveraendert lassen
897                 }
898             }
899             else                                    //  nMaxLevel erreicht
900                 if (nResult != DET_INS_INSERTED)
901                     nResult = DET_INS_CONTINUE;
902         }
903     }
904 
905     pFCell->SetRunning(sal_False);
906 
907     return nResult;
908 }
909 
910 sal_uInt16 ScDetectiveFunc::FindPredLevelArea( const ScRange& rRef,
911                                                 sal_uInt16 nLevel, sal_uInt16 nDeleteLevel )
912 {
913     sal_uInt16 nResult = nLevel;
914 
915     ScCellIterator aCellIter( pDoc, rRef);
916     ScBaseCell* pCell = aCellIter.GetFirst();
917     while (pCell)
918     {
919         if (pCell->GetCellType() == CELLTYPE_FORMULA)
920         {
921             sal_uInt16 nTemp = FindPredLevel( aCellIter.GetCol(), aCellIter.GetRow(), nLevel, nDeleteLevel );
922             if (nTemp > nResult)
923                 nResult = nTemp;
924         }
925         pCell = aCellIter.GetNext();
926     }
927 
928     return nResult;
929 }
930 
931                                             //  nDeleteLevel != 0   -> loeschen
932 
933 sal_uInt16 ScDetectiveFunc::FindPredLevel( SCCOL nCol, SCROW nRow, sal_uInt16 nLevel, sal_uInt16 nDeleteLevel )
934 {
935     DBG_ASSERT( nLevel<1000, "Level" );
936 
937     ScBaseCell* pCell;
938     pDoc->GetCell( nCol, nRow, nTab, pCell );
939     if (!pCell)
940         return nLevel;
941     if (pCell->GetCellType() != CELLTYPE_FORMULA)
942         return nLevel;
943 
944     ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
945     if (pFCell->IsRunning())
946         return nLevel;
947 
948     if (pFCell->GetDirty())
949         pFCell->Interpret();                // nach SetRunning geht's nicht mehr!
950     pFCell->SetRunning(sal_True);
951 
952     sal_uInt16 nResult = nLevel;
953     sal_Bool bDelete = ( nDeleteLevel && nLevel == nDeleteLevel-1 );
954 
955     if ( bDelete )
956     {
957         DeleteArrowsAt( nCol, nRow, sal_True );                 // Pfeile, die hierher zeigen
958     }
959 
960     ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
961     ScRange aRef;
962     while ( aIter.GetNextRef( aRef) )
963     {
964         sal_Bool bArea = ( aRef.aStart != aRef.aEnd );
965 
966         if ( bDelete )                  // Rahmen loeschen ?
967         {
968             if (bArea)
969             {
970                 DeleteBox( aRef.aStart.Col(), aRef.aStart.Row(), aRef.aEnd.Col(), aRef.aEnd.Row() );
971             }
972         }
973         else                            // weitersuchen
974         {
975             if ( HasArrow( aRef.aStart, nCol,nRow,nTab ) )
976             {
977                 sal_uInt16 nTemp;
978                 if (bArea)
979                     nTemp = FindPredLevelArea( aRef, nLevel+1, nDeleteLevel );
980                 else
981                     nTemp = FindPredLevel( aRef.aStart.Col(),aRef.aStart.Row(),
982                                                         nLevel+1, nDeleteLevel );
983                 if (nTemp > nResult)
984                     nResult = nTemp;
985             }
986         }
987     }
988 
989     pFCell->SetRunning(sal_False);
990 
991     return nResult;
992 }
993 
994 //------------------------------------------------------------------------
995 
996 sal_uInt16 ScDetectiveFunc::InsertErrorLevel( SCCOL nCol, SCROW nRow, ScDetectiveData& rData,
997                                             sal_uInt16 nLevel )
998 {
999     ScBaseCell* pCell;
1000     pDoc->GetCell( nCol, nRow, nTab, pCell );
1001     if (!pCell)
1002         return DET_INS_EMPTY;
1003     if (pCell->GetCellType() != CELLTYPE_FORMULA)
1004         return DET_INS_EMPTY;
1005 
1006     ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
1007     if (pFCell->IsRunning())
1008         return DET_INS_CIRCULAR;
1009 
1010     if (pFCell->GetDirty())
1011         pFCell->Interpret();                // nach SetRunning geht's nicht mehr!
1012     pFCell->SetRunning(sal_True);
1013 
1014     sal_uInt16 nResult = DET_INS_EMPTY;
1015 
1016     ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
1017     ScRange aRef;
1018     ScAddress aErrorPos;
1019     sal_Bool bHasError = sal_False;
1020     while ( aIter.GetNextRef( aRef ) )
1021     {
1022         if (HasError( aRef, aErrorPos ))
1023         {
1024             bHasError = sal_True;
1025             if (DrawEntry( nCol, nRow, ScRange( aErrorPos), rData ))
1026                 nResult = DET_INS_INSERTED;
1027 
1028             //  und weiterverfolgen
1029 
1030             if ( nLevel < rData.GetMaxLevel() )         // praktisch immer
1031             {
1032                 if (InsertErrorLevel( aErrorPos.Col(), aErrorPos.Row(),
1033                                                         rData, nLevel+1 ) == DET_INS_INSERTED)
1034                     nResult = DET_INS_INSERTED;
1035             }
1036         }
1037     }
1038 
1039     pFCell->SetRunning(sal_False);
1040 
1041                                                     // Blaetter ?
1042     if (!bHasError)
1043         if (InsertPredLevel( nCol, nRow, rData, rData.GetMaxLevel() ) == DET_INS_INSERTED)
1044             nResult = DET_INS_INSERTED;
1045 
1046     return nResult;
1047 }
1048 
1049 //------------------------------------------------------------------------
1050 
1051 sal_uInt16 ScDetectiveFunc::InsertSuccLevel( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
1052                                         ScDetectiveData& rData, sal_uInt16 nLevel )
1053 {
1054     //  ueber ganzes Dokument
1055 
1056     sal_uInt16 nResult = DET_INS_EMPTY;
1057 //  ScCellIterator aCellIter( pDoc, 0,0, nTab, MAXCOL,MAXROW, nTab );
1058     ScCellIterator aCellIter( pDoc, 0,0,0, MAXCOL,MAXROW,MAXTAB );          // alle Tabellen
1059     ScBaseCell* pCell = aCellIter.GetFirst();
1060     while (pCell)
1061     {
1062         if (pCell->GetCellType() == CELLTYPE_FORMULA)
1063         {
1064             ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
1065             sal_Bool bRunning = pFCell->IsRunning();
1066 
1067             if (pFCell->GetDirty())
1068                 pFCell->Interpret();                // nach SetRunning geht's nicht mehr!
1069             pFCell->SetRunning(sal_True);
1070 
1071             ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
1072             ScRange aRef;
1073             while ( aIter.GetNextRef( aRef) )
1074             {
1075                 if (aRef.aStart.Tab() <= nTab && aRef.aEnd.Tab() >= nTab)
1076                 {
1077                     if (Intersect( nCol1,nRow1,nCol2,nRow2,
1078                             aRef.aStart.Col(),aRef.aStart.Row(),
1079                             aRef.aEnd.Col(),aRef.aEnd.Row() ))
1080                     {
1081                         sal_Bool bAlien = ( aCellIter.GetTab() != nTab );
1082                         sal_Bool bDrawRet;
1083                         if (bAlien)
1084                             bDrawRet = DrawAlienEntry( aRef, rData );
1085                         else
1086                             bDrawRet = DrawEntry( aCellIter.GetCol(), aCellIter.GetRow(),
1087                                                     aRef, rData );
1088                         if (bDrawRet)
1089                         {
1090                             nResult = DET_INS_INSERTED;         //  neuer Pfeil eingetragen
1091                         }
1092                         else
1093                         {
1094                             if (bRunning)
1095                             {
1096                                 if (nResult == DET_INS_EMPTY)
1097                                     nResult = DET_INS_CIRCULAR;
1098                             }
1099                             else
1100                             {
1101                                         //  weiterverfolgen
1102 
1103                                 if ( nLevel < rData.GetMaxLevel() )
1104                                 {
1105                                     sal_uInt16 nSubResult = InsertSuccLevel(
1106                                                             aCellIter.GetCol(), aCellIter.GetRow(),
1107                                                             aCellIter.GetCol(), aCellIter.GetRow(),
1108                                                             rData, nLevel+1 );
1109                                     switch (nSubResult)
1110                                     {
1111                                         case DET_INS_INSERTED:
1112                                             nResult = DET_INS_INSERTED;
1113                                             break;
1114                                         case DET_INS_CONTINUE:
1115                                             if (nResult != DET_INS_INSERTED)
1116                                                 nResult = DET_INS_CONTINUE;
1117                                             break;
1118                                         case DET_INS_CIRCULAR:
1119                                             if (nResult == DET_INS_EMPTY)
1120                                                 nResult = DET_INS_CIRCULAR;
1121                                             break;
1122                                         // DET_INS_EMPTY: unveraendert lassen
1123                                     }
1124                                 }
1125                                 else                                    //  nMaxLevel erreicht
1126                                     if (nResult != DET_INS_INSERTED)
1127                                         nResult = DET_INS_CONTINUE;
1128                             }
1129                         }
1130                     }
1131                 }
1132             }
1133             pFCell->SetRunning(bRunning);
1134         }
1135         pCell = aCellIter.GetNext();
1136     }
1137 
1138     return nResult;
1139 }
1140 
1141 sal_uInt16 ScDetectiveFunc::FindSuccLevel( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
1142                                         sal_uInt16 nLevel, sal_uInt16 nDeleteLevel )
1143 {
1144     DBG_ASSERT( nLevel<1000, "Level" );
1145 
1146     sal_uInt16 nResult = nLevel;
1147     sal_Bool bDelete = ( nDeleteLevel && nLevel == nDeleteLevel-1 );
1148 
1149     ScCellIterator aCellIter( pDoc, 0,0, nTab, MAXCOL,MAXROW, nTab );
1150     ScBaseCell* pCell = aCellIter.GetFirst();
1151     while (pCell)
1152     {
1153         if (pCell->GetCellType() == CELLTYPE_FORMULA)
1154         {
1155             ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
1156             sal_Bool bRunning = pFCell->IsRunning();
1157 
1158             if (pFCell->GetDirty())
1159                 pFCell->Interpret();                // nach SetRunning geht's nicht mehr!
1160             pFCell->SetRunning(sal_True);
1161 
1162             ScDetectiveRefIter aIter( (ScFormulaCell*) pCell );
1163             ScRange aRef;
1164             while ( aIter.GetNextRef( aRef) )
1165             {
1166                 if (aRef.aStart.Tab() <= nTab && aRef.aEnd.Tab() >= nTab)
1167                 {
1168                     if (Intersect( nCol1,nRow1,nCol2,nRow2,
1169                             aRef.aStart.Col(),aRef.aStart.Row(),
1170                             aRef.aEnd.Col(),aRef.aEnd.Row() ))
1171                     {
1172                         if ( bDelete )                          // Pfeile, die hier anfangen
1173                         {
1174                             if (aRef.aStart != aRef.aEnd)
1175                             {
1176                                 DeleteBox( aRef.aStart.Col(), aRef.aStart.Row(),
1177                                                 aRef.aEnd.Col(), aRef.aEnd.Row() );
1178                             }
1179                             DeleteArrowsAt( aRef.aStart.Col(), aRef.aStart.Row(), sal_False );
1180                         }
1181                         else if ( !bRunning &&
1182                                 HasArrow( aRef.aStart,
1183                                             aCellIter.GetCol(),aCellIter.GetRow(),aCellIter.GetTab() ) )
1184                         {
1185                             sal_uInt16 nTemp = FindSuccLevel( aCellIter.GetCol(), aCellIter.GetRow(),
1186                                                             aCellIter.GetCol(), aCellIter.GetRow(),
1187                                                             nLevel+1, nDeleteLevel );
1188                             if (nTemp > nResult)
1189                                 nResult = nTemp;
1190                         }
1191                     }
1192                 }
1193             }
1194 
1195             pFCell->SetRunning(bRunning);
1196         }
1197         pCell = aCellIter.GetNext();
1198     }
1199 
1200     return nResult;
1201 }
1202 
1203 
1204 //
1205 //  --------------------------------------------------------------------------------
1206 //
1207 
1208 sal_Bool ScDetectiveFunc::ShowPred( SCCOL nCol, SCROW nRow )
1209 {
1210     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1211     if (!pModel)
1212         return sal_False;
1213 
1214     ScDetectiveData aData( pModel );
1215 
1216     sal_uInt16 nMaxLevel = 0;
1217     sal_uInt16 nResult = DET_INS_CONTINUE;
1218     while (nResult == DET_INS_CONTINUE && nMaxLevel < 1000)
1219     {
1220         aData.SetMaxLevel( nMaxLevel );
1221         nResult = InsertPredLevel( nCol, nRow, aData, 0 );
1222         ++nMaxLevel;
1223     }
1224 
1225     return ( nResult == DET_INS_INSERTED );
1226 }
1227 
1228 sal_Bool ScDetectiveFunc::ShowSucc( SCCOL nCol, SCROW nRow )
1229 {
1230     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1231     if (!pModel)
1232         return sal_False;
1233 
1234     ScDetectiveData aData( pModel );
1235 
1236     sal_uInt16 nMaxLevel = 0;
1237     sal_uInt16 nResult = DET_INS_CONTINUE;
1238     while (nResult == DET_INS_CONTINUE && nMaxLevel < 1000)
1239     {
1240         aData.SetMaxLevel( nMaxLevel );
1241         nResult = InsertSuccLevel( nCol, nRow, nCol, nRow, aData, 0 );
1242         ++nMaxLevel;
1243     }
1244 
1245     return ( nResult == DET_INS_INSERTED );
1246 }
1247 
1248 sal_Bool ScDetectiveFunc::ShowError( SCCOL nCol, SCROW nRow )
1249 {
1250     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1251     if (!pModel)
1252         return sal_False;
1253 
1254     ScRange aRange( nCol, nRow, nTab );
1255     ScAddress aErrPos;
1256     if ( !HasError( aRange,aErrPos ) )
1257         return sal_False;
1258 
1259     ScDetectiveData aData( pModel );
1260 
1261     aData.SetMaxLevel( 1000 );
1262     sal_uInt16 nResult = InsertErrorLevel( nCol, nRow, aData, 0 );
1263 
1264     return ( nResult == DET_INS_INSERTED );
1265 }
1266 
1267 sal_Bool ScDetectiveFunc::DeleteSucc( SCCOL nCol, SCROW nRow )
1268 {
1269     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1270     if (!pModel)
1271         return sal_False;
1272 
1273     sal_uInt16 nLevelCount = FindSuccLevel( nCol, nRow, nCol, nRow, 0, 0 );
1274     if ( nLevelCount )
1275         FindSuccLevel( nCol, nRow, nCol, nRow, 0, nLevelCount );            // loeschen
1276 
1277     return ( nLevelCount != 0 );
1278 }
1279 
1280 sal_Bool ScDetectiveFunc::DeletePred( SCCOL nCol, SCROW nRow )
1281 {
1282     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1283     if (!pModel)
1284         return sal_False;
1285 
1286     sal_uInt16 nLevelCount = FindPredLevel( nCol, nRow, 0, 0 );
1287     if ( nLevelCount )
1288         FindPredLevel( nCol, nRow, 0, nLevelCount );            // loeschen
1289 
1290     return ( nLevelCount != 0 );
1291 }
1292 
1293 sal_Bool ScDetectiveFunc::DeleteAll( ScDetectiveDelete eWhat )
1294 {
1295     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1296     if (!pModel)
1297         return sal_False;
1298 
1299     SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
1300     DBG_ASSERT(pPage,"Page ?");
1301 
1302     pPage->RecalcObjOrdNums();
1303 
1304     long    nDelCount = 0;
1305     sal_uLong   nObjCount = pPage->GetObjCount();
1306     if (nObjCount)
1307     {
1308         SdrObject** ppObj = new SdrObject*[nObjCount];
1309 
1310         SdrObjListIter aIter( *pPage, IM_FLAT );
1311         SdrObject* pObject = aIter.Next();
1312         while (pObject)
1313         {
1314             if ( pObject->GetLayer() == SC_LAYER_INTERN )
1315             {
1316                 sal_Bool bDoThis = sal_True;
1317                 if ( eWhat != SC_DET_ALL )
1318                 {
1319                     sal_Bool bCircle = ( pObject->ISA(SdrCircObj) );
1320                     sal_Bool bCaption = ScDrawLayer::IsNoteCaption( pObject );
1321                     if ( eWhat == SC_DET_DETECTIVE )        // Detektiv, aus Menue
1322                         bDoThis = !bCaption;                // auch Kreise
1323                     else if ( eWhat == SC_DET_CIRCLES )     // Kreise, wenn neue erzeugt werden
1324                         bDoThis = bCircle;
1325                     else if ( eWhat == SC_DET_ARROWS )      // DetectiveRefresh
1326                         bDoThis = !bCaption && !bCircle;    // don't include circles
1327                     else
1328                     {
1329                         DBG_ERROR("wat?");
1330                     }
1331                 }
1332                 if ( bDoThis )
1333                     ppObj[nDelCount++] = pObject;
1334             }
1335 
1336             pObject = aIter.Next();
1337         }
1338 
1339         long i;
1340         for (i=1; i<=nDelCount; i++)
1341             pModel->AddCalcUndo( new SdrUndoRemoveObj( *ppObj[nDelCount-i] ) );
1342 
1343         for (i=1; i<=nDelCount; i++)
1344             pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() );
1345 
1346         delete[] ppObj;
1347 
1348         Modified();
1349     }
1350 
1351     return ( nDelCount != 0 );
1352 }
1353 
1354 sal_Bool ScDetectiveFunc::MarkInvalid(sal_Bool& rOverflow)
1355 {
1356     rOverflow = sal_False;
1357     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1358     if (!pModel)
1359         return sal_False;
1360 
1361     sal_Bool bDeleted = DeleteAll( SC_DET_CIRCLES );        // nur die Kreise
1362 
1363     ScDetectiveData aData( pModel );
1364     long nInsCount = 0;
1365 
1366     //  Stellen suchen, wo Gueltigkeit definiert ist
1367 
1368     ScDocAttrIterator aAttrIter( pDoc, nTab, 0,0,MAXCOL,MAXROW );
1369     SCCOL nCol;
1370     SCROW nRow1;
1371     SCROW nRow2;
1372     const ScPatternAttr* pPattern = aAttrIter.GetNext( nCol, nRow1, nRow2 );
1373     while ( pPattern && nInsCount < SC_DET_MAXCIRCLE )
1374     {
1375         sal_uLong nIndex = ((const SfxUInt32Item&)pPattern->GetItem(ATTR_VALIDDATA)).GetValue();
1376         if (nIndex)
1377         {
1378             const ScValidationData* pData = pDoc->GetValidationEntry( nIndex );
1379             if ( pData )
1380             {
1381                 //  Zellen in dem Bereich durchgehen
1382 
1383                 sal_Bool bMarkEmpty = !pData->IsIgnoreBlank();
1384                 SCROW nNextRow = nRow1;
1385                 SCROW nRow;
1386                 ScCellIterator aCellIter( pDoc, nCol,nRow1,nTab, nCol,nRow2,nTab );
1387                 ScBaseCell* pCell = aCellIter.GetFirst();
1388                 while ( pCell && nInsCount < SC_DET_MAXCIRCLE )
1389                 {
1390                     SCROW nCellRow = aCellIter.GetRow();
1391                     if ( bMarkEmpty )
1392                         for ( nRow = nNextRow; nRow < nCellRow && nInsCount < SC_DET_MAXCIRCLE; nRow++ )
1393                         {
1394                             DrawCircle( nCol, nRow, aData );
1395                             ++nInsCount;
1396                         }
1397                     if ( !pData->IsDataValid( pCell, ScAddress( nCol, nCellRow, nTab ) ) )
1398                     {
1399                         DrawCircle( nCol, nCellRow, aData );
1400                         ++nInsCount;
1401                     }
1402                     nNextRow = nCellRow + 1;
1403                     pCell = aCellIter.GetNext();
1404                 }
1405                 if ( bMarkEmpty )
1406                     for ( nRow = nNextRow; nRow <= nRow2 && nInsCount < SC_DET_MAXCIRCLE; nRow++ )
1407                     {
1408                         DrawCircle( nCol, nRow, aData );
1409                         ++nInsCount;
1410                     }
1411             }
1412         }
1413 
1414         pPattern = aAttrIter.GetNext( nCol, nRow1, nRow2 );
1415     }
1416 
1417     if ( nInsCount >= SC_DET_MAXCIRCLE )
1418         rOverflow = sal_True;
1419 
1420     return ( bDeleted || nInsCount != 0 );
1421 }
1422 
1423 void ScDetectiveFunc::UpdateAllComments( ScDocument& rDoc )
1424 {
1425     //  for all caption objects, update attributes and SpecialTextBoxShadow flag
1426     //  (on all tables - nTab is ignored!)
1427 
1428     //  no undo actions, this is refreshed after undo
1429 
1430     ScDrawLayer* pModel = rDoc.GetDrawLayer();
1431     if (!pModel)
1432         return;
1433 
1434     for( SCTAB nObjTab = 0, nTabCount = rDoc.GetTableCount(); nObjTab < nTabCount; ++nObjTab )
1435     {
1436         rDoc.InitializeNoteCaptions( nObjTab );
1437         SdrPage* pPage = pModel->GetPage( static_cast< sal_uInt16 >( nObjTab ) );
1438         DBG_ASSERT( pPage, "Page ?" );
1439         if( pPage )
1440         {
1441             SdrObjListIter aIter( *pPage, IM_FLAT );
1442             for( SdrObject* pObject = aIter.Next(); pObject; pObject = aIter.Next() )
1443             {
1444                 if ( ScDrawObjData* pData = ScDrawLayer::GetNoteCaptionData( pObject, nObjTab ) )
1445                 {
1446                     ScPostIt* pNote = rDoc.GetNote( pData->maStart );
1447                     // caption should exist, we iterate over drawing objects...
1448                     DBG_ASSERT( pNote && (pNote->GetCaption() == pObject), "ScDetectiveFunc::UpdateAllComments - invalid cell note" );
1449                     if( pNote )
1450                     {
1451                         ScCommentData aData( rDoc, pModel );
1452                         SfxItemSet aAttrColorSet = pObject->GetMergedItemSet();
1453                         aAttrColorSet.Put( XFillColorItem( String(), GetCommentColor() ) );
1454                         aData.UpdateCaptionSet( aAttrColorSet );
1455                         pObject->SetMergedItemSetAndBroadcast( aData.GetCaptionSet() );
1456                         if( SdrCaptionObj* pCaption = dynamic_cast< SdrCaptionObj* >( pObject ) )
1457                         {
1458                             pCaption->SetSpecialTextBoxShadow();
1459                             pCaption->SetFixedTail();
1460                         }
1461                     }
1462                 }
1463             }
1464         }
1465     }
1466 }
1467 
1468 void ScDetectiveFunc::UpdateAllArrowColors()
1469 {
1470     //  no undo actions necessary
1471 
1472     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1473     if (!pModel)
1474         return;
1475 
1476     for( SCTAB nObjTab = 0, nTabCount = pDoc->GetTableCount(); nObjTab < nTabCount; ++nObjTab )
1477     {
1478         SdrPage* pPage = pModel->GetPage( static_cast< sal_uInt16 >( nObjTab ) );
1479         DBG_ASSERT( pPage, "Page ?" );
1480         if( pPage )
1481         {
1482             SdrObjListIter aIter( *pPage, IM_FLAT );
1483             for( SdrObject* pObject = aIter.Next(); pObject; pObject = aIter.Next() )
1484             {
1485                 if ( pObject->GetLayer() == SC_LAYER_INTERN )
1486                 {
1487                     sal_Bool bArrow = sal_False;
1488                     sal_Bool bError = sal_False;
1489 
1490                     ScAddress aPos;
1491                     ScRange aSource;
1492                     sal_Bool bDummy;
1493                     ScDetectiveObjType eType = GetDetectiveObjectType( pObject, nObjTab, aPos, aSource, bDummy );
1494                     if ( eType == SC_DETOBJ_ARROW || eType == SC_DETOBJ_TOOTHERTAB )
1495                     {
1496                         //  source is valid, determine error flag from source range
1497 
1498                         ScAddress aErrPos;
1499                         if ( HasError( aSource, aErrPos ) )
1500                             bError = sal_True;
1501                         else
1502                             bArrow = sal_True;
1503                     }
1504                     else if ( eType == SC_DETOBJ_FROMOTHERTAB )
1505                     {
1506                         //  source range is no longer known, take error flag from formula itself
1507                         //  (this means, if the formula has an error, all references to other tables
1508                         //  are marked red)
1509 
1510                         ScAddress aErrPos;
1511                         if ( HasError( ScRange( aPos), aErrPos ) )
1512                             bError = sal_True;
1513                         else
1514                             bArrow = sal_True;
1515                     }
1516                     else if ( eType == SC_DETOBJ_CIRCLE )
1517                     {
1518                         //  circles (error marks) are always red
1519 
1520                         bError = sal_True;
1521                     }
1522                     else if ( eType == SC_DETOBJ_NONE )
1523                     {
1524                         //  frame for area reference has no ObjType, always gets arrow color
1525 
1526                         if ( pObject->ISA( SdrRectObj ) && !pObject->ISA( SdrCaptionObj ) )
1527                         {
1528                             bArrow = sal_True;
1529                         }
1530                     }
1531 
1532                     if ( bArrow || bError )
1533                     {
1534                         ColorData nColorData = ( bError ? GetErrorColor() : GetArrowColor() );
1535                         //pObject->SendRepaintBroadcast(pObject->GetBoundRect());
1536                         pObject->SetMergedItem( XLineColorItem( String(), Color( nColorData ) ) );
1537 
1538                         // repaint only
1539                         pObject->ActionChanged();
1540                         // pObject->SendRepaintBroadcast(pObject->GetBoundRect());
1541                     }
1542                 }
1543             }
1544         }
1545     }
1546 }
1547 
1548 sal_Bool ScDetectiveFunc::FindFrameForObject( SdrObject* pObject, ScRange& rRange )
1549 {
1550     //  find the rectangle for an arrow (always the object directly before the arrow)
1551     //  rRange must be initialized to the source cell of the arrow (start of area)
1552 
1553     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1554     if (!pModel) return sal_False;
1555 
1556     SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab));
1557     DBG_ASSERT(pPage,"Page ?");
1558     if (!pPage) return sal_False;
1559 
1560     // test if the object is a direct page member
1561     if( pObject && pObject->GetPage() && (pObject->GetPage() == pObject->GetObjList()) )
1562     {
1563         // Is there a previous object?
1564         const sal_uInt32 nOrdNum(pObject->GetOrdNum());
1565 
1566         if(nOrdNum > 0)
1567         {
1568             SdrObject* pPrevObj = pPage->GetObj(nOrdNum - 1);
1569 
1570             if ( pPrevObj && pPrevObj->GetLayer() == SC_LAYER_INTERN && pPrevObj->ISA(SdrRectObj) )
1571             {
1572                 ScDrawObjData* pPrevData = ScDrawLayer::GetObjDataTab( pPrevObj, rRange.aStart.Tab() );
1573                 if ( pPrevData && pPrevData->maStart.IsValid() && pPrevData->maEnd.IsValid() && (pPrevData->maStart == rRange.aStart) )
1574                 {
1575                     rRange.aEnd = pPrevData->maEnd;
1576                     return sal_True;
1577                 }
1578             }
1579         }
1580     }
1581     return sal_False;
1582 }
1583 
1584 ScDetectiveObjType ScDetectiveFunc::GetDetectiveObjectType( SdrObject* pObject, SCTAB nObjTab,
1585                                 ScAddress& rPosition, ScRange& rSource, sal_Bool& rRedLine )
1586 {
1587     rRedLine = sal_False;
1588     ScDetectiveObjType eType = SC_DETOBJ_NONE;
1589 
1590     if ( pObject && pObject->GetLayer() == SC_LAYER_INTERN )
1591     {
1592         if ( ScDrawObjData* pData = ScDrawLayer::GetObjDataTab( pObject, nObjTab ) )
1593         {
1594             bool bValidStart = pData->maStart.IsValid();
1595             bool bValidEnd = pData->maEnd.IsValid();
1596 
1597             if ( pObject->IsPolyObj() && pObject->GetPointCount() == 2 )
1598             {
1599                 // line object -> arrow
1600 
1601                 if ( bValidStart )
1602                     eType = bValidEnd ? SC_DETOBJ_ARROW : SC_DETOBJ_TOOTHERTAB;
1603                 else if ( bValidEnd )
1604                     eType = SC_DETOBJ_FROMOTHERTAB;
1605 
1606                 if ( bValidStart )
1607                     rSource = pData->maStart;
1608                 if ( bValidEnd )
1609                     rPosition = pData->maEnd;
1610 
1611                 if ( bValidStart && lcl_HasThickLine( *pObject ) )
1612                 {
1613                     // thick line -> look for frame before this object
1614 
1615                     FindFrameForObject( pObject, rSource );     // modifies rSource
1616                 }
1617 
1618                 ColorData nObjColor = ((const XLineColorItem&)pObject->GetMergedItem(XATTR_LINECOLOR)).GetColorValue().GetColor();
1619                 if ( nObjColor == GetErrorColor() && nObjColor != GetArrowColor() )
1620                     rRedLine = sal_True;
1621             }
1622             else if ( pObject->ISA(SdrCircObj) )
1623             {
1624                 if ( bValidStart )
1625                 {
1626                     // cell position is returned in rPosition
1627 
1628                     rPosition = pData->maStart;
1629                     eType = SC_DETOBJ_CIRCLE;
1630                 }
1631             }
1632         }
1633     }
1634 
1635     return eType;
1636 }
1637 
1638 void ScDetectiveFunc::InsertObject( ScDetectiveObjType eType,
1639                             const ScAddress& rPosition, const ScRange& rSource,
1640                             sal_Bool bRedLine )
1641 {
1642     ScDrawLayer* pModel = pDoc->GetDrawLayer();
1643     if (!pModel) return;
1644     ScDetectiveData aData( pModel );
1645 
1646     switch (eType)
1647     {
1648         case SC_DETOBJ_ARROW:
1649         case SC_DETOBJ_FROMOTHERTAB:
1650             InsertArrow( rPosition.Col(), rPosition.Row(),
1651                          rSource.aStart.Col(), rSource.aStart.Row(),
1652                          rSource.aEnd.Col(), rSource.aEnd.Row(),
1653                          (eType == SC_DETOBJ_FROMOTHERTAB), bRedLine, aData );
1654             break;
1655         case SC_DETOBJ_TOOTHERTAB:
1656             InsertToOtherTab( rSource.aStart.Col(), rSource.aStart.Row(),
1657                               rSource.aEnd.Col(), rSource.aEnd.Row(),
1658                               bRedLine, aData );
1659             break;
1660         case SC_DETOBJ_CIRCLE:
1661             DrawCircle( rPosition.Col(), rPosition.Row(), aData );
1662             break;
1663         default:
1664         {
1665             // added to avoid warnings
1666         }
1667     }
1668 }
1669 
1670 // static
1671 ColorData ScDetectiveFunc::GetArrowColor()
1672 {
1673     if (!bColorsInitialized)
1674         InitializeColors();
1675     return nArrowColor;
1676 }
1677 
1678 // static
1679 ColorData ScDetectiveFunc::GetErrorColor()
1680 {
1681     if (!bColorsInitialized)
1682         InitializeColors();
1683     return nErrorColor;
1684 }
1685 
1686 // static
1687 ColorData ScDetectiveFunc::GetCommentColor()
1688 {
1689     if (!bColorsInitialized)
1690         InitializeColors();
1691     return nCommentColor;
1692 }
1693 
1694 // static
1695 void ScDetectiveFunc::InitializeColors()
1696 {
1697     // may be called several times to update colors from configuration
1698 
1699     const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig();
1700     nArrowColor   = rColorCfg.GetColorValue(svtools::CALCDETECTIVE).nColor;
1701     nErrorColor   = rColorCfg.GetColorValue(svtools::CALCDETECTIVEERROR).nColor;
1702     nCommentColor = rColorCfg.GetColorValue(svtools::CALCNOTESBACKGROUND).nColor;
1703 
1704     bColorsInitialized = sal_True;
1705 }
1706 
1707 // static
1708 sal_Bool ScDetectiveFunc::IsColorsInitialized()
1709 {
1710     return bColorsInitialized;
1711 }
1712 
1713