xref: /trunk/main/sc/source/core/data/postit.cxx (revision 8394eaf1)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_sc.hxx"
26 
27 #include "postit.hxx"
28 
29 #include <rtl/ustrbuf.hxx>
30 #include <unotools/useroptions.hxx>
31 #include <svx/svdpage.hxx>
32 #include <svx/svdocapt.hxx>
33 #include <editeng/outlobj.hxx>
34 #include <editeng/editobj.hxx>
35 #include <basegfx/polygon/b2dpolygon.hxx>
36 
37 #include "scitems.hxx"
38 #include <svx/xlnstit.hxx>
39 #include <svx/xlnstwit.hxx>
40 #include <svx/xlnstcit.hxx>
41 #include <svx/sxcecitm.hxx>
42 #include <svx/xflclit.hxx>
43 #include <svx/sdshitm.hxx>
44 #include <svx/sdsxyitm.hxx>
45 
46 #include "document.hxx"
47 #include "docpool.hxx"
48 #include "patattr.hxx"
49 #include "cell.hxx"
50 #include "drwlayer.hxx"
51 #include "userdat.hxx"
52 #include "detfunc.hxx"
53 
54 using ::rtl::OUString;
55 using ::rtl::OUStringBuffer;
56 
57 // ============================================================================
58 
59 namespace {
60 
61 const long SC_NOTECAPTION_WIDTH             =  2900; // Default width of note caption textbox.
62 const long SC_NOTECAPTION_MAXWIDTH_TEMP     = 12000; // Maximum width of temporary note caption textbox.
63 const long SC_NOTECAPTION_HEIGHT            =  1800; // Default height of note caption textbox.
64 const long SC_NOTECAPTION_CELLDIST          =   600; // Default distance of note captions to border of anchor cell.
65 const long SC_NOTECAPTION_OFFSET_Y          = -1500; // Default Y offset of note captions to top border of anchor cell.
66 const long SC_NOTECAPTION_OFFSET_X          =  1500; // Default X offset of note captions to left border of anchor cell.
67 const long SC_NOTECAPTION_BORDERDIST_TEMP   =   100; // Distance of temporary note captions to visible sheet area.
68 
69 // ============================================================================
70 
71 /** Static helper functions for caption objects. */
72 class ScCaptionUtil
73 {
74 public:
75 	/** Moves the caption object to the correct layer according to passed visibility. */
76 	static void         SetCaptionLayer( SdrCaptionObj& rCaption, bool bShown );
77 	/** Sets basic caption settings required for note caption objects. */
78 	static void         SetBasicCaptionSettings( SdrCaptionObj& rCaption, bool bShown );
79 	/** Stores the cell position of the note in the user data area of the caption. */
80 	static void         SetCaptionUserData( SdrCaptionObj& rCaption, const ScAddress& rPos );
81 	/** Sets all default formatting attributes to the caption object. */
82 	static void         SetDefaultItems( SdrCaptionObj& rCaption, ScDocument& rDoc );
83 	/** Updates caption item set according to the passed item set while removing shadow items. */
84 	static void         SetCaptionItems( SdrCaptionObj& rCaption, const SfxItemSet& rItemSet );
85 };
86 
87 // ----------------------------------------------------------------------------
88 
SetCaptionLayer(SdrCaptionObj & rCaption,bool bShown)89 void ScCaptionUtil::SetCaptionLayer( SdrCaptionObj& rCaption, bool bShown )
90 {
91     SdrLayerID nLayer = bShown ? SC_LAYER_INTERN : SC_LAYER_HIDDEN;
92     if( nLayer != rCaption.GetLayer() )
93         rCaption.SetLayer( nLayer );
94 }
95 
SetBasicCaptionSettings(SdrCaptionObj & rCaption,bool bShown)96 void ScCaptionUtil::SetBasicCaptionSettings( SdrCaptionObj& rCaption, bool bShown )
97 {
98     ScDrawLayer::SetAnchor( &rCaption, SCA_PAGE );
99     SetCaptionLayer( rCaption, bShown );
100     rCaption.SetFixedTail();
101     rCaption.SetSpecialTextBoxShadow();
102 }
103 
SetCaptionUserData(SdrCaptionObj & rCaption,const ScAddress & rPos)104 void ScCaptionUtil::SetCaptionUserData( SdrCaptionObj& rCaption, const ScAddress& rPos )
105 {
106     // pass true to ScDrawLayer::GetObjData() to create the object data entry
107     ScDrawObjData* pObjData = ScDrawLayer::GetObjData( &rCaption, true );
108     OSL_ENSURE( pObjData, "ScCaptionUtil::SetCaptionUserData - missing drawing object user data" );
109     pObjData->maStart = rPos;
110     pObjData->mbNote = true;
111 }
112 
SetDefaultItems(SdrCaptionObj & rCaption,ScDocument & rDoc)113 void ScCaptionUtil::SetDefaultItems( SdrCaptionObj& rCaption, ScDocument& rDoc )
114 {
115     SfxItemSet aItemSet = rCaption.GetMergedItemSet();
116 
117     // caption tail arrow
118     ::basegfx::B2DPolygon aTriangle;
119     aTriangle.append( ::basegfx::B2DPoint( 10.0,  0.0 ) );
120     aTriangle.append( ::basegfx::B2DPoint(  0.0, 30.0 ) );
121     aTriangle.append( ::basegfx::B2DPoint( 20.0, 30.0 ) );
122     aTriangle.setClosed( true );
123     /*  #99319# Line ends are now created with an empty name. The
124         checkForUniqueItem() method then finds a unique name for the items
125         value. */
126     aItemSet.Put( XLineStartItem( String::EmptyString(), ::basegfx::B2DPolyPolygon( aTriangle ) ) );
127     aItemSet.Put( XLineStartWidthItem( 200 ) );
128     aItemSet.Put( XLineStartCenterItem( sal_False ) );
129     aItemSet.Put( XFillStyleItem( XFILL_SOLID ) );
130     aItemSet.Put( XFillColorItem( String::EmptyString(), ScDetectiveFunc::GetCommentColor() ) );
131     aItemSet.Put( SdrCaptionEscDirItem( SDRCAPT_ESCBESTFIT ) );
132 
133     // shadow
134     /*  SdrShadowItem has sal_False, instead the shadow is set for the
135         rectangle only with SetSpecialTextBoxShadow() when the object is
136         created (item must be set to adjust objects from older files). */
137     aItemSet.Put( SdrShadowItem( sal_False ) );
138     aItemSet.Put( SdrShadowXDistItem( 100 ) );
139     aItemSet.Put( SdrShadowYDistItem( 100 ) );
140 
141     // text attributes
142     aItemSet.Put( SdrTextLeftDistItem( 100 ) );
143     aItemSet.Put( SdrTextRightDistItem( 100 ) );
144     aItemSet.Put( SdrTextUpperDistItem( 100 ) );
145     aItemSet.Put( SdrTextLowerDistItem( 100 ) );
146     aItemSet.Put( SdrTextAutoGrowWidthItem( sal_False ) );
147     aItemSet.Put( SdrTextAutoGrowHeightItem( sal_True ) );
148     // #78943# use the default cell style to be able to modify the caption font
149     const ScPatternAttr& rDefPattern = static_cast< const ScPatternAttr& >( rDoc.GetPool()->GetDefaultItem( ATTR_PATTERN ) );
150     rDefPattern.FillEditItemSet( &aItemSet );
151 
152     rCaption.SetMergedItemSet( aItemSet );
153 }
154 
SetCaptionItems(SdrCaptionObj & rCaption,const SfxItemSet & rItemSet)155 void ScCaptionUtil::SetCaptionItems( SdrCaptionObj& rCaption, const SfxItemSet& rItemSet )
156 {
157     // copy all items
158     rCaption.SetMergedItemSet( rItemSet );
159     // reset shadow items
160     rCaption.SetMergedItem( SdrShadowItem( sal_False ) );
161     rCaption.SetMergedItem( SdrShadowXDistItem( 100 ) );
162     rCaption.SetMergedItem( SdrShadowYDistItem( 100 ) );
163     rCaption.SetSpecialTextBoxShadow();
164 }
165 
166 // ============================================================================
167 
168 /** Helper for creation and manipulation of caption drawing objects independent
169     from cell annotations. */
170 class ScCaptionCreator
171 {
172 public:
173     /** Create a new caption. The caption will not be inserted into the document. */
174     explicit            ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, bool bShown, bool bTailFront );
175     /** Manipulate an existing caption. */
176     explicit            ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, SdrCaptionObj& rCaption );
177 
178     /** Returns the drawing layer page of the sheet contained in maPos. */
179     SdrPage*            GetDrawPage();
180     /** Returns the caption drawing object. */
GetCaption()181     inline SdrCaptionObj* GetCaption() { return mpCaption; }
182 
183     /** Moves the caption inside the passed rectangle. Uses page area if 0 is passed. */
184     void                FitCaptionToRect( const Rectangle* pVisRect = 0 );
185     /** Places the caption inside the passed rectangle, tries to keep the cell rectangle uncovered. Uses page area if 0 is passed. */
186     void                AutoPlaceCaption( const Rectangle* pVisRect = 0 );
187     /** Updates caption tail and textbox according to current cell position. Uses page area if 0 is passed. */
188     void                UpdateCaptionPos( const Rectangle* pVisRect = 0 );
189 
190 protected:
191     /** Helper constructor for derived classes. */
192     explicit            ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos );
193 
194     /** Calculates the caption tail position according to current cell position. */
195     Point               CalcTailPos( bool bTailFront );
196     /** Implements creation of the caption object. The caption will not be inserted into the document. */
197     void                CreateCaption( bool bShown, bool bTailFront );
198 
199 private:
200     /** Initializes all members. */
201     void                Initialize();
202     /** Returns the passed rectangle if existing, page rectangle otherwise. */
GetVisRect(const Rectangle * pVisRect) const203     inline const Rectangle& GetVisRect( const Rectangle* pVisRect ) const { return pVisRect ? *pVisRect : maPageRect; }
204 
205 private:
206     ScDocument&         mrDoc;
207     ScAddress           maPos;
208     SdrCaptionObj*      mpCaption;
209     Rectangle           maPageRect;
210     Rectangle           maCellRect;
211     bool                mbNegPage;
212 };
213 
214 // ----------------------------------------------------------------------------
215 
ScCaptionCreator(ScDocument & rDoc,const ScAddress & rPos,bool bShown,bool bTailFront)216 ScCaptionCreator::ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, bool bShown, bool bTailFront ) :
217     mrDoc( rDoc ),
218     maPos( rPos ),
219     mpCaption( 0 )
220 {
221     Initialize();
222     CreateCaption( bShown, bTailFront );
223 }
224 
ScCaptionCreator(ScDocument & rDoc,const ScAddress & rPos,SdrCaptionObj & rCaption)225 ScCaptionCreator::ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, SdrCaptionObj& rCaption ) :
226     mrDoc( rDoc ),
227     maPos( rPos ),
228     mpCaption( &rCaption )
229 {
230     Initialize();
231 }
232 
ScCaptionCreator(ScDocument & rDoc,const ScAddress & rPos)233 ScCaptionCreator::ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos ) :
234     mrDoc( rDoc ),
235     maPos( rPos ),
236     mpCaption( 0 )
237 {
238     Initialize();
239 }
240 
GetDrawPage()241 SdrPage* ScCaptionCreator::GetDrawPage()
242 {
243     ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer();
244     return pDrawLayer ? pDrawLayer->GetPage( static_cast< sal_uInt16 >( maPos.Tab() ) ) : 0;
245 }
246 
FitCaptionToRect(const Rectangle * pVisRect)247 void ScCaptionCreator::FitCaptionToRect( const Rectangle* pVisRect )
248 {
249     const Rectangle& rVisRect = GetVisRect( pVisRect );
250 
251     // tail position
252     Point aTailPos = mpCaption->GetTailPos();
253     aTailPos.X() = ::std::max( ::std::min( aTailPos.X(), rVisRect.Right() ), rVisRect.Left() );
254     aTailPos.Y() = ::std::max( ::std::min( aTailPos.Y(), rVisRect.Bottom() ), rVisRect.Top() );
255     mpCaption->SetTailPos( aTailPos );
256 
257     // caption rectangle
258     Rectangle aCaptRect = mpCaption->GetLogicRect();
259     Point aCaptPos = aCaptRect.TopLeft();
260     // move textbox inside right border of visible area
261     aCaptPos.X() = ::std::min< long >( aCaptPos.X(), rVisRect.Right() - aCaptRect.GetWidth() );
262     // move textbox inside left border of visible area (this may move it outside on right side again)
263     aCaptPos.X() = ::std::max< long >( aCaptPos.X(), rVisRect.Left() );
264     // move textbox inside bottom border of visible area
265     aCaptPos.Y() = ::std::min< long >( aCaptPos.Y(), rVisRect.Bottom() - aCaptRect.GetHeight() );
266     // move textbox inside top border of visible area (this may move it outside on bottom side again)
267     aCaptPos.Y() = ::std::max< long >( aCaptPos.Y(), rVisRect.Top() );
268     // update caption
269     aCaptRect.SetPos( aCaptPos );
270     mpCaption->SetLogicRect( aCaptRect );
271 }
272 
AutoPlaceCaption(const Rectangle * pVisRect)273 void ScCaptionCreator::AutoPlaceCaption( const Rectangle* pVisRect )
274 {
275     const Rectangle& rVisRect = GetVisRect( pVisRect );
276 
277     // caption rectangle
278     Rectangle aCaptRect = mpCaption->GetLogicRect();
279     long nWidth = aCaptRect.GetWidth();
280     long nHeight = aCaptRect.GetHeight();
281 
282     // n***Space contains available space between border of visible area and cell
283     long nLeftSpace = maCellRect.Left() - rVisRect.Left() + 1;
284     long nRightSpace = rVisRect.Right() - maCellRect.Right() + 1;
285     long nTopSpace = maCellRect.Top() - rVisRect.Top() + 1;
286     long nBottomSpace = rVisRect.Bottom() - maCellRect.Bottom() + 1;
287 
288     // nNeeded*** contains textbox dimensions plus needed distances to cell or border of visible area
289     long nNeededSpaceX = nWidth + SC_NOTECAPTION_CELLDIST;
290     long nNeededSpaceY = nHeight + SC_NOTECAPTION_CELLDIST;
291 
292     // bFitsWidth*** == true means width of textbox fits into horizontal free space of visible area
293     bool bFitsWidthLeft = nNeededSpaceX <= nLeftSpace;      // text box width fits into the width left of cell
294     bool bFitsWidthRight = nNeededSpaceX <= nRightSpace;    // text box width fits into the width right of cell
295     bool bFitsWidth = nWidth <= rVisRect.GetWidth();        // text box width fits into width of visible area
296 
297     // bFitsHeight*** == true means height of textbox fits into vertical free space of visible area
298     bool bFitsHeightTop = nNeededSpaceY <= nTopSpace;       // text box height fits into the height above cell
299     bool bFitsHeightBottom = nNeededSpaceY <= nBottomSpace; // text box height fits into the height below cell
300     bool bFitsHeight = nHeight <= rVisRect.GetHeight();     // text box height fits into height of visible area
301 
302     // bFits*** == true means the textbox fits completely into free space of visible area
303     bool bFitsLeft = bFitsWidthLeft && bFitsHeight;
304     bool bFitsRight = bFitsWidthRight && bFitsHeight;
305     bool bFitsTop = bFitsWidth && bFitsHeightTop;
306     bool bFitsBottom = bFitsWidth && bFitsHeightBottom;
307 
308     Point aCaptPos;
309     // use left/right placement if possible, or if top/bottom placement not possible
310     if( bFitsLeft || bFitsRight || (!bFitsTop && !bFitsBottom) )
311     {
312         // prefer left in RTL sheet and right in LTR sheets
313         bool bPreferLeft = bFitsLeft && (mbNegPage || !bFitsRight);
314         bool bPreferRight = bFitsRight && (!mbNegPage || !bFitsLeft);
315         // move to left, if left is preferred, or if neither left nor right fit and there is more space to the left
316         if( bPreferLeft || (!bPreferRight && (nLeftSpace > nRightSpace)) )
317             aCaptPos.X() = maCellRect.Left() - SC_NOTECAPTION_CELLDIST - nWidth;
318         else // to right
319             aCaptPos.X() = maCellRect.Right() + SC_NOTECAPTION_CELLDIST;
320         // Y position according to top cell border
321         aCaptPos.Y() = maCellRect.Top() + SC_NOTECAPTION_OFFSET_Y;
322     }
323     else // top or bottom placement
324     {
325         // X position
326         aCaptPos.X() = maCellRect.Left() + SC_NOTECAPTION_OFFSET_X;
327         // top placement, if possible
328         if( bFitsTop )
329             aCaptPos.Y() = maCellRect.Top() - SC_NOTECAPTION_CELLDIST - nHeight;
330         else // bottom placement
331             aCaptPos.Y() = maCellRect.Bottom() + SC_NOTECAPTION_CELLDIST;
332     }
333 
334     // update textbox position in note caption object
335     aCaptRect.SetPos( aCaptPos );
336     mpCaption->SetLogicRect( aCaptRect );
337     FitCaptionToRect( pVisRect );
338 }
339 
UpdateCaptionPos(const Rectangle * pVisRect)340 void ScCaptionCreator::UpdateCaptionPos( const Rectangle* pVisRect )
341 {
342     ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer();
343 
344     // update caption position
345     const Point& rOldTailPos = mpCaption->GetTailPos();
346     Point aTailPos = CalcTailPos( false );
347     if( rOldTailPos != aTailPos )
348     {
349         // create drawing undo action
350         if( pDrawLayer )
351             if( pDrawLayer->IsUndoAllowed() )
352                 if( pDrawLayer->IsRecording() )
353                     pDrawLayer->AddCalcUndo( pDrawLayer->GetSdrUndoFactory().CreateUndoGeoObject( *mpCaption ) );
354 
355         // calculate new caption rectangle (#i98141# handle LTR<->RTL switch correctly)
356         Rectangle aCaptRect = mpCaption->GetLogicRect();
357         long nDiffX = (rOldTailPos.X() >= 0) ? (aCaptRect.Left() - rOldTailPos.X()) : (rOldTailPos.X() - aCaptRect.Right());
358         if( mbNegPage ) nDiffX = -nDiffX - aCaptRect.GetWidth();
359         long nDiffY = aCaptRect.Top() - rOldTailPos.Y();
360         aCaptRect.SetPos( aTailPos + Point( nDiffX, nDiffY ) );
361         // set new tail position and caption rectangle
362         mpCaption->SetTailPos( aTailPos );
363         mpCaption->SetLogicRect( aCaptRect );
364         // fit caption into draw page
365         FitCaptionToRect( pVisRect );
366     }
367 
368     // update cell position in caption user data
369     ScDrawObjData* pCaptData = ScDrawLayer::GetNoteCaptionData( mpCaption, maPos.Tab() );
370     if( pCaptData && (maPos != pCaptData->maStart) )
371     {
372         // create drawing undo action
373         if( pDrawLayer && pDrawLayer->IsRecording() )
374             pDrawLayer->AddCalcUndo< ScUndoObjData >(mpCaption, pCaptData->maStart, pCaptData->maEnd, maPos, pCaptData->maEnd );
375         // set new position
376         pCaptData->maStart = maPos;
377     }
378 }
379 
CalcTailPos(bool bTailFront)380 Point ScCaptionCreator::CalcTailPos( bool bTailFront )
381 {
382     // tail position
383     bool bTailLeft = bTailFront != mbNegPage;
384     Point aTailPos = bTailLeft ? maCellRect.TopLeft() : maCellRect.TopRight();
385     // move caption point 1/10 mm inside cell
386     if( bTailLeft ) aTailPos.X() += 10; else aTailPos.X() -= 10;
387     aTailPos.Y() += 10;
388     return aTailPos;
389 }
390 
CreateCaption(bool bShown,bool bTailFront)391 void ScCaptionCreator::CreateCaption( bool bShown, bool bTailFront )
392 {
393     // create the caption drawing object
394     Rectangle aTextRect( Point( 0 , 0 ), Size( SC_NOTECAPTION_WIDTH, SC_NOTECAPTION_HEIGHT ) );
395     Point aTailPos = CalcTailPos( bTailFront );
396     mpCaption = new SdrCaptionObj( aTextRect, aTailPos );
397     // basic caption settings
398     ScCaptionUtil::SetBasicCaptionSettings( *mpCaption, bShown );
399 }
400 
Initialize()401 void ScCaptionCreator::Initialize()
402 {
403     maCellRect = ScDrawLayer::GetCellRect( mrDoc, maPos, true );
404     mbNegPage = mrDoc.IsNegativePage( maPos.Tab() );
405     if( SdrPage* pDrawPage = GetDrawPage() )
406     {
407         maPageRect = Rectangle( Point( 0, 0 ), pDrawPage->GetSize() );
408         /*  #i98141# SdrPage::GetSize() returns negative width in RTL mode.
409             The call to Rectangle::Adjust() orders left/right coordinate
410             accordingly. */
411         maPageRect.Justify();
412     }
413 }
414 
415 // ============================================================================
416 
417 /** Helper for creation of permanent caption drawing objects for cell notes. */
418 class ScNoteCaptionCreator : public ScCaptionCreator
419 {
420 public:
421     /** Create a new caption object and inserts it into the document. */
422     explicit            ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScNoteData& rNoteData );
423     /** Manipulate an existing caption. */
424     explicit            ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, SdrCaptionObj& rCaption, bool bShown );
425 };
426 
427 // ----------------------------------------------------------------------------
428 
ScNoteCaptionCreator(ScDocument & rDoc,const ScAddress & rPos,ScNoteData & rNoteData)429 ScNoteCaptionCreator::ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScNoteData& rNoteData ) :
430     ScCaptionCreator( rDoc, rPos ) // use helper c'tor that does not create the caption yet
431 {
432     SdrPage* pDrawPage = GetDrawPage();
433     OSL_ENSURE( pDrawPage, "ScNoteCaptionCreator::ScNoteCaptionCreator - no drawing page" );
434     if( pDrawPage )
435     {
436         // create the caption drawing object
437         CreateCaption( rNoteData.mbShown, false );
438         rNoteData.mpCaption = GetCaption();
439         OSL_ENSURE( rNoteData.mpCaption, "ScNoteCaptionCreator::ScNoteCaptionCreator - missing caption object" );
440         if( rNoteData.mpCaption )
441         {
442             // store note position in user data of caption object
443             ScCaptionUtil::SetCaptionUserData( *rNoteData.mpCaption, rPos );
444             // insert object into draw page
445             pDrawPage->InsertObject( rNoteData.mpCaption );
446         }
447     }
448 }
449 
ScNoteCaptionCreator(ScDocument & rDoc,const ScAddress & rPos,SdrCaptionObj & rCaption,bool bShown)450 ScNoteCaptionCreator::ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, SdrCaptionObj& rCaption, bool bShown ) :
451     ScCaptionCreator( rDoc, rPos, rCaption )
452 {
453     SdrPage* pDrawPage = GetDrawPage();
454     OSL_ENSURE( pDrawPage, "ScNoteCaptionCreator::ScNoteCaptionCreator - no drawing page" );
455     OSL_ENSURE( rCaption.GetPage() == pDrawPage, "ScNoteCaptionCreator::ScNoteCaptionCreator - wrong drawing page in caption" );
456     if( pDrawPage && (rCaption.GetPage() == pDrawPage) )
457     {
458         // store note position in user data of caption object
459         ScCaptionUtil::SetCaptionUserData( rCaption, rPos );
460         // basic caption settings
461         ScCaptionUtil::SetBasicCaptionSettings( rCaption, bShown );
462         // set correct tail position
463         rCaption.SetTailPos( CalcTailPos( false ) );
464     }
465 }
466 
467 } // namespace
468 
469 // ============================================================================
470 
471 struct ScCaptionInitData
472 {
473     typedef ::std::auto_ptr< SfxItemSet >           SfxItemSetPtr;
474     typedef ::std::auto_ptr< OutlinerParaObject >   OutlinerParaObjPtr;
475 
476     SfxItemSetPtr       mxItemSet;          // Caption object formatting.
477     OutlinerParaObjPtr  mxOutlinerObj;      // Text object with all text portion formatting.
478     ::rtl::OUString     maSimpleText;       // Simple text without formatting.
479     Point               maCaptionOffset;    // Caption position relative to cell corner.
480     Size                maCaptionSize;      // Size of the caption object.
481     bool                mbDefaultPosSize;   // True = use default position and size for caption.
482 
483     explicit            ScCaptionInitData();
484 };
485 
486 // ----------------------------------------------------------------------------
487 
ScCaptionInitData()488 ScCaptionInitData::ScCaptionInitData() :
489     mbDefaultPosSize( true )
490 {
491 }
492 
493 // ============================================================================
494 
ScNoteData(bool bShown)495 ScNoteData::ScNoteData( bool bShown ) :
496     mpCaption( 0 ),
497     mbShown( bShown )
498 {
499 }
500 
~ScNoteData()501 ScNoteData::~ScNoteData()
502 {
503 }
504 
505 // ============================================================================
506 
ScPostIt(ScDocument & rDoc,const ScAddress & rPos,bool bShown)507 ScPostIt::ScPostIt( ScDocument& rDoc, const ScAddress& rPos, bool bShown ) :
508     mrDoc( rDoc ),
509     maNoteData( bShown )
510 {
511     AutoStamp();
512     CreateCaption( rPos );
513 }
514 
ScPostIt(ScDocument & rDoc,const ScAddress & rPos,const ScPostIt & rNote)515 ScPostIt::ScPostIt( ScDocument& rDoc, const ScAddress& rPos, const ScPostIt& rNote ) :
516     mrDoc( rDoc ),
517     maNoteData( rNote.maNoteData )
518 {
519     maNoteData.mpCaption = 0;
520     CreateCaption( rPos, rNote.maNoteData.mpCaption );
521 }
522 
ScPostIt(ScDocument & rDoc,const ScAddress & rPos,const ScNoteData & rNoteData,bool bAlwaysCreateCaption)523 ScPostIt::ScPostIt( ScDocument& rDoc, const ScAddress& rPos, const ScNoteData& rNoteData, bool bAlwaysCreateCaption ) :
524     mrDoc( rDoc ),
525     maNoteData( rNoteData )
526 {
527     if( bAlwaysCreateCaption || maNoteData.mbShown )
528         CreateCaptionFromInitData( rPos );
529 }
530 
~ScPostIt()531 ScPostIt::~ScPostIt()
532 {
533     RemoveCaption();
534 }
535 
Clone(const ScAddress & rOwnPos,ScDocument & rDestDoc,const ScAddress & rDestPos,bool bCloneCaption) const536 ScPostIt* ScPostIt::Clone( const ScAddress& rOwnPos, ScDocument& rDestDoc, const ScAddress& rDestPos, bool bCloneCaption ) const
537 {
538     CreateCaptionFromInitData( rOwnPos );
539     return bCloneCaption ? new ScPostIt( rDestDoc, rDestPos, *this ) : new ScPostIt( rDestDoc, rDestPos, maNoteData, false );
540 }
541 
AutoStamp()542 void ScPostIt::AutoStamp()
543 {
544     maNoteData.maDate = ScGlobal::pLocaleData->getDate( Date() );
545     maNoteData.maAuthor = SvtUserOptions().GetID();
546 }
547 
GetOutlinerObject() const548 const OutlinerParaObject* ScPostIt::GetOutlinerObject() const
549 {
550     if( maNoteData.mpCaption )
551         return maNoteData.mpCaption->GetOutlinerParaObject();
552     if( maNoteData.mxInitData.get() )
553         return maNoteData.mxInitData->mxOutlinerObj.get();
554     return 0;
555 }
556 
GetEditTextObject() const557 const EditTextObject* ScPostIt::GetEditTextObject() const
558 {
559     const OutlinerParaObject* pOPO = GetOutlinerObject();
560     return pOPO ? &pOPO->GetTextObject() : 0;
561 }
562 
GetText() const563 OUString ScPostIt::GetText() const
564 {
565     if( const EditTextObject* pEditObj = GetEditTextObject() )
566     {
567         OUStringBuffer aBuffer;
568         for( sal_uInt32 nPara = 0, nParaCount = pEditObj->GetParagraphCount(); nPara < nParaCount; ++nPara )
569         {
570             if( nPara > 0 )
571                 aBuffer.append( sal_Unicode( '\n' ) );
572             aBuffer.append( pEditObj->GetText( nPara ) );
573         }
574         return aBuffer.makeStringAndClear();
575     }
576     if( maNoteData.mxInitData.get() )
577         return maNoteData.mxInitData->maSimpleText;
578     return OUString();
579 }
580 
HasMultiLineText() const581 bool ScPostIt::HasMultiLineText() const
582 {
583     if( const EditTextObject* pEditObj = GetEditTextObject() )
584         return pEditObj->GetParagraphCount() > 1;
585     if( maNoteData.mxInitData.get() )
586         return maNoteData.mxInitData->maSimpleText.indexOf( '\n' ) >= 0;
587     return false;
588 }
589 
SetText(const ScAddress & rPos,const OUString & rText)590 void ScPostIt::SetText( const ScAddress& rPos, const OUString& rText )
591 {
592     CreateCaptionFromInitData( rPos );
593     if( maNoteData.mpCaption )
594         maNoteData.mpCaption->SetText( rText );
595 }
596 
GetOrCreateCaption(const ScAddress & rPos) const597 SdrCaptionObj* ScPostIt::GetOrCreateCaption( const ScAddress& rPos ) const
598 {
599     CreateCaptionFromInitData( rPos );
600     return maNoteData.mpCaption;
601 }
602 
ForgetCaption()603 void ScPostIt::ForgetCaption()
604 {
605     /*  This function is used in undo actions to give up the responsibility for
606         the caption object which is handled by separate drawing undo actions. */
607     maNoteData.mpCaption = 0;
608     maNoteData.mxInitData.reset();
609 }
610 
ShowCaption(const ScAddress & rPos,bool bShow)611 void ScPostIt::ShowCaption( const ScAddress& rPos, bool bShow )
612 {
613     CreateCaptionFromInitData( rPos );
614     // no separate drawing undo needed, handled completely inside ScUndoShowHideNote
615     maNoteData.mbShown = bShow;
616     if( maNoteData.mpCaption )
617         ScCaptionUtil::SetCaptionLayer( *maNoteData.mpCaption, bShow );
618 }
619 
ShowCaptionTemp(const ScAddress & rPos,bool bShow)620 void ScPostIt::ShowCaptionTemp( const ScAddress& rPos, bool bShow )
621 {
622     CreateCaptionFromInitData( rPos );
623     if( maNoteData.mpCaption )
624         ScCaptionUtil::SetCaptionLayer( *maNoteData.mpCaption, maNoteData.mbShown || bShow );
625 }
626 
UpdateCaptionPos(const ScAddress & rPos)627 void ScPostIt::UpdateCaptionPos( const ScAddress& rPos )
628 {
629     CreateCaptionFromInitData( rPos );
630     if( maNoteData.mpCaption )
631     {
632         ScCaptionCreator aCreator( mrDoc, rPos, *maNoteData.mpCaption );
633         aCreator.UpdateCaptionPos();
634     }
635 }
636 
637 // private --------------------------------------------------------------------
638 
CreateCaptionFromInitData(const ScAddress & rPos) const639 void ScPostIt::CreateCaptionFromInitData( const ScAddress& rPos ) const
640 {
641     OSL_ENSURE( maNoteData.mpCaption || maNoteData.mxInitData.get(), "ScPostIt::CreateCaptionFromInitData - need caption object or initial caption data" );
642     if( maNoteData.mxInitData.get() )
643     {
644         /*  This function is called from ScPostIt::Clone() when copying cells
645             to the clipboard/undo document, and when copying cells from the
646             clipboard/undo document. The former should always be called first,
647             so if called in an clipboard/undo document, the caption should have
648             been created already. */
649         OSL_ENSURE( !mrDoc.IsUndo() && !mrDoc.IsClipboard(), "ScPostIt::CreateCaptionFromInitData - note caption should not be created in undo/clip documents" );
650 
651         /*  #i104915# Never try to create notes in Undo document, leads to
652             crash due to missing document members (e.g. row height array). */
653         if( !maNoteData.mpCaption && !mrDoc.IsUndo() )
654         {
655             // ScNoteCaptionCreator c'tor creates the caption and inserts it into the document and maNoteData
656             ScNoteCaptionCreator aCreator( mrDoc, rPos, maNoteData );
657             if( maNoteData.mpCaption )
658             {
659                 ScCaptionInitData& rInitData = *maNoteData.mxInitData;
660 
661                 // transfer ownership of outliner object to caption, or set simple text
662                 OSL_ENSURE( rInitData.mxOutlinerObj.get() || (rInitData.maSimpleText.getLength() > 0),
663                     "ScPostIt::CreateCaptionFromInitData - need either outliner para object or simple text" );
664                 if( rInitData.mxOutlinerObj.get() )
665                     maNoteData.mpCaption->SetOutlinerParaObject( rInitData.mxOutlinerObj.release() );
666                 else
667                     maNoteData.mpCaption->SetText( rInitData.maSimpleText );
668 
669                 // copy all items or set default items; reset shadow items
670                 ScCaptionUtil::SetDefaultItems( *maNoteData.mpCaption, mrDoc );
671                 if( rInitData.mxItemSet.get() )
672                     ScCaptionUtil::SetCaptionItems( *maNoteData.mpCaption, *rInitData.mxItemSet );
673 
674                 // set position and size of the caption object
675                 if( rInitData.mbDefaultPosSize )
676                 {
677                     // set other items and fit caption size to text
678                     maNoteData.mpCaption->SetMergedItem( SdrTextMinFrameWidthItem( SC_NOTECAPTION_WIDTH ) );
679                     maNoteData.mpCaption->SetMergedItem( SdrTextMaxFrameWidthItem( SC_NOTECAPTION_MAXWIDTH_TEMP ) );
680                     maNoteData.mpCaption->AdjustTextFrameWidthAndHeight();
681                     aCreator.AutoPlaceCaption();
682                 }
683                 else
684                 {
685                     Rectangle aCellRect = ScDrawLayer::GetCellRect( mrDoc, rPos, true );
686                     bool bNegPage = mrDoc.IsNegativePage( rPos.Tab() );
687                     long nPosX = bNegPage ? (aCellRect.Left() - rInitData.maCaptionOffset.X()) : (aCellRect.Right() + rInitData.maCaptionOffset.X());
688                     long nPosY = aCellRect.Top() + rInitData.maCaptionOffset.Y();
689                     Rectangle aCaptRect( Point( nPosX, nPosY ), rInitData.maCaptionSize );
690                     maNoteData.mpCaption->SetLogicRect( aCaptRect );
691                     aCreator.FitCaptionToRect();
692                 }
693             }
694         }
695         // forget the initial caption data struct
696         maNoteData.mxInitData.reset();
697     }
698 }
699 
CreateCaption(const ScAddress & rPos,const SdrCaptionObj * pCaption)700 void ScPostIt::CreateCaption( const ScAddress& rPos, const SdrCaptionObj* pCaption )
701 {
702     OSL_ENSURE( !maNoteData.mpCaption, "ScPostIt::CreateCaption - unexpected caption object found" );
703     maNoteData.mpCaption = 0;
704 
705     /*  #i104915# Never try to create notes in Undo document, leads to
706         crash due to missing document members (e.g. row height array). */
707     OSL_ENSURE( !mrDoc.IsUndo(), "ScPostIt::CreateCaption - note caption should not be created in undo documents" );
708     if( mrDoc.IsUndo() )
709         return;
710 
711     // drawing layer may be missing, if a note is copied into a clipboard document
712     if( mrDoc.IsClipboard() )
713         mrDoc.InitDrawLayer();
714 
715     // ScNoteCaptionCreator c'tor creates the caption and inserts it into the document and maNoteData
716     ScNoteCaptionCreator aCreator( mrDoc, rPos, maNoteData );
717     if( maNoteData.mpCaption )
718     {
719         // clone settings of passed caption
720         if( pCaption )
721         {
722             // copy edit text object (object must be inserted into page already)
723             if( OutlinerParaObject* pOPO = pCaption->GetOutlinerParaObject() )
724                 maNoteData.mpCaption->SetOutlinerParaObject( new OutlinerParaObject( *pOPO ) );
725             // copy formatting items (after text has been copied to apply font formatting)
726             maNoteData.mpCaption->SetMergedItemSetAndBroadcast( pCaption->GetMergedItemSet() );
727             // move textbox position relative to new cell, copy textbox size
728             Rectangle aCaptRect = pCaption->GetLogicRect();
729             Point aDist = maNoteData.mpCaption->GetTailPos() - pCaption->GetTailPos();
730             aCaptRect.Move( aDist.X(), aDist.Y() );
731             maNoteData.mpCaption->SetLogicRect( aCaptRect );
732             aCreator.FitCaptionToRect();
733         }
734         else
735         {
736             // set default formatting and default position
737             ScCaptionUtil::SetDefaultItems( *maNoteData.mpCaption, mrDoc );
738             aCreator.AutoPlaceCaption();
739         }
740 
741         // create undo action
742         if( ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer() )
743                 if( pDrawLayer->IsUndoAllowed() )
744                     if( pDrawLayer->IsRecording() )
745                         pDrawLayer->AddCalcUndo( pDrawLayer->GetSdrUndoFactory().CreateUndoNewObject( *maNoteData.mpCaption ) );
746     }
747 }
748 
RemoveCaption()749 void ScPostIt::RemoveCaption()
750 {
751 
752     /*  Remove caption object only, if this note is its owner (e.g. notes in
753         undo documents refer to captions in original document, do not remove
754         them from drawing layer here). */
755     ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer();
756     if( maNoteData.mpCaption && (pDrawLayer == maNoteData.mpCaption->GetModel()) )
757     {
758         OSL_ENSURE( pDrawLayer, "ScPostIt::RemoveCaption - object without drawing layer" );
759         SdrPage* pDrawPage = maNoteData.mpCaption->GetPage();
760         OSL_ENSURE( pDrawPage, "ScPostIt::RemoveCaption - object without drawing page" );
761         if( pDrawPage )
762         {
763             pDrawPage->RecalcObjOrdNums();
764             // create drawing undo action (before removing the object to have valid draw page in undo action)
765             bool bRecording = ( pDrawLayer && pDrawLayer->IsUndoAllowed() && pDrawLayer->IsRecording() );
766             if( bRecording )
767                 pDrawLayer->AddCalcUndo( pDrawLayer->GetSdrUndoFactory().CreateUndoDeleteObject( *maNoteData.mpCaption ) );
768             // remove the object from the drawing page, delete if undo is disabled
769             SdrObject* pObj = pDrawPage->RemoveObject( maNoteData.mpCaption->GetOrdNum() );
770             if( !bRecording )
771                 SdrObject::Free( pObj );
772         }
773     }
774     maNoteData.mpCaption = 0;
775 }
776 
777 // ============================================================================
778 
UpdateCaptionPositions(ScDocument & rDoc,const ScRange & rRange)779 void ScNoteUtil::UpdateCaptionPositions( ScDocument& rDoc, const ScRange& rRange )
780 {
781     // do not use ScCellIterator, it skips filtered and subtotal cells
782     for( ScAddress aPos( rRange.aStart ); aPos.Tab() <= rRange.aEnd.Tab(); aPos.IncTab() )
783         for( aPos.SetCol( rRange.aStart.Col() ); aPos.Col() <= rRange.aEnd.Col(); aPos.IncCol() )
784             for( aPos.SetRow( rRange.aStart.Row() ); aPos.Row() <= rRange.aEnd.Row(); aPos.IncRow() )
785                 if( ScPostIt* pNote = rDoc.GetNote( aPos ) )
786                     pNote->UpdateCaptionPos( aPos );
787 }
788 
CreateTempCaption(ScDocument & rDoc,const ScAddress & rPos,SdrPage & rDrawPage,const OUString & rUserText,const Rectangle & rVisRect,bool bTailFront)789 SdrCaptionObj* ScNoteUtil::CreateTempCaption(
790         ScDocument& rDoc, const ScAddress& rPos, SdrPage& rDrawPage,
791         const OUString& rUserText, const Rectangle& rVisRect, bool bTailFront )
792 {
793     OUStringBuffer aBuffer( rUserText );
794     // add plain text of invisible (!) cell note (no formatting etc.)
795     SdrCaptionObj* pNoteCaption = 0;
796     const ScPostIt* pNote = rDoc.GetNote( rPos );
797     if( pNote && !pNote->IsCaptionShown() )
798     {
799         if( aBuffer.getLength() > 0 )
800             aBuffer.appendAscii( RTL_CONSTASCII_STRINGPARAM( "\n________\n" ) ).append( pNote->GetText() );
801         pNoteCaption = pNote->GetOrCreateCaption( rPos );
802     }
803 
804     // create a caption if any text exists
805     if( !pNoteCaption && (aBuffer.getLength() == 0) )
806         return 0;
807 
808     // prepare visible rectangle (add default distance to all borders)
809     Rectangle aVisRect(
810         rVisRect.Left() + SC_NOTECAPTION_BORDERDIST_TEMP,
811         rVisRect.Top() + SC_NOTECAPTION_BORDERDIST_TEMP,
812         rVisRect.Right() - SC_NOTECAPTION_BORDERDIST_TEMP,
813         rVisRect.Bottom() - SC_NOTECAPTION_BORDERDIST_TEMP );
814 
815     // create the caption object
816     ScCaptionCreator aCreator( rDoc, rPos, true, bTailFront );
817     SdrCaptionObj* pCaption = aCreator.GetCaption();
818 
819     // insert caption into page (needed to set caption text)
820     rDrawPage.InsertObject( pCaption );
821 
822     // clone the edit text object, unless user text is present, then set this text
823     if( pNoteCaption && (rUserText.getLength() == 0) )
824     {
825         if( OutlinerParaObject* pOPO = pNoteCaption->GetOutlinerParaObject() )
826             pCaption->SetOutlinerParaObject( new OutlinerParaObject( *pOPO ) );
827         // set formatting (must be done after setting text) and resize the box to fit the text
828         pCaption->SetMergedItemSetAndBroadcast( pNoteCaption->GetMergedItemSet() );
829         Rectangle aCaptRect( pCaption->GetLogicRect().TopLeft(), pNoteCaption->GetLogicRect().GetSize() );
830         pCaption->SetLogicRect( aCaptRect );
831     }
832     else
833     {
834         // if pNoteCaption is null, then aBuffer contains some text
835         pCaption->SetText( aBuffer.makeStringAndClear() );
836         ScCaptionUtil::SetDefaultItems( *pCaption, rDoc );
837         // adjust caption size to text size
838         long nMaxWidth = ::std::min< long >( aVisRect.GetWidth() * 2 / 3, SC_NOTECAPTION_MAXWIDTH_TEMP );
839         pCaption->SetMergedItem( SdrTextAutoGrowWidthItem( sal_True ) );
840         pCaption->SetMergedItem( SdrTextMinFrameWidthItem( SC_NOTECAPTION_WIDTH ) );
841         pCaption->SetMergedItem( SdrTextMaxFrameWidthItem( nMaxWidth ) );
842         pCaption->SetMergedItem( SdrTextAutoGrowHeightItem( sal_True ) );
843         pCaption->AdjustTextFrameWidthAndHeight();
844     }
845 
846     // move caption into visible area
847     aCreator.AutoPlaceCaption( &aVisRect );
848     return pCaption;
849 }
850 
CreateNoteFromCaption(ScDocument & rDoc,const ScAddress & rPos,SdrCaptionObj & rCaption,bool bShown)851 ScPostIt* ScNoteUtil::CreateNoteFromCaption(
852         ScDocument& rDoc, const ScAddress& rPos, SdrCaptionObj& rCaption, bool bShown )
853 {
854     ScNoteData aNoteData( bShown );
855     aNoteData.mpCaption = &rCaption;
856     ScPostIt* pNote = new ScPostIt( rDoc, rPos, aNoteData, false );
857     pNote->AutoStamp();
858     rDoc.TakeNote( rPos, pNote );
859     // if pNote still points to the note after TakeNote(), insertion was successful
860     if( pNote )
861     {
862         // ScNoteCaptionCreator c'tor updates the caption object to be part of a note
863         ScNoteCaptionCreator aCreator( rDoc, rPos, rCaption, bShown );
864     }
865     return pNote;
866 }
867 
CreateNoteFromObjectData(ScDocument & rDoc,const ScAddress & rPos,SfxItemSet * pItemSet,OutlinerParaObject * pOutlinerObj,const Rectangle & rCaptionRect,bool bShown,bool bAlwaysCreateCaption)868 ScPostIt* ScNoteUtil::CreateNoteFromObjectData(
869         ScDocument& rDoc, const ScAddress& rPos, SfxItemSet* pItemSet,
870         OutlinerParaObject* pOutlinerObj, const Rectangle& rCaptionRect,
871         bool bShown, bool bAlwaysCreateCaption )
872 {
873     OSL_ENSURE( pItemSet && pOutlinerObj, "ScNoteUtil::CreateNoteFromObjectData - item set and outliner object expected" );
874     ScNoteData aNoteData( bShown );
875     aNoteData.mxInitData.reset( new ScCaptionInitData );
876     ScCaptionInitData& rInitData = *aNoteData.mxInitData;
877     rInitData.mxItemSet.reset( pItemSet );
878     rInitData.mxOutlinerObj.reset( pOutlinerObj );
879 
880     // convert absolute caption position to relative position
881     rInitData.mbDefaultPosSize = rCaptionRect.IsEmpty();
882     if( !rInitData.mbDefaultPosSize )
883     {
884         Rectangle aCellRect = ScDrawLayer::GetCellRect( rDoc, rPos, true );
885         bool bNegPage = rDoc.IsNegativePage( rPos.Tab() );
886         rInitData.maCaptionOffset.X() = bNegPage ? (aCellRect.Left() - rCaptionRect.Right()) : (rCaptionRect.Left() - aCellRect.Right());
887         rInitData.maCaptionOffset.Y() = rCaptionRect.Top() - aCellRect.Top();
888         rInitData.maCaptionSize = rCaptionRect.GetSize();
889     }
890 
891     /*  Create the note and insert it into the document. If the note is
892         visible, the caption object will be created automatically. */
893     ScPostIt* pNote = new ScPostIt( rDoc, rPos, aNoteData, bAlwaysCreateCaption );
894     pNote->AutoStamp();
895     rDoc.TakeNote( rPos, pNote );
896     // if pNote still points to the note after TakeNote(), insertion was successful
897     return pNote;
898 }
899 
CreateNoteFromString(ScDocument & rDoc,const ScAddress & rPos,const OUString & rNoteText,bool bShown,bool bAlwaysCreateCaption)900 ScPostIt* ScNoteUtil::CreateNoteFromString(
901         ScDocument& rDoc, const ScAddress& rPos, const OUString& rNoteText,
902         bool bShown, bool bAlwaysCreateCaption )
903 {
904     ScPostIt* pNote = 0;
905     if( rNoteText.getLength() > 0 )
906     {
907         ScNoteData aNoteData( bShown );
908         aNoteData.mxInitData.reset( new ScCaptionInitData );
909         ScCaptionInitData& rInitData = *aNoteData.mxInitData;
910         rInitData.maSimpleText = rNoteText;
911         rInitData.mbDefaultPosSize = true;
912 
913         /*  Create the note and insert it into the document. If the note is
914             visible, the caption object will be created automatically. */
915         pNote = new ScPostIt( rDoc, rPos, aNoteData, bAlwaysCreateCaption );
916         pNote->AutoStamp();
917         rDoc.TakeNote( rPos, pNote );
918         // if pNote still points to the note after TakeNote(), insertion was successful
919     }
920     return pNote;
921 }
922 
923 // ============================================================================
924