xref: /core/editeng/source/outliner/outliner.cxx (revision 37722469)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <comphelper/string.hxx>
21 #include <svl/eitem.hxx>
22 #include <svl/intitem.hxx>
23 #include <editeng/editeng.hxx>
24 #include <editeng/editview.hxx>
25 #include <editeng/editdata.hxx>
26 #include <editeng/lrspitem.hxx>
27 
28 #include <math.h>
29 #include <svl/style.hxx>
30 #include <editeng/outliner.hxx>
31 #include "paralist.hxx"
32 #include <editeng/outlobj.hxx>
33 #include <outleeng.hxx>
34 #include "outlundo.hxx"
35 #include <editeng/eeitem.hxx>
36 #include <editeng/editstat.hxx>
37 #include <editeng/overflowingtxt.hxx>
38 #include <editeng/editobj.hxx>
39 #include <svl/itemset.hxx>
40 #include <vcl/metric.hxx>
41 #include <editeng/numitem.hxx>
42 #include <editeng/adjustitem.hxx>
43 #include <vcl/GraphicObject.hxx>
44 #include <editeng/svxfont.hxx>
45 #include <editeng/brushitem.hxx>
46 #include <svl/itempool.hxx>
47 #include <libxml/xmlwriter.h>
48 #include <sal/log.hxx>
49 #include <o3tl/safeint.hxx>
50 #include <o3tl/string_view.hxx>
51 #include <o3tl/temporary.hxx>
52 #include <osl/diagnose.h>
53 
54 #include <memory>
55 using std::advance;
56 
57 
58 // Outliner
59 
60 
ImplCheckDepth(sal_Int16 & rnDepth) const61 void Outliner::ImplCheckDepth( sal_Int16& rnDepth ) const
62 {
63     if( rnDepth < gnMinDepth )
64         rnDepth = gnMinDepth;
65     else if( rnDepth > nMaxDepth )
66         rnDepth = nMaxDepth;
67 }
68 
Insert(const OUString & rText,sal_Int32 nAbsPos,sal_Int16 nDepth)69 Paragraph* Outliner::Insert(const OUString& rText, sal_Int32 nAbsPos, sal_Int16 nDepth)
70 {
71     DBG_ASSERT(pParaList->GetParagraphCount(),"Insert:No Paras");
72 
73     Paragraph* pPara;
74 
75     ImplCheckDepth( nDepth );
76 
77     sal_Int32 nParagraphCount = pParaList->GetParagraphCount();
78     if( nAbsPos > nParagraphCount )
79         nAbsPos = nParagraphCount;
80 
81     if( bFirstParaIsEmpty )
82     {
83         pPara = pParaList->GetParagraph( 0 );
84         if( pPara->GetDepth() != nDepth )
85         {
86             nDepthChangedHdlPrevDepth = pPara->GetDepth();
87             ParaFlag nPrevFlags = pPara->nFlags;
88             pPara->SetDepth( nDepth );
89             DepthChangedHdl(pPara, nPrevFlags);
90         }
91         pPara->nFlags |= ParaFlag::HOLDDEPTH;
92         SetText( rText, pPara );
93     }
94     else
95     {
96         bool bUpdate = pEditEngine->SetUpdateLayout( false );
97         ImplBlockInsertionCallbacks( true );
98         pPara = new Paragraph( nDepth );
99         pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nAbsPos );
100         pEditEngine->InsertParagraph( nAbsPos, OUString() );
101         DBG_ASSERT(pPara==pParaList->GetParagraph(nAbsPos),"Insert:Failed");
102         ImplInitDepth( nAbsPos, nDepth, false );
103         ParagraphInsertedHdl(pPara);
104         pPara->nFlags |= ParaFlag::HOLDDEPTH;
105         SetText( rText, pPara );
106         ImplBlockInsertionCallbacks( false );
107         pEditEngine->SetUpdateLayout( bUpdate );
108     }
109     bFirstParaIsEmpty = false;
110     DBG_ASSERT(pEditEngine->GetParagraphCount()==pParaList->GetParagraphCount(),"SetText failed");
111     return pPara;
112 }
113 
114 
ParagraphInserted(sal_Int32 nPara)115 void Outliner::ParagraphInserted( sal_Int32 nPara )
116 {
117 
118     if ( nBlockInsCallback )
119         return;
120 
121     if( bPasting || pEditEngine->IsInUndo() )
122     {
123         Paragraph* pPara = new Paragraph( -1 );
124         pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nPara );
125         if( pEditEngine->IsInUndo() )
126         {
127             pPara->bVisible = true;
128             const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL );
129             pPara->SetDepth( rLevel.GetValue() );
130         }
131     }
132     else
133     {
134         sal_Int16 nDepth = -1;
135         Paragraph* pParaBefore = pParaList->GetParagraph( nPara-1 );
136         if ( pParaBefore )
137             nDepth = pParaBefore->GetDepth();
138 
139         Paragraph* pPara = new Paragraph( nDepth );
140         pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nPara );
141 
142         if( !pEditEngine->IsInUndo() )
143         {
144             ImplCalcBulletText( nPara, true, false );
145             ParagraphInsertedHdl(pPara);
146         }
147     }
148 }
149 
ParagraphDeleted(sal_Int32 nPara)150 void Outliner::ParagraphDeleted( sal_Int32 nPara )
151 {
152 
153     if ( nBlockInsCallback || ( nPara == EE_PARA_ALL ) )
154         return;
155 
156     Paragraph* pPara = pParaList->GetParagraph( nPara );
157     if (!pPara)
158         return;
159 
160     sal_Int16 nDepth = pPara->GetDepth();
161 
162     if( !pEditEngine->IsInUndo() )
163     {
164         aParaRemovingHdl.Call( { this, pPara } );
165     }
166 
167     pParaList->Remove( nPara );
168 
169     if( pEditEngine->IsInUndo() || bPasting )
170         return;
171 
172     pPara = pParaList->GetParagraph( nPara );
173     if ( pPara && ( pPara->GetDepth() > nDepth ) )
174     {
175         ImplCalcBulletText( nPara, true, false );
176         // Search for next on the this level ...
177         while ( pPara && pPara->GetDepth() > nDepth )
178             pPara = pParaList->GetParagraph( ++nPara );
179     }
180 
181     if ( pPara && ( pPara->GetDepth() == nDepth ) )
182         ImplCalcBulletText( nPara, true, false );
183 }
184 
Init(OutlinerMode nMode)185 void Outliner::Init( OutlinerMode nMode )
186 {
187     nOutlinerMode = nMode;
188 
189     Clear();
190 
191     EEControlBits nCtrl = pEditEngine->GetControlWord();
192     nCtrl &= ~EEControlBits(EEControlBits::OUTLINER|EEControlBits::OUTLINER2);
193 
194     SetMaxDepth( 9 );
195 
196     switch ( GetOutlinerMode() )
197     {
198         case OutlinerMode::TextObject:
199         case OutlinerMode::TitleObject:
200             break;
201 
202         case OutlinerMode::OutlineObject:
203             nCtrl |= EEControlBits::OUTLINER2;
204             break;
205         case OutlinerMode::OutlineView:
206             nCtrl |= EEControlBits::OUTLINER;
207             break;
208 
209         default: OSL_FAIL( "Outliner::Init - Invalid Mode!" );
210     }
211 
212     pEditEngine->SetControlWord( nCtrl );
213 
214     const bool bWasUndoEnabled(IsUndoEnabled());
215     EnableUndo(false);
216     ImplInitDepth( 0, -1, false );
217     GetUndoManager().Clear();
218     EnableUndo(bWasUndoEnabled);
219 }
220 
SetMaxDepth(sal_Int16 nDepth)221 void Outliner::SetMaxDepth( sal_Int16 nDepth )
222 {
223     if( nMaxDepth != nDepth )
224     {
225         nMaxDepth = std::min( nDepth, sal_Int16(SVX_MAX_NUM-1) );
226     }
227 }
228 
GetDepth(sal_Int32 nPara) const229 sal_Int16 Outliner::GetDepth( sal_Int32 nPara ) const
230 {
231     Paragraph* pPara = pParaList->GetParagraph( nPara );
232     DBG_ASSERT( pPara, "Outliner::GetDepth - Paragraph not found!" );
233     return pPara ? pPara->GetDepth() : -1;
234 }
235 
SetDepth(Paragraph * pPara,sal_Int16 nNewDepth)236 void Outliner::SetDepth( Paragraph* pPara, sal_Int16 nNewDepth )
237 {
238 
239     ImplCheckDepth( nNewDepth );
240 
241     if ( nNewDepth == pPara->GetDepth() )
242         return;
243 
244     nDepthChangedHdlPrevDepth = pPara->GetDepth();
245     ParaFlag nPrevFlags = pPara->nFlags;
246 
247     sal_Int32 nPara = GetAbsPos( pPara );
248     ImplInitDepth( nPara, nNewDepth, true );
249     ImplCalcBulletText( nPara, false, false );
250 
251     if ( GetOutlinerMode() == OutlinerMode::OutlineObject )
252         ImplSetLevelDependentStyleSheet( nPara );
253 
254     DepthChangedHdl(pPara, nPrevFlags);
255 }
256 
GetNumberingStartValue(sal_Int32 nPara) const257 sal_Int16 Outliner::GetNumberingStartValue( sal_Int32 nPara ) const
258 {
259     Paragraph* pPara = pParaList->GetParagraph( nPara );
260     DBG_ASSERT( pPara, "Outliner::GetNumberingStartValue - Paragraph not found!" );
261     return pPara ? pPara->GetNumberingStartValue() : -1;
262 }
263 
SetNumberingStartValue(sal_Int32 nPara,sal_Int16 nNumberingStartValue)264 void Outliner::SetNumberingStartValue( sal_Int32 nPara, sal_Int16 nNumberingStartValue )
265 {
266     Paragraph* pPara = pParaList->GetParagraph( nPara );
267     DBG_ASSERT( pPara, "Outliner::GetNumberingStartValue - Paragraph not found!" );
268     if( pPara && pPara->GetNumberingStartValue() != nNumberingStartValue )
269     {
270         if( IsUndoEnabled() && !IsInUndo() )
271             InsertUndo( std::make_unique<OutlinerUndoChangeParaNumberingRestart>( this, nPara,
272                 pPara->GetNumberingStartValue(), nNumberingStartValue,
273                 pPara->IsParaIsNumberingRestart(), pPara->IsParaIsNumberingRestart() ) );
274 
275         pPara->SetNumberingStartValue( nNumberingStartValue );
276         ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() );
277         pEditEngine->SetModified();
278     }
279 }
280 
IsParaIsNumberingRestart(sal_Int32 nPara) const281 bool Outliner::IsParaIsNumberingRestart( sal_Int32 nPara ) const
282 {
283     Paragraph* pPara = pParaList->GetParagraph( nPara );
284     DBG_ASSERT( pPara, "Outliner::IsParaIsNumberingRestart - Paragraph not found!" );
285     return pPara && pPara->IsParaIsNumberingRestart();
286 }
287 
SetParaIsNumberingRestart(sal_Int32 nPara,bool bParaIsNumberingRestart)288 void Outliner::SetParaIsNumberingRestart( sal_Int32 nPara, bool bParaIsNumberingRestart )
289 {
290     Paragraph* pPara = pParaList->GetParagraph( nPara );
291     DBG_ASSERT( pPara, "Outliner::SetParaIsNumberingRestart - Paragraph not found!" );
292     if( pPara && (pPara->IsParaIsNumberingRestart() != bParaIsNumberingRestart) )
293     {
294         if( IsUndoEnabled() && !IsInUndo() )
295             InsertUndo( std::make_unique<OutlinerUndoChangeParaNumberingRestart>( this, nPara,
296                 pPara->GetNumberingStartValue(), pPara->GetNumberingStartValue(),
297                 pPara->IsParaIsNumberingRestart(), bParaIsNumberingRestart ) );
298 
299         pPara->SetParaIsNumberingRestart( bParaIsNumberingRestart );
300         ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() );
301         pEditEngine->SetModified();
302     }
303 }
304 
GetBulletsNumberingStatus(const sal_Int32 nParaStart,const sal_Int32 nParaEnd) const305 sal_Int32 Outliner::GetBulletsNumberingStatus(
306     const sal_Int32 nParaStart,
307     const sal_Int32 nParaEnd ) const
308 {
309     if ( nParaStart > nParaEnd
310          || nParaEnd >= pParaList->GetParagraphCount() )
311     {
312         SAL_WARN("editeng", "<Outliner::GetBulletsNumberingStatus> - unexpected parameter values" );
313         return 2;
314     }
315 
316     sal_Int32 nBulletsCount = 0;
317     sal_Int32 nNumberingCount = 0;
318     for (sal_Int32 nPara = nParaStart; nPara <= nParaEnd; ++nPara)
319     {
320         if ( !pParaList->GetParagraph(nPara) )
321         {
322             break;
323         }
324         const SvxNumberFormat* pFmt = GetNumberFormat(nPara);
325         if (!pFmt)
326         {
327             // At least, exists one paragraph that has no Bullets/Numbering.
328             break;
329         }
330         else if ((pFmt->GetNumberingType() == SVX_NUM_BITMAP) || (pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL))
331         {
332             // Having Bullets in this paragraph.
333             nBulletsCount++;
334         }
335         else
336         {
337             // Having Numbering in this paragraph.
338             nNumberingCount++;
339         }
340     }
341 
342     const sal_Int32 nParaCount = nParaEnd - nParaStart + 1;
343     if ( nBulletsCount == nParaCount )
344     {
345         return 0;
346     }
347     else if ( nNumberingCount == nParaCount )
348     {
349         return 1;
350     }
351     return 2;
352 }
353 
GetBulletsNumberingStatus() const354 sal_Int32 Outliner::GetBulletsNumberingStatus() const
355 {
356     return pParaList->GetParagraphCount() > 0
357            ? GetBulletsNumberingStatus( 0, pParaList->GetParagraphCount()-1 )
358            : 2;
359 }
360 
CreateParaObject(sal_Int32 nStartPara,sal_Int32 nCount) const361 std::optional<OutlinerParaObject> Outliner::CreateParaObject( sal_Int32 nStartPara, sal_Int32 nCount ) const
362 {
363     if ( static_cast<sal_uInt64>(nStartPara) + nCount >
364             o3tl::make_unsigned(pParaList->GetParagraphCount()) )
365         nCount = pParaList->GetParagraphCount() - nStartPara;
366 
367     // When a new OutlinerParaObject is created because a paragraph is just being deleted,
368     // it can happen that the ParaList is not updated yet...
369     if ( ( nStartPara + nCount ) > pEditEngine->GetParagraphCount() )
370         nCount = pEditEngine->GetParagraphCount() - nStartPara;
371 
372     if (nCount <= 0)
373         return std::nullopt;
374 
375     std::unique_ptr<EditTextObject> xText = pEditEngine->CreateTextObject( nStartPara, nCount );
376     const bool bIsEditDoc(OutlinerMode::TextObject == GetOutlinerMode());
377     ParagraphDataVector aParagraphDataVector(nCount);
378     const sal_Int32 nLastPara(nStartPara + nCount - 1);
379 
380     for(sal_Int32 nPara(nStartPara); nPara <= nLastPara; nPara++)
381     {
382         aParagraphDataVector[nPara-nStartPara] = *GetParagraph(nPara);
383     }
384 
385     xText->ClearPortionInfo(); // tdf#147166 the PortionInfo is unwanted here
386     OutlinerParaObject aPObj(std::move(xText), std::move(aParagraphDataVector), bIsEditDoc);
387     aPObj.SetOutlinerMode(GetOutlinerMode());
388 
389     return aPObj;
390 }
391 
SetToEmptyText()392 void Outliner::SetToEmptyText()
393 {
394     SetText(GetEmptyParaObject());
395 }
396 
SetText(const OUString & rText,Paragraph * pPara)397 void Outliner::SetText( const OUString& rText, Paragraph* pPara )
398 {
399     assert(pPara && "SetText:No Para");
400 
401     const sal_Int32 nPara = pParaList->GetAbsPos( pPara );
402 
403     if (pEditEngine->GetText( nPara ) == rText)
404     {
405         // short-circuit logic to improve performance
406         bFirstParaIsEmpty = false;
407         return;
408     }
409 
410     const bool bUpdate = pEditEngine->SetUpdateLayout( false );
411     ImplBlockInsertionCallbacks( true );
412 
413     if (rText.isEmpty())
414     {
415         pEditEngine->SetText( nPara, rText );
416         ImplInitDepth( nPara, pPara->GetDepth(), false );
417     }
418     else
419     {
420         const OUString aText(convertLineEnd(rText, LINEEND_LF));
421 
422         sal_Int32 nPos = 0;
423         sal_Int32 nInsPos = nPara+1;
424         sal_Int32 nIdx {0};
425         // Loop over all tokens, but ignore the last one if empty
426         // (i.e. if strings ends with the delimiter, detected by
427         // checking nIdx against string length). This check also
428         // handle empty strings.
429         while( nIdx>=0 && nIdx<aText.getLength() )
430         {
431             std::u16string_view aStr = o3tl::getToken(aText, 0, '\x0A', nIdx );
432 
433             sal_Int16 nCurDepth;
434             if( nPos )
435             {
436                 pPara = new Paragraph( -1 );
437                 nCurDepth = -1;
438             }
439             else
440                 nCurDepth = pPara->GetDepth();
441 
442             // In the outliner mode, filter the tabs and set the indentation
443             // about a LRSpaceItem. In EditEngine mode intend over old tabs
444             if( ( GetOutlinerMode() == OutlinerMode::OutlineObject ) ||
445                 ( GetOutlinerMode() == OutlinerMode::OutlineView ) )
446             {
447                 // Extract Tabs
448                 size_t nTabs = 0;
449                 while ( ( nTabs < aStr.size() ) && ( aStr[nTabs] == '\t' ) )
450                     nTabs++;
451                 if ( nTabs )
452                     aStr = aStr.substr(nTabs);
453 
454                 // Keep depth?  (see Outliner::Insert)
455                 if( !(pPara->nFlags & ParaFlag::HOLDDEPTH) )
456                 {
457                     nCurDepth = nTabs-1; //TODO: sal_Int32 -> sal_Int16!
458                     ImplCheckDepth( nCurDepth );
459                     pPara->SetDepth( nCurDepth );
460                 }
461             }
462             if( nPos ) // not with the first paragraph
463             {
464                 pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nInsPos );
465                 pEditEngine->InsertParagraph( nInsPos, OUString(aStr) );
466                 ParagraphInsertedHdl(pPara);
467             }
468             else
469             {
470                 nInsPos--;
471                 pEditEngine->SetText( nInsPos, OUString(aStr) );
472             }
473             ImplInitDepth( nInsPos, nCurDepth, false );
474             nInsPos++;
475             nPos++;
476         }
477     }
478 
479     DBG_ASSERT(pParaList->GetParagraphCount()==pEditEngine->GetParagraphCount(),"SetText failed!");
480     bFirstParaIsEmpty = false;
481     ImplBlockInsertionCallbacks( false );
482     // Restore the update mode.
483     pEditEngine->SetUpdateLayout(bUpdate, /*bRestoring=*/true);
484 }
485 
486 // pView == 0 -> Ignore tabs
487 
ImpConvertEdtToOut(sal_Int32 nPara)488 bool Outliner::ImpConvertEdtToOut( sal_Int32 nPara )
489 {
490 
491     bool bConverted = false;
492     sal_Int32 nTabs = 0;
493     ESelection aDelSel;
494 
495     OUString aName;
496 
497     OUString aStr( pEditEngine->GetText( nPara ) );
498     const sal_Unicode* pPtr = aStr.getStr();
499 
500     sal_Int32 nHeadingNumberStart = 0;
501     sal_Int32 nNumberingNumberStart = 0;
502     SfxStyleSheet* pStyle= pEditEngine->GetStyleSheet( nPara );
503     if( pStyle )
504     {
505         OUString aHeading_US( u"heading"_ustr );
506         OUString aNumber_US( u"Numbering"_ustr );
507         aName = pStyle->GetName();
508         sal_Int32 nSearch;
509         if ( ( nSearch = aName.indexOf( aHeading_US ) ) != -1 )
510             nHeadingNumberStart = nSearch + aHeading_US.getLength();
511         else if ( ( nSearch = aName.indexOf( aNumber_US ) ) != -1 )
512             nNumberingNumberStart = nSearch + aNumber_US.getLength();
513     }
514 
515     if ( nHeadingNumberStart || nNumberingNumberStart )
516     {
517         // PowerPoint import ?
518         if( nHeadingNumberStart && ( aStr.getLength() >= 2 ) &&
519                 ( pPtr[0] != '\t' ) && ( pPtr[1] == '\t' ) )
520         {
521             // Extract Bullet and Tab
522             aDelSel = ESelection( nPara, 0, nPara, 2 );
523         }
524 
525         sal_Int32 nPos = nHeadingNumberStart ? nHeadingNumberStart : nNumberingNumberStart;
526         std::u16string_view aLevel = comphelper::string::stripStart(aName.subView(nPos), ' ');
527         nTabs = o3tl::toInt32(aLevel);
528         if( nTabs )
529             nTabs--; // Level 0 = "heading 1"
530         bConverted = true;
531     }
532     else
533     {
534         // filter leading tabs
535         while( *pPtr == '\t' )
536         {
537             pPtr++;
538             nTabs++;
539         }
540         // Remove tabs from the text
541         if( nTabs )
542             aDelSel = ESelection( nPara, 0, nPara, nTabs );
543     }
544 
545     if ( aDelSel.HasRange() )
546     {
547         pEditEngine->QuickDelete( aDelSel );
548     }
549 
550     const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL );
551     sal_Int16 nOutlLevel = rLevel.GetValue();
552 
553     ImplCheckDepth( nOutlLevel );
554     ImplInitDepth( nPara, nOutlLevel, false );
555 
556     return bConverted;
557 }
558 
SetText(const OutlinerParaObject & rPObj)559 void Outliner::SetText( const OutlinerParaObject& rPObj )
560 {
561     bool bUpdate = pEditEngine->SetUpdateLayout( false );
562 
563     bool bUndo = pEditEngine->IsUndoEnabled();
564     EnableUndo( false );
565 
566     Init( rPObj.GetOutlinerMode() );
567 
568     ImplBlockInsertionCallbacks( true );
569     pEditEngine->SetText(rPObj.GetTextObject());
570 
571     bFirstParaIsEmpty = false;
572 
573     pParaList->Clear();
574     for( sal_Int32 nCurPara = 0; nCurPara < rPObj.Count(); nCurPara++ )
575     {
576         std::unique_ptr<Paragraph> pPara(new Paragraph( rPObj.GetParagraphData(nCurPara)));
577         ImplCheckDepth( pPara->nDepth );
578 
579         pParaList->Append(std::move(pPara));
580         ImplCheckNumBulletItem( nCurPara );
581     }
582 
583     ImplCheckParagraphs( 0, pParaList->GetParagraphCount() );
584 
585     EnableUndo( bUndo );
586     ImplBlockInsertionCallbacks( false );
587     pEditEngine->SetUpdateLayout( bUpdate );
588 
589     DBG_ASSERT( pParaList->GetParagraphCount()==rPObj.Count(),"SetText failed");
590     DBG_ASSERT( pEditEngine->GetParagraphCount()==rPObj.Count(),"SetText failed");
591 }
592 
AddText(const OutlinerParaObject & rPObj,bool bAppend)593 void Outliner::AddText( const OutlinerParaObject& rPObj, bool bAppend )
594 {
595     bool bUpdate = pEditEngine->SetUpdateLayout( false );
596 
597     ImplBlockInsertionCallbacks( true );
598     sal_Int32 nPara;
599     if( bFirstParaIsEmpty )
600     {
601         pParaList->Clear();
602         pEditEngine->SetText(rPObj.GetTextObject());
603         nPara = 0;
604         bAppend = false;
605     }
606     else
607     {
608         nPara = pParaList->GetParagraphCount();
609         pEditEngine->InsertParagraph( EE_PARA_APPEND, rPObj.GetTextObject(), bAppend );
610     }
611     bFirstParaIsEmpty = false;
612 
613     for( sal_Int32 n = 0; n < rPObj.Count(); n++ )
614     {
615         if ( n == 0 && bAppend )
616         {
617             // This first "paragraph" was just appended to an existing (incomplete) paragraph.
618             // Since no new paragraph will be added, the assumed increase-by-1 also won't happen.
619             --nPara;
620             continue;
621         }
622 
623         Paragraph* pPara = new Paragraph( rPObj.GetParagraphData(n) );
624         pParaList->Append(std::unique_ptr<Paragraph>(pPara));
625         sal_Int32 nP = nPara+n;
626         DBG_ASSERT(pParaList->GetAbsPos(pPara)==nP,"AddText:Out of sync");
627         ImplInitDepth( nP, pPara->GetDepth(), false );
628     }
629     DBG_ASSERT( pEditEngine->GetParagraphCount()==pParaList->GetParagraphCount(), "SetText: OutOfSync" );
630 
631     ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() );
632 
633     ImplBlockInsertionCallbacks( false );
634     pEditEngine->SetUpdateLayout( bUpdate );
635 }
636 
CalcFieldValue(const SvxFieldItem & rField,sal_Int32 nPara,sal_Int32 nPos,std::optional<Color> & rpTxtColor,std::optional<Color> & rpFldColor,std::optional<FontLineStyle> & rpFldLineStyle)637 OUString Outliner::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor, std::optional<FontLineStyle>& rpFldLineStyle )
638 {
639     if ( !aCalcFieldValueHdl.IsSet() )
640         return OUString( ' ' );
641 
642     EditFieldInfo aFldInfo( this, rField, nPara, nPos );
643     // The FldColor is preset with COL_LIGHTGRAY.
644     if ( rpFldColor )
645         aFldInfo.SetFieldColor( *rpFldColor );
646 
647     aCalcFieldValueHdl.Call( &aFldInfo );
648     if ( aFldInfo.GetTextColor() )
649     {
650         rpTxtColor = *aFldInfo.GetTextColor();
651     }
652 
653     if ( aFldInfo.GetFontLineStyle() )
654     {
655         rpFldLineStyle = *aFldInfo.GetFontLineStyle();
656     }
657 
658     if (aFldInfo.GetFieldColor())
659         rpFldColor = *aFldInfo.GetFieldColor();
660     else
661         rpFldColor.reset();
662 
663     return aFldInfo.GetRepresentation();
664 }
665 
SetStyleSheet(sal_Int32 nPara,SfxStyleSheet * pStyle)666 void Outliner::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle )
667 {
668     Paragraph* pPara = pParaList->GetParagraph( nPara );
669     if (pPara)
670     {
671         pEditEngine->SetStyleSheet( nPara, pStyle );
672         ImplCheckNumBulletItem(  nPara );
673     }
674 }
675 
ImplCheckNumBulletItem(sal_Int32 nPara)676 void Outliner::ImplCheckNumBulletItem( sal_Int32 nPara )
677 {
678     Paragraph* pPara = pParaList->GetParagraph( nPara );
679     if (pPara)
680         pPara->aBulSize.setWidth( -1 );
681 }
682 
ImplSetLevelDependentStyleSheet(sal_Int32 nPara)683 void Outliner::ImplSetLevelDependentStyleSheet( sal_Int32 nPara )
684 {
685 
686     DBG_ASSERT( ( GetOutlinerMode() == OutlinerMode::OutlineObject ) || ( GetOutlinerMode() == OutlinerMode::OutlineView ), "SetLevelDependentStyleSheet: Wrong Mode!" );
687 
688     SfxStyleSheet* pStyle = GetStyleSheet( nPara );
689 
690     if ( !pStyle )
691         return;
692 
693     sal_Int16 nDepth = GetDepth( nPara );
694     if( nDepth < 0 )
695         nDepth = 0;
696 
697     OUString aNewStyleSheetName( pStyle->GetName() );
698     aNewStyleSheetName = aNewStyleSheetName.subView( 0, aNewStyleSheetName.getLength()-1 ) +
699         OUString::number( nDepth+1 );
700     SfxStyleSheet* pNewStyle = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( aNewStyleSheetName, pStyle->GetFamily() ));
701     DBG_ASSERT( pNewStyle, "AutoStyleSheetName - Style not found!" );
702     if ( pNewStyle && ( pNewStyle != GetStyleSheet( nPara ) ) )
703     {
704         SfxItemSet aOldAttrs( GetParaAttribs( nPara ) );
705         SetStyleSheet( nPara, pNewStyle );
706         if ( aOldAttrs.GetItemState( EE_PARA_NUMBULLET ) == SfxItemState::SET )
707         {
708             SfxItemSet aAttrs( GetParaAttribs( nPara ) );
709             aAttrs.Put( aOldAttrs.Get( EE_PARA_NUMBULLET ) );
710             SetParaAttribs( nPara, aAttrs );
711         }
712     }
713 }
714 
ImplInitDepth(sal_Int32 nPara,sal_Int16 nDepth,bool bCreateUndo)715 void Outliner::ImplInitDepth( sal_Int32 nPara, sal_Int16 nDepth, bool bCreateUndo )
716 {
717 
718     DBG_ASSERT( ( nDepth >= gnMinDepth ) && ( nDepth <= nMaxDepth ), "ImplInitDepth - Depth is invalid!" );
719 
720     Paragraph* pPara = pParaList->GetParagraph( nPara );
721     if (!pPara)
722         return;
723     sal_Int16 nOldDepth = pPara->GetDepth();
724     pPara->SetDepth( nDepth );
725 
726     // For IsInUndo attributes and style do not have to be set, there
727     // the old values are restored by the EditEngine.
728     if( IsInUndo() )
729         return;
730 
731     bool bUpdate = pEditEngine->SetUpdateLayout( false );
732 
733     bool bUndo = bCreateUndo && IsUndoEnabled();
734 
735     SfxItemSet aAttrs( pEditEngine->GetParaAttribs( nPara ) );
736     aAttrs.Put( SfxInt16Item( EE_PARA_OUTLLEVEL, nDepth ) );
737     pEditEngine->SetParaAttribs( nPara, aAttrs );
738     ImplCheckNumBulletItem( nPara );
739     ImplCalcBulletText( nPara, false, false );
740 
741     if ( bUndo )
742     {
743         InsertUndo( std::make_unique<OutlinerUndoChangeDepth>( this, nPara, nOldDepth, nDepth ) );
744     }
745 
746     pEditEngine->SetUpdateLayout( bUpdate );
747 }
748 
SetParaAttribs(sal_Int32 nPara,const SfxItemSet & rSet)749 void Outliner::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet )
750 {
751 
752     pEditEngine->SetParaAttribs( nPara, rSet );
753 }
754 
SetCharAttribs(sal_Int32 nPara,const SfxItemSet & rSet)755 void Outliner::SetCharAttribs(sal_Int32 nPara, const SfxItemSet& rSet)
756 {
757     pEditEngine->SetCharAttribs(nPara, rSet);
758 }
759 
Expand(Paragraph const * pPara)760 bool Outliner::Expand( Paragraph const * pPara )
761 {
762     if ( !pParaList->HasHiddenChildren( pPara ) )
763         return false;
764 
765     std::unique_ptr<OLUndoExpand> pUndo;
766     bool bUndo = IsUndoEnabled() && !IsInUndo();
767     if( bUndo )
768     {
769         UndoActionStart( OLUNDO_EXPAND );
770         pUndo.reset( new OLUndoExpand( this, OLUNDO_EXPAND ) );
771         pUndo->nCount = pParaList->GetAbsPos( pPara );
772     }
773     pParaList->Expand( pPara );
774     InvalidateBullet(pParaList->GetAbsPos(pPara));
775     if( bUndo )
776     {
777         InsertUndo( std::move(pUndo) );
778         UndoActionEnd();
779     }
780     return true;
781 }
782 
Collapse(Paragraph const * pPara)783 bool Outliner::Collapse( Paragraph const * pPara )
784 {
785     if ( !pParaList->HasVisibleChildren( pPara ) ) // collapsed
786         return false;
787 
788     std::unique_ptr<OLUndoExpand> pUndo;
789     bool bUndo = false;
790 
791     if( !IsInUndo() && IsUndoEnabled() )
792         bUndo = true;
793     if( bUndo )
794     {
795         UndoActionStart( OLUNDO_COLLAPSE );
796         pUndo.reset( new OLUndoExpand( this, OLUNDO_COLLAPSE ) );
797         pUndo->nCount = pParaList->GetAbsPos( pPara );
798     }
799 
800     pParaList->Collapse( pPara );
801     InvalidateBullet(pParaList->GetAbsPos(pPara));
802     if( bUndo )
803     {
804         InsertUndo( std::move(pUndo) );
805         UndoActionEnd();
806     }
807     return true;
808 }
809 
ImpCalcBulletFont(sal_Int32 nPara) const810 vcl::Font Outliner::ImpCalcBulletFont( sal_Int32 nPara ) const
811 {
812     const SvxNumberFormat* pFmt = GetNumberFormat( nPara );
813     assert(pFmt && "ImpCalcBulletFont: Missing!");
814     DBG_ASSERT(( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) && ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ), "ImpCalcBulletFont: BitmapBullet!" );
815 
816     vcl::Font aStdFont;
817     if ( !pEditEngine->IsFlatMode() )
818     {
819         ESelection aSel( nPara, 0, nPara, 0 );
820         aStdFont = EditEngine::CreateFontFromItemSet( pEditEngine->GetAttribs( aSel ), pEditEngine->GetScriptType( aSel ) );
821     }
822     else
823     {
824         aStdFont = pEditEngine->GetStandardFont( nPara );
825     }
826 
827     vcl::Font aBulletFont;
828     std::optional<vcl::Font> pSourceFont;
829     if ( pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL )
830     {
831         pSourceFont = pFmt->GetBulletFont();
832     }
833 
834     if (pSourceFont)
835     {
836         aBulletFont = *pSourceFont;
837     }
838     else
839     {
840         aBulletFont = aStdFont;
841         aBulletFont.SetUnderline( LINESTYLE_NONE );
842         aBulletFont.SetOverline( LINESTYLE_NONE );
843         aBulletFont.SetStrikeout( STRIKEOUT_NONE );
844         aBulletFont.SetEmphasisMark( FontEmphasisMark::NONE );
845         aBulletFont.SetRelief( FontRelief::NONE );
846     }
847 
848     // Use original scale...
849 
850     double fFontScaleY = pFmt->GetBulletRelSize() / 100.0 * getScalingParameters().fFontY;
851     double fScaledLineHeight = aStdFont.GetFontSize().Height() * fFontScaleY;
852 
853     aBulletFont.SetAlignment( ALIGN_BOTTOM );
854     aBulletFont.SetFontSize(Size(0, basegfx::fround(fScaledLineHeight)));
855     bool bVertical = IsVertical();
856     aBulletFont.SetVertical( bVertical );
857     aBulletFont.SetOrientation( Degree10(bVertical ? (IsTopToBottom() ? 2700 : 900) : 0) );
858 
859     Color aColor( COL_AUTO );
860     if( !pEditEngine->IsFlatMode() && !( pEditEngine->GetControlWord() & EEControlBits::NOCOLORS ) )
861     {
862         aColor = pFmt->GetBulletColor();
863     }
864 
865     if ( ( aColor == COL_AUTO ) || ( IsForceAutoColor() ) )
866         aColor = pEditEngine->GetAutoColor();
867 
868     aBulletFont.SetColor( aColor );
869     return aBulletFont;
870 }
871 
PaintBullet(sal_Int32 nPara,const Point & rStartPos,const Point & rOrigin,Degree10 nOrientation,OutputDevice & rOutDev)872 void Outliner::PaintBullet(sal_Int32 nPara, const Point& rStartPos, const Point& rOrigin,
873                            Degree10 nOrientation, OutputDevice& rOutDev)
874 {
875 
876     bool bDrawBullet = false;
877     if (pEditEngine)
878     {
879         const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE );
880         bDrawBullet = rBulletState.GetValue();
881     }
882 
883     if (!(bDrawBullet && ImplHasNumberFormat(nPara)))
884         return;
885 
886     bool bVertical = IsVertical();
887     bool bTopToBottom = IsTopToBottom();
888 
889     bool bRightToLeftPara = pEditEngine->IsRightToLeft( nPara );
890 
891     tools::Rectangle aBulletArea( ImpCalcBulletArea( nPara, true, false ) );
892 
893     double fSpacingFactorX = getScalingParameters().fSpacingX;
894 
895     tools::Long nStretchBulletX = basegfx::fround<tools::Long>(double(aBulletArea.Left()) * fSpacingFactorX);
896     tools::Long nStretchBulletWidth = basegfx::fround<tools::Long>(double(aBulletArea.GetWidth()) * fSpacingFactorX);
897     aBulletArea = tools::Rectangle(Point(nStretchBulletX, aBulletArea.Top()),
898                              Size(nStretchBulletWidth, aBulletArea.GetHeight()) );
899 
900     Paragraph* pPara = pParaList->GetParagraph( nPara );
901     const SvxNumberFormat* pFmt = GetNumberFormat( nPara );
902     if ( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) )
903     {
904         if( pFmt->GetNumberingType() != SVX_NUM_BITMAP )
905         {
906             vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) );
907             // Use baseline
908             bool bSymbol = pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL;
909             aBulletFont.SetAlignment( bSymbol ? ALIGN_BOTTOM : ALIGN_BASELINE );
910             vcl::Font aOldFont = rOutDev.GetFont();
911             rOutDev.SetFont( aBulletFont );
912 
913             ParagraphInfos  aParaInfos = pEditEngine->GetParagraphInfos( nPara );
914             Point aTextPos;
915             if ( !bVertical )
916             {
917 //                  aTextPos.Y() = rStartPos.Y() + aBulletArea.Bottom();
918                 aTextPos.setY( rStartPos.Y() + ( bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent ) );
919                 if ( !bRightToLeftPara )
920                     aTextPos.setX( rStartPos.X() + aBulletArea.Left() );
921                 else
922                     aTextPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Right() );
923             }
924             else
925             {
926                 if (bTopToBottom)
927                 {
928 //                      aTextPos.X() = rStartPos.X() - aBulletArea.Bottom();
929                     aTextPos.setX( rStartPos.X() - (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent) );
930                     aTextPos.setY( rStartPos.Y() + aBulletArea.Left() );
931                 }
932                 else
933                 {
934                     aTextPos.setX( rStartPos.X() + (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent) );
935                     aTextPos.setY( rStartPos.Y() + aBulletArea.Left() );
936                 }
937             }
938 
939             if ( nOrientation )
940             {
941                 // Both TopLeft and bottom left is not quite correct,
942                 // since in EditEngine baseline ...
943                 rOrigin.RotateAround(aTextPos, nOrientation);
944 
945                 vcl::Font aRotatedFont( aBulletFont );
946                 aRotatedFont.SetOrientation( nOrientation );
947                 rOutDev.SetFont( aRotatedFont );
948             }
949 
950             // VCL will take care of brackets and so on...
951             vcl::text::ComplexTextLayoutFlags nLayoutMode = rOutDev.GetLayoutMode();
952             nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags(vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::BiDiStrong);
953             if ( bRightToLeftPara )
954                 nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft | vcl::text::ComplexTextLayoutFlags::BiDiStrong;
955             rOutDev.SetLayoutMode( nLayoutMode );
956 
957             if(bStrippingPortions)
958             {
959                 const vcl::Font& aSvxFont(rOutDev.GetFont());
960                 KernArray aBuf;
961                 rOutDev.GetTextArray( pPara->GetText(), &aBuf );
962 
963                 if(bSymbol)
964                 {
965                     // aTextPos is Bottom, go to Baseline
966                     FontMetric aMetric(rOutDev.GetFontMetric());
967                     aTextPos.AdjustY( -(aMetric.GetDescent()) );
968                 }
969 
970                 assert(aBuf.get_factor() == 1);
971                 DrawingText(aTextPos, pPara->GetText(), 0, pPara->GetText().getLength(), aBuf.get_subunit_array(), {},
972                     aSvxFont, nPara, bRightToLeftPara ? 1 : 0, nullptr, nullptr, false, false, true, nullptr, Color(), Color());
973             }
974             else
975             {
976                 rOutDev.DrawText( aTextPos, pPara->GetText() );
977             }
978 
979             rOutDev.SetFont( aOldFont );
980         }
981         else
982         {
983             if ( pFmt->GetBrush()->GetGraphicObject() )
984             {
985                 Point aBulletPos;
986                 if ( !bVertical )
987                 {
988                     aBulletPos.setY( rStartPos.Y() + aBulletArea.Top() );
989                     if ( !bRightToLeftPara )
990                         aBulletPos.setX( rStartPos.X() + aBulletArea.Left() );
991                     else
992                         aBulletPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Right() );
993                 }
994                 else
995                 {
996                     if (bTopToBottom)
997                     {
998                         aBulletPos.setX( rStartPos.X() - aBulletArea.Bottom() );
999                         aBulletPos.setY( rStartPos.Y() + aBulletArea.Left() );
1000                     }
1001                     else
1002                     {
1003                         aBulletPos.setX( rStartPos.X() + aBulletArea.Top() );
1004                         aBulletPos.setY( rStartPos.Y() - aBulletArea.Right() );
1005                     }
1006                 }
1007 
1008                 if(bStrippingPortions)
1009                 {
1010                     if(aDrawBulletHdl.IsSet())
1011                     {
1012                         // call something analog to aDrawPortionHdl (if set) and feed it something
1013                         // analog to DrawPortionInfo...
1014                         // created aDrawBulletHdl, Set/GetDrawBulletHdl.
1015                         // created DrawBulletInfo and added handling to sdrtextdecomposition.cxx
1016                         DrawBulletInfo aDrawBulletInfo(
1017                             *pFmt->GetBrush()->GetGraphicObject(),
1018                             aBulletPos,
1019                             pPara->aBulSize);
1020 
1021                         aDrawBulletHdl.Call(&aDrawBulletInfo);
1022                     }
1023                 }
1024                 else
1025                 {
1026                     pFmt->GetBrush()->GetGraphicObject()->Draw(rOutDev, aBulletPos, pPara->aBulSize);
1027                 }
1028             }
1029         }
1030     }
1031 
1032     // In case of collapsed subparagraphs paint a line before the text.
1033     if( !pParaList->HasChildren(pPara) || pParaList->HasVisibleChildren(pPara) ||
1034             bStrippingPortions || nOrientation )
1035         return;
1036 
1037     tools::Long nWidth = rOutDev.PixelToLogic( Size( 10, 0 ) ).Width();
1038 
1039     Point aStartPos, aEndPos;
1040     if ( !bVertical )
1041     {
1042         aStartPos.setY( rStartPos.Y() + aBulletArea.Bottom() );
1043         if ( !bRightToLeftPara )
1044             aStartPos.setX( rStartPos.X() + aBulletArea.Right() );
1045         else
1046             aStartPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Left() );
1047         aEndPos = aStartPos;
1048         aEndPos.AdjustX(nWidth );
1049     }
1050     else
1051     {
1052         aStartPos.setX( rStartPos.X() - aBulletArea.Bottom() );
1053         aStartPos.setY( rStartPos.Y() + aBulletArea.Right() );
1054         aEndPos = aStartPos;
1055         aEndPos.AdjustY(nWidth );
1056     }
1057 
1058     const Color& rOldLineColor = rOutDev.GetLineColor();
1059     rOutDev.SetLineColor( COL_BLACK );
1060     rOutDev.DrawLine( aStartPos, aEndPos );
1061     rOutDev.SetLineColor( rOldLineColor );
1062 }
1063 
InvalidateBullet(sal_Int32 nPara)1064 void Outliner::InvalidateBullet(sal_Int32 nPara)
1065 {
1066     tools::Long nLineHeight = static_cast<tools::Long>(pEditEngine->GetLineHeight(nPara ));
1067     for (OutlinerView* pView : aViewList)
1068     {
1069         Point aPos( pView->pEditView->GetWindowPosTopLeft(nPara ) );
1070         tools::Rectangle aRect( pView->GetOutputArea() );
1071         aRect.SetRight( aPos.X() );
1072         aRect.SetTop( aPos.Y() );
1073         aRect.SetBottom( aPos.Y() );
1074         aRect.AdjustBottom(nLineHeight );
1075 
1076         pView->pEditView->InvalidateWindow(aRect);
1077     }
1078 }
1079 
Read(SvStream & rInput,const OUString & rBaseURL,EETextFormat eFormat,SvKeyValueIterator * pHTTPHeaderAttrs)1080 ErrCode Outliner::Read( SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs )
1081 {
1082 
1083     bool bOldUndo = pEditEngine->IsUndoEnabled();
1084     EnableUndo( false );
1085 
1086     bool bUpdate = pEditEngine->SetUpdateLayout( false );
1087 
1088     Clear();
1089 
1090     ImplBlockInsertionCallbacks( true );
1091     ErrCode nRet = pEditEngine->Read( rInput, rBaseURL, eFormat, pHTTPHeaderAttrs );
1092 
1093     bFirstParaIsEmpty = false;
1094 
1095     sal_Int32 nParas = pEditEngine->GetParagraphCount();
1096     pParaList->Clear();
1097     for ( sal_Int32 n = 0; n < nParas; n++ )
1098     {
1099         std::unique_ptr<Paragraph> pPara(new Paragraph( 0 ));
1100         pParaList->Append(std::move(pPara));
1101     }
1102 
1103     ImpFilterIndents( 0, nParas-1 );
1104 
1105     ImplBlockInsertionCallbacks( false );
1106     pEditEngine->SetUpdateLayout( bUpdate );
1107     EnableUndo( bOldUndo );
1108 
1109     return nRet;
1110 }
1111 
1112 
ImpFilterIndents(sal_Int32 nFirstPara,sal_Int32 nLastPara)1113 void Outliner::ImpFilterIndents( sal_Int32 nFirstPara, sal_Int32 nLastPara )
1114 {
1115     bool bUpdate = pEditEngine->SetUpdateLayout( false );
1116 
1117     Paragraph* pLastConverted = nullptr;
1118     for( sal_Int32 nPara = nFirstPara; nPara <= nLastPara; nPara++ )
1119     {
1120         Paragraph* pPara = pParaList->GetParagraph( nPara );
1121         if (pPara)
1122         {
1123                     if( ImpConvertEdtToOut( nPara ) )
1124                     {
1125                             pLastConverted = pPara;
1126                     }
1127                     else if ( pLastConverted )
1128                     {
1129                             // Arrange normal paragraphs below the heading ...
1130                             pPara->SetDepth( pLastConverted->GetDepth() );
1131                     }
1132 
1133                     ImplInitDepth( nPara, pPara->GetDepth(), false );
1134         }
1135     }
1136 
1137     pEditEngine->SetUpdateLayout( bUpdate );
1138 }
1139 
GetUndoManager()1140 EditUndoManager& Outliner::GetUndoManager()
1141 {
1142     return pEditEngine->GetUndoManager();
1143 }
1144 
SetUndoManager(EditUndoManager * pNew)1145 EditUndoManager* Outliner::SetUndoManager(EditUndoManager* pNew)
1146 {
1147     return pEditEngine->SetUndoManager(pNew);
1148 }
1149 
ImpTextPasted(sal_Int32 nStartPara,sal_Int32 nCount)1150 void Outliner::ImpTextPasted( sal_Int32 nStartPara, sal_Int32 nCount )
1151 {
1152     bool bUpdate = pEditEngine->SetUpdateLayout( false );
1153 
1154     const sal_Int32 nStart = nStartPara;
1155 
1156     Paragraph* pPara = pParaList->GetParagraph( nStartPara );
1157 
1158     while( nCount && pPara )
1159     {
1160         if( GetOutlinerMode() != OutlinerMode::TextObject )
1161         {
1162             nDepthChangedHdlPrevDepth = pPara->GetDepth();
1163             ParaFlag nPrevFlags = pPara->nFlags;
1164 
1165             ImpConvertEdtToOut( nStartPara );
1166 
1167             if( nStartPara == nStart )
1168             {
1169                 // the existing paragraph has changed depth or flags
1170                 if( (pPara->GetDepth() != nDepthChangedHdlPrevDepth) || (pPara->nFlags != nPrevFlags) )
1171                     DepthChangedHdl(pPara, nPrevFlags);
1172             }
1173         }
1174         else // EditEngine mode
1175         {
1176             sal_Int16 nDepth = -1;
1177             const SfxItemSet& rAttrs = pEditEngine->GetParaAttribs( nStartPara );
1178             if ( rAttrs.GetItemState( EE_PARA_OUTLLEVEL ) == SfxItemState::SET )
1179             {
1180                 const SfxInt16Item& rLevel = rAttrs.Get( EE_PARA_OUTLLEVEL );
1181                 nDepth = rLevel.GetValue();
1182             }
1183             if ( nDepth != GetDepth( nStartPara ) )
1184                 ImplInitDepth( nStartPara, nDepth, false );
1185         }
1186 
1187         nCount--;
1188         nStartPara++;
1189         pPara = pParaList->GetParagraph( nStartPara );
1190     }
1191 
1192     pEditEngine->SetUpdateLayout( bUpdate );
1193 
1194     DBG_ASSERT(pParaList->GetParagraphCount()==pEditEngine->GetParagraphCount(),"ImpTextPasted failed");
1195 }
1196 
IndentingPagesHdl(OutlinerView * pView)1197 bool Outliner::IndentingPagesHdl( OutlinerView* pView )
1198 {
1199     if( !aIndentingPagesHdl.IsSet() )
1200         return true;
1201     return aIndentingPagesHdl.Call( pView );
1202 }
1203 
ImpCanIndentSelectedPages(OutlinerView * pCurView)1204 bool Outliner::ImpCanIndentSelectedPages( OutlinerView* pCurView )
1205 {
1206     // The selected pages must already be set in advance through
1207     // ImpCalcSelectedPages
1208 
1209     // If the first paragraph is on level 0 it can not indented in any case,
1210     // possible there might be indentations in the following on the 0 level.
1211     if ( ( mnFirstSelPage == 0 ) && ( GetOutlinerMode() != OutlinerMode::TextObject ) )
1212     {
1213         if ( nDepthChangedHdlPrevDepth == 1 )   // is the only page
1214             return false;
1215         else
1216             (void)pCurView->ImpCalcSelectedPages( false );  // without the first
1217     }
1218     return IndentingPagesHdl( pCurView );
1219 }
1220 
1221 
ImpCanDeleteSelectedPages(OutlinerView * pCurView)1222 bool Outliner::ImpCanDeleteSelectedPages( OutlinerView* pCurView )
1223 {
1224     // The selected pages must already be set in advance through
1225     // ImpCalcSelectedPages
1226     return RemovingPagesHdl( pCurView );
1227 }
1228 
Outliner(SfxItemPool * pPool,OutlinerMode nMode)1229 Outliner::Outliner(SfxItemPool* pPool, OutlinerMode nMode)
1230     : mnFirstSelPage(0)
1231     , nDepthChangedHdlPrevDepth(0)
1232     , nMaxDepth(9)
1233     , bFirstParaIsEmpty(true)
1234     , nBlockInsCallback(0)
1235     , bStrippingPortions(false)
1236     , bPasting(false)
1237 {
1238 
1239     pParaList.reset( new ParagraphList );
1240     pParaList->SetVisibleStateChangedHdl( LINK( this, Outliner, ParaVisibleStateChangedHdl ) );
1241     std::unique_ptr<Paragraph> pPara(new Paragraph( 0 ));
1242     pParaList->Append(std::move(pPara));
1243 
1244     pEditEngine.reset( new OutlinerEditEng( this, pPool ) );
1245     pEditEngine->SetBeginMovingParagraphsHdl( LINK( this, Outliner, BeginMovingParagraphsHdl ) );
1246     pEditEngine->SetEndMovingParagraphsHdl( LINK( this, Outliner, EndMovingParagraphsHdl ) );
1247     pEditEngine->SetBeginPasteOrDropHdl( LINK( this, Outliner, BeginPasteOrDropHdl ) );
1248     pEditEngine->SetEndPasteOrDropHdl( LINK( this, Outliner, EndPasteOrDropHdl ) );
1249 
1250     Init( nMode );
1251 }
1252 
~Outliner()1253 Outliner::~Outliner()
1254 {
1255     pParaList->Clear();
1256     pParaList.reset();
1257     pEditEngine.reset();
1258 }
1259 
InsertView(OutlinerView * pView,size_t nIndex)1260 size_t Outliner::InsertView( OutlinerView* pView, size_t nIndex )
1261 {
1262     size_t ActualIndex;
1263 
1264     if ( nIndex >= aViewList.size() )
1265     {
1266         aViewList.push_back( pView );
1267         ActualIndex = aViewList.size() - 1;
1268     }
1269     else
1270     {
1271         ViewList::iterator it = aViewList.begin();
1272         advance( it, nIndex );
1273         ActualIndex = nIndex;
1274     }
1275     pEditEngine->InsertView(  pView->pEditView.get(), nIndex );
1276     return ActualIndex;
1277 }
1278 
RemoveView(OutlinerView const * pView)1279 void Outliner::RemoveView( OutlinerView const * pView )
1280 {
1281     ViewList::iterator it = std::find(aViewList.begin(), aViewList.end(), pView);
1282     if (it != aViewList.end())
1283     {
1284         pView->pEditView->HideCursor(); // HACK
1285         pEditEngine->RemoveView(  pView->pEditView.get() );
1286         aViewList.erase( it );
1287     }
1288 }
1289 
RemoveView(size_t nIndex)1290 void Outliner::RemoveView( size_t nIndex )
1291 {
1292     EditView* pEditView = pEditEngine->GetView( nIndex );
1293     pEditView->HideCursor(); // HACK
1294 
1295     pEditEngine->RemoveView( nIndex );
1296 
1297     {
1298         ViewList::iterator it = aViewList.begin();
1299         advance( it, nIndex );
1300         aViewList.erase( it );
1301     }
1302 }
1303 
1304 
GetView(size_t nIndex) const1305 OutlinerView* Outliner::GetView( size_t nIndex ) const
1306 {
1307     return ( nIndex >= aViewList.size() ) ? nullptr : aViewList[ nIndex ];
1308 }
1309 
GetViewCount() const1310 size_t Outliner::GetViewCount() const
1311 {
1312     return aViewList.size();
1313 }
1314 
ParagraphInsertedHdl(Paragraph * pPara)1315 void Outliner::ParagraphInsertedHdl(Paragraph* pPara)
1316 {
1317     if( !IsInUndo() )
1318         aParaInsertedHdl.Call( { this, pPara } );
1319 }
1320 
1321 
DepthChangedHdl(Paragraph * pPara,ParaFlag nPrevFlags)1322 void Outliner::DepthChangedHdl(Paragraph* pPara, ParaFlag nPrevFlags)
1323 {
1324     if( !IsInUndo() )
1325         aDepthChangedHdl.Call( { this, pPara, nPrevFlags } );
1326 }
1327 
1328 
GetAbsPos(Paragraph const * pPara) const1329 sal_Int32 Outliner::GetAbsPos( Paragraph const * pPara ) const
1330 {
1331     DBG_ASSERT(pPara,"GetAbsPos:No Para");
1332     return pParaList->GetAbsPos( pPara );
1333 }
1334 
GetParagraphCount() const1335 sal_Int32 Outliner::GetParagraphCount() const
1336 {
1337     return pParaList->GetParagraphCount();
1338 }
1339 
GetParagraph(sal_Int32 nAbsPos) const1340 Paragraph* Outliner::GetParagraph( sal_Int32 nAbsPos ) const
1341 {
1342     return pParaList->GetParagraph( nAbsPos );
1343 }
1344 
HasChildren(Paragraph const * pParagraph) const1345 bool Outliner::HasChildren( Paragraph const * pParagraph ) const
1346 {
1347     return pParaList->HasChildren( pParagraph );
1348 }
1349 
ImplHasNumberFormat(sal_Int32 nPara) const1350 bool Outliner::ImplHasNumberFormat( sal_Int32 nPara ) const
1351 {
1352     return GetNumberFormat(nPara) != nullptr;
1353 }
1354 
GetNumberFormat(sal_Int32 nPara) const1355 const SvxNumberFormat* Outliner::GetNumberFormat( sal_Int32 nPara ) const
1356 {
1357     const SvxNumberFormat* pFmt = nullptr;
1358 
1359     Paragraph* pPara = pParaList->GetParagraph( nPara );
1360     if (!pPara)
1361         return nullptr;
1362 
1363     sal_Int16 nDepth = pPara->GetDepth();
1364 
1365     if( nDepth >= 0 )
1366     {
1367         const SvxNumBulletItem& rNumBullet = pEditEngine->GetParaAttrib( nPara, EE_PARA_NUMBULLET );
1368         if ( rNumBullet.GetNumRule().GetLevelCount() > nDepth )
1369             pFmt = rNumBullet.GetNumRule().Get( nDepth );
1370     }
1371 
1372     return pFmt;
1373 }
1374 
ImplGetBulletSize(sal_Int32 nPara)1375 Size Outliner::ImplGetBulletSize( sal_Int32 nPara )
1376 {
1377     Paragraph* pPara = pParaList->GetParagraph( nPara );
1378     if (!pPara)
1379         return Size();
1380 
1381     if( pPara->aBulSize.Width() == -1 )
1382     {
1383         const SvxNumberFormat* pFmt = GetNumberFormat( nPara );
1384         assert(pFmt && "ImplGetBulletSize - no Bullet!");
1385 
1386         if ( pFmt->GetNumberingType() == SVX_NUM_NUMBER_NONE )
1387         {
1388             pPara->aBulSize = Size( 0, 0 );
1389         }
1390         else if( pFmt->GetNumberingType() != SVX_NUM_BITMAP )
1391         {
1392             OUString aBulletText = ImplGetBulletText( nPara );
1393             OutputDevice* pRefDev = pEditEngine->GetRefDevice();
1394             vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) );
1395             vcl::Font aRefFont( pRefDev->GetFont());
1396             pRefDev->SetFont( aBulletFont );
1397             pPara->aBulSize.setWidth( pRefDev->GetTextWidth( aBulletText ) );
1398             pPara->aBulSize.setHeight( pRefDev->GetTextHeight() );
1399             pRefDev->SetFont( aRefFont );
1400         }
1401         else
1402         {
1403             pPara->aBulSize = OutputDevice::LogicToLogic(pFmt->GetGraphicSize(),
1404                     MapMode(MapUnit::Map100thMM),
1405                     pEditEngine->GetRefDevice()->GetMapMode());
1406         }
1407     }
1408 
1409     return pPara->aBulSize;
1410 }
1411 
ImplCheckParagraphs(sal_Int32 nStart,sal_Int32 nEnd)1412 void Outliner::ImplCheckParagraphs( sal_Int32 nStart, sal_Int32 nEnd )
1413 {
1414 
1415     for ( sal_Int32 n = nStart; n < nEnd; n++ )
1416     {
1417         Paragraph* pPara = pParaList->GetParagraph( n );
1418         if (pPara)
1419         {
1420             pPara->Invalidate();
1421             ImplCalcBulletText( n, false, false );
1422         }
1423     }
1424 }
1425 
SetRefDevice(OutputDevice * pRefDev)1426 void Outliner::SetRefDevice( OutputDevice* pRefDev )
1427 {
1428     pEditEngine->SetRefDevice( pRefDev );
1429     for ( sal_Int32 n = pParaList->GetParagraphCount(); n; )
1430     {
1431         Paragraph* pPara = pParaList->GetParagraph( --n );
1432         pPara->Invalidate();
1433     }
1434 }
1435 
ParaAttribsChanged(sal_Int32 nPara)1436 void Outliner::ParaAttribsChanged( sal_Int32 nPara )
1437 {
1438     // The Outliner does not have an undo of its own, when paragraphs are
1439     // separated/merged. When ParagraphInserted the attribute EE_PARA_OUTLLEVEL
1440     // may not be set, this is however needed when the depth of the paragraph
1441     // is to be determined.
1442     if (!pEditEngine->IsInUndo())
1443         return;
1444     if (pParaList->GetParagraphCount() != pEditEngine->GetParagraphCount())
1445         return;
1446     Paragraph* pPara = pParaList->GetParagraph(nPara);
1447     if (!pPara)
1448         return;
1449     // tdf#100734: force update of bullet
1450     pPara->Invalidate();
1451     const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL );
1452     if (pPara->GetDepth() == rLevel.GetValue())
1453         return;
1454     pPara->SetDepth(rLevel.GetValue());
1455     ImplCalcBulletText(nPara, true, true);
1456 }
1457 
StyleSheetChanged(SfxStyleSheet const * pStyle)1458 void Outliner::StyleSheetChanged( SfxStyleSheet const * pStyle )
1459 {
1460 
1461     // The EditEngine calls StyleSheetChanged also for derived styles.
1462     // Here all the paragraphs, which had the said template, used to be
1463     // hunted by an ImpRecalcParaAttribs, why?
1464     // => only the Bullet-representation can really change...
1465     sal_Int32 nParas = pParaList->GetParagraphCount();
1466     for( sal_Int32 nPara = 0; nPara < nParas; nPara++ )
1467     {
1468         if ( pEditEngine->GetStyleSheet( nPara ) == pStyle )
1469         {
1470             ImplCheckNumBulletItem( nPara );
1471             ImplCalcBulletText( nPara, false, false );
1472             // EditEngine formats changed paragraphs before calling this method,
1473             // so they are not reformatted now and use wrong bullet indent
1474             pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) );
1475         }
1476     }
1477 }
1478 
ImpCalcBulletArea(sal_Int32 nPara,bool bAdjust,bool bReturnPaperPos)1479 tools::Rectangle Outliner::ImpCalcBulletArea( sal_Int32 nPara, bool bAdjust, bool bReturnPaperPos )
1480 {
1481     // Bullet area within the paragraph ...
1482     tools::Rectangle aBulletArea;
1483 
1484     const SvxNumberFormat* pFmt = GetNumberFormat( nPara );
1485     if ( pFmt )
1486     {
1487         Point aTopLeft;
1488         Size aBulletSize( ImplGetBulletSize( nPara ) );
1489 
1490         bool bOutlineMode = bool( pEditEngine->GetControlWord() & EEControlBits::OUTLINER );
1491 
1492         // the ODF attribute text:space-before which holds the spacing to add to the left of the label
1493         const auto nSpaceBefore = pFmt->GetAbsLSpace() + pFmt->GetFirstLineOffset();
1494 
1495         const SvxLRSpaceItem& rLR = pEditEngine->GetParaAttrib( nPara, bOutlineMode ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE );
1496         aTopLeft.setX( rLR.GetTextLeft() + rLR.GetTextFirstLineOffset() + nSpaceBefore );
1497 
1498         tools::Long nBulletWidth = std::max( static_cast<tools::Long>(-rLR.GetTextFirstLineOffset()), static_cast<tools::Long>((-pFmt->GetFirstLineOffset()) + pFmt->GetCharTextDistance()) );
1499         if ( nBulletWidth < aBulletSize.Width() )   // The Bullet creates its space
1500             nBulletWidth = aBulletSize.Width();
1501 
1502         if ( bAdjust && !bOutlineMode )
1503         {
1504             // Adjust when centered or align right
1505             const SvxAdjustItem& rItem = pEditEngine->GetParaAttrib( nPara, EE_PARA_JUST );
1506             if ( ( !pEditEngine->IsRightToLeft( nPara ) && ( rItem.GetAdjust() != SvxAdjust::Left ) ) ||
1507                  ( pEditEngine->IsRightToLeft( nPara ) && ( rItem.GetAdjust() != SvxAdjust::Right ) ) )
1508             {
1509                 aTopLeft.setX( pEditEngine->GetFirstLineStartX( nPara ) - nBulletWidth );
1510             }
1511         }
1512 
1513         // Vertical:
1514         ParagraphInfos aInfos = pEditEngine->GetParagraphInfos( nPara );
1515         if ( aInfos.bValid )
1516         {
1517             aTopLeft.setY( /* aInfos.nFirstLineOffset + */ // nFirstLineOffset is already added to the StartPos (PaintBullet) from the EditEngine
1518                             aInfos.nFirstLineHeight - aInfos.nFirstLineTextHeight
1519                             + aInfos.nFirstLineTextHeight / 2
1520                             - aBulletSize.Height() / 2 );
1521             // may prefer to print out on the baseline ...
1522             if( ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) && ( pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL ) )
1523             {
1524                 vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) );
1525                 if ( aBulletFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL )
1526                 {
1527                     OutputDevice* pRefDev = pEditEngine->GetRefDevice();
1528                     vcl::Font aOldFont = pRefDev->GetFont();
1529                     pRefDev->SetFont( aBulletFont );
1530                     FontMetric aMetric( pRefDev->GetFontMetric() );
1531                     // Leading on the first line ...
1532                     aTopLeft.setY( /* aInfos.nFirstLineOffset + */ aInfos.nFirstLineMaxAscent );
1533                     aTopLeft.AdjustY( -(aMetric.GetAscent()) );
1534                     pRefDev->SetFont( aOldFont );
1535                 }
1536             }
1537         }
1538 
1539         // Horizontal:
1540         if( pFmt->GetNumAdjust() == SvxAdjust::Right )
1541         {
1542             aTopLeft.AdjustX(nBulletWidth - aBulletSize.Width() );
1543         }
1544         else if( pFmt->GetNumAdjust() == SvxAdjust::Center )
1545         {
1546             aTopLeft.AdjustX(( nBulletWidth - aBulletSize.Width() ) / 2 );
1547         }
1548 
1549         if ( aTopLeft.X() < 0 )     // then push
1550             aTopLeft.setX( 0 );
1551 
1552         aBulletArea = tools::Rectangle( aTopLeft, aBulletSize );
1553     }
1554     if ( bReturnPaperPos )
1555     {
1556         Size aBulletSize( aBulletArea.GetSize() );
1557         Point aBulletDocPos( aBulletArea.TopLeft() );
1558         aBulletDocPos.AdjustY(pEditEngine->GetDocPosTopLeft( nPara ).Y() );
1559         Point aBulletPos( aBulletDocPos );
1560 
1561         if ( IsVertical() )
1562         {
1563             aBulletPos.setY( aBulletDocPos.X() );
1564             aBulletPos.setX( GetPaperSize().Width() - aBulletDocPos.Y() );
1565             // Rotate:
1566             aBulletPos.AdjustX( -(aBulletSize.Height()) );
1567             Size aSz( aBulletSize );
1568             aBulletSize.setWidth( aSz.Height() );
1569             aBulletSize.setHeight( aSz.Width() );
1570         }
1571         else if ( pEditEngine->IsRightToLeft( nPara ) )
1572         {
1573             aBulletPos.setX( GetPaperSize().Width() - aBulletDocPos.X() - aBulletSize.Width() );
1574         }
1575 
1576         aBulletArea = tools::Rectangle( aBulletPos, aBulletSize );
1577     }
1578     return aBulletArea;
1579 }
1580 
GetBulletInfo(sal_Int32 nPara)1581 EBulletInfo Outliner::GetBulletInfo( sal_Int32 nPara )
1582 {
1583     EBulletInfo aInfo;
1584 
1585     aInfo.nParagraph = nPara;
1586     aInfo.bVisible = ImplHasNumberFormat( nPara );
1587 
1588     const SvxNumberFormat* pFmt = GetNumberFormat( nPara );
1589     aInfo.nType = pFmt ? pFmt->GetNumberingType() : 0;
1590 
1591     if( pFmt )
1592     {
1593         if( pFmt->GetNumberingType() != SVX_NUM_BITMAP )
1594         {
1595             aInfo.aText = ImplGetBulletText( nPara );
1596 
1597             if( pFmt->GetBulletFont() )
1598                 aInfo.aFont = *pFmt->GetBulletFont();
1599         }
1600     }
1601 
1602     if ( aInfo.bVisible )
1603     {
1604         aInfo.aBounds = ImpCalcBulletArea( nPara, true, true );
1605     }
1606 
1607     return aInfo;
1608 }
1609 
GetText(Paragraph const * pParagraph,sal_Int32 nCount) const1610 OUString Outliner::GetText( Paragraph const * pParagraph, sal_Int32 nCount ) const
1611 {
1612 
1613     OUStringBuffer aText(128);
1614     sal_Int32 nStartPara = pParaList->GetAbsPos( pParagraph );
1615     for ( sal_Int32 n = 0; n < nCount; n++ )
1616     {
1617         aText.append(pEditEngine->GetText( nStartPara + n ));
1618         if ( (n+1) < nCount )
1619             aText.append("\n");
1620     }
1621     return aText.makeStringAndClear();
1622 }
1623 
Remove(Paragraph const * pPara,sal_Int32 nParaCount)1624 void Outliner::Remove( Paragraph const * pPara, sal_Int32 nParaCount )
1625 {
1626 
1627     sal_Int32 nPos = pParaList->GetAbsPos( pPara );
1628     if( !nPos && ( nParaCount >= pParaList->GetParagraphCount() ) )
1629     {
1630         Clear();
1631     }
1632     else
1633     {
1634         for( sal_Int32 n = 0; n < nParaCount; n++ )
1635             pEditEngine->RemoveParagraph( nPos );
1636     }
1637 }
1638 
StripPortions()1639 void Outliner::StripPortions()
1640 {
1641     bStrippingPortions = true;
1642     pEditEngine->StripPortions();
1643     bStrippingPortions = false;
1644 }
1645 
DrawingText(const Point & rStartPos,const OUString & rText,sal_Int32 nTextStart,sal_Int32 nTextLen,std::span<const sal_Int32> pDXArray,std::span<const sal_Bool> pKashidaArray,const SvxFont & rFont,sal_Int32 nPara,sal_uInt8 nRightToLeft,const EEngineData::WrongSpellVector * pWrongSpellVector,const SvxFieldData * pFieldData,bool bEndOfLine,bool bEndOfParagraph,bool bEndOfBullet,const css::lang::Locale * pLocale,const Color & rOverlineColor,const Color & rTextLineColor)1646 void Outliner::DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart,
1647                             sal_Int32 nTextLen, std::span<const sal_Int32> pDXArray,
1648                             std::span<const sal_Bool> pKashidaArray, const SvxFont& rFont,
1649                             sal_Int32 nPara, sal_uInt8 nRightToLeft,
1650                             const EEngineData::WrongSpellVector* pWrongSpellVector,
1651                             const SvxFieldData* pFieldData,
1652                             bool bEndOfLine,
1653                             bool bEndOfParagraph,
1654                             bool bEndOfBullet,
1655                             const css::lang::Locale* pLocale,
1656                             const Color& rOverlineColor,
1657                             const Color& rTextLineColor)
1658 {
1659     if(aDrawPortionHdl.IsSet())
1660     {
1661         DrawPortionInfo aInfo( rStartPos, rText, nTextStart, nTextLen, rFont, nPara, pDXArray, pKashidaArray, pWrongSpellVector,
1662             pFieldData, pLocale, rOverlineColor, rTextLineColor, nRightToLeft, false, 0, bEndOfLine, bEndOfParagraph, bEndOfBullet);
1663 
1664         aDrawPortionHdl.Call( &aInfo );
1665     }
1666 }
1667 
DrawingTab(const Point & rStartPos,tools::Long nWidth,const OUString & rChar,const SvxFont & rFont,sal_Int32 nPara,sal_uInt8 nRightToLeft,bool bEndOfLine,bool bEndOfParagraph,const Color & rOverlineColor,const Color & rTextLineColor)1668 void Outliner::DrawingTab( const Point& rStartPos, tools::Long nWidth, const OUString& rChar, const SvxFont& rFont,
1669     sal_Int32 nPara, sal_uInt8 nRightToLeft, bool bEndOfLine, bool bEndOfParagraph,
1670     const Color& rOverlineColor, const Color& rTextLineColor)
1671 {
1672     if(aDrawPortionHdl.IsSet())
1673     {
1674         DrawPortionInfo aInfo( rStartPos, rChar, 0, rChar.getLength(), rFont, nPara, {}, {}, nullptr,
1675             nullptr, nullptr, rOverlineColor, rTextLineColor, nRightToLeft, true, nWidth, bEndOfLine, bEndOfParagraph, false);
1676 
1677         aDrawPortionHdl.Call( &aInfo );
1678     }
1679 }
1680 
RemovingPagesHdl(OutlinerView * pView)1681 bool Outliner::RemovingPagesHdl( OutlinerView* pView )
1682 {
1683     return !aRemovingPagesHdl.IsSet() || aRemovingPagesHdl.Call( pView );
1684 }
1685 
ImpCanDeleteSelectedPages(OutlinerView * pCurView,sal_Int32 _nFirstPage,sal_Int32 nPages)1686 bool Outliner::ImpCanDeleteSelectedPages( OutlinerView* pCurView, sal_Int32 _nFirstPage, sal_Int32 nPages )
1687 {
1688 
1689     nDepthChangedHdlPrevDepth = nPages;
1690     mnFirstSelPage = _nFirstPage;
1691     return RemovingPagesHdl( pCurView );
1692 }
1693 
GetParaAttribs(sal_Int32 nPara) const1694 SfxItemSet const & Outliner::GetParaAttribs( sal_Int32 nPara ) const
1695 {
1696     return pEditEngine->GetParaAttribs( nPara );
1697 }
1698 
IMPL_LINK(Outliner,ParaVisibleStateChangedHdl,Paragraph &,rPara,void)1699 IMPL_LINK( Outliner, ParaVisibleStateChangedHdl, Paragraph&, rPara, void )
1700 {
1701     sal_Int32 nPara = pParaList->GetAbsPos( &rPara );
1702     pEditEngine->ShowParagraph( nPara, rPara.IsVisible() );
1703 }
1704 
IMPL_LINK_NOARG(Outliner,BeginMovingParagraphsHdl,MoveParagraphsInfo &,void)1705 IMPL_LINK_NOARG(Outliner, BeginMovingParagraphsHdl, MoveParagraphsInfo&, void)
1706 {
1707     if( !IsInUndo() )
1708         aBeginMovingHdl.Call( this );
1709 }
1710 
IMPL_LINK(Outliner,BeginPasteOrDropHdl,PasteOrDropInfos &,rInfos,void)1711 IMPL_LINK( Outliner, BeginPasteOrDropHdl, PasteOrDropInfos&, rInfos, void )
1712 {
1713     UndoActionStart( EDITUNDO_DRAGANDDROP );
1714     maBeginPasteOrDropHdl.Call(&rInfos);
1715 }
1716 
IMPL_LINK(Outliner,EndPasteOrDropHdl,PasteOrDropInfos &,rInfos,void)1717 IMPL_LINK( Outliner, EndPasteOrDropHdl, PasteOrDropInfos&, rInfos, void )
1718 {
1719     bPasting = false;
1720     ImpTextPasted( rInfos.nStartPara, rInfos.nEndPara - rInfos.nStartPara + 1 );
1721     maEndPasteOrDropHdl.Call( &rInfos );
1722     UndoActionEnd();
1723 }
1724 
IMPL_LINK(Outliner,EndMovingParagraphsHdl,MoveParagraphsInfo &,rInfos,void)1725 IMPL_LINK( Outliner, EndMovingParagraphsHdl, MoveParagraphsInfo&, rInfos, void )
1726 {
1727     pParaList->MoveParagraphs( rInfos.nStartPara, rInfos.nDestPara, rInfos.nEndPara - rInfos.nStartPara + 1 );
1728     sal_Int32 nChangesStart = std::min( rInfos.nStartPara, rInfos.nDestPara );
1729     sal_Int32 nParas = pParaList->GetParagraphCount();
1730     for ( sal_Int32 n = nChangesStart; n < nParas; n++ )
1731         ImplCalcBulletText( n, false, false );
1732 
1733     if( !IsInUndo() )
1734         aEndMovingHdl.Call( this );
1735 }
1736 
isSameNumbering(const SvxNumberFormat & rN1,const SvxNumberFormat & rN2)1737 static bool isSameNumbering( const SvxNumberFormat& rN1, const SvxNumberFormat& rN2 )
1738 {
1739     if( rN1.GetNumberingType() != rN2.GetNumberingType() )
1740         return false;
1741 
1742     if( rN1.GetNumStr(1) != rN2.GetNumStr(1) )
1743         return false;
1744 
1745     if( (rN1.GetPrefix() != rN2.GetPrefix()) || (rN1.GetSuffix() != rN2.GetSuffix()) )
1746         return false;
1747 
1748     return true;
1749 }
1750 
ImplGetNumbering(sal_Int32 nPara,const SvxNumberFormat * pParaFmt)1751 sal_uInt16 Outliner::ImplGetNumbering( sal_Int32 nPara, const SvxNumberFormat* pParaFmt )
1752 {
1753     sal_uInt16 nNumber = pParaFmt->GetStart() - 1;
1754 
1755     Paragraph* pPara = pParaList->GetParagraph( nPara );
1756     const sal_Int16 nParaDepth = pPara->GetDepth();
1757 
1758     do
1759     {
1760         pPara = pParaList->GetParagraph( nPara );
1761         const sal_Int16 nDepth = pPara->GetDepth();
1762 
1763         // ignore paragraphs that are below our paragraph or have no numbering
1764         if( (nDepth > nParaDepth) || (nDepth == -1) )
1765             continue;
1766 
1767         // stop on paragraphs that are above our paragraph
1768         if( nDepth < nParaDepth )
1769             break;
1770 
1771         const SvxNumberFormat* pFmt = GetNumberFormat( nPara );
1772 
1773         if( pFmt == nullptr )
1774             continue; // ignore paragraphs without bullets
1775 
1776         // check if numbering less than or equal to pParaFmt
1777         if( !isSameNumbering( *pFmt, *pParaFmt ) || ( pFmt->GetStart() < pParaFmt->GetStart() ) )
1778             break;
1779 
1780         if (  pFmt->GetStart() > pParaFmt->GetStart() )
1781         {
1782            nNumber += pFmt->GetStart() - pParaFmt->GetStart();
1783            pParaFmt = pFmt;
1784         }
1785 
1786         const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE );
1787 
1788         if( rBulletState.GetValue() )
1789             nNumber += 1;
1790 
1791         // same depth, same number format, check for restart
1792         const sal_Int16 nNumberingStartValue = pPara->GetNumberingStartValue();
1793         if( (nNumberingStartValue != -1) || pPara->IsParaIsNumberingRestart() )
1794         {
1795             if( nNumberingStartValue != -1 )
1796                 nNumber += nNumberingStartValue - 1;
1797             break;
1798         }
1799     }
1800     while( nPara-- );
1801 
1802     return nNumber;
1803 }
1804 
ImplCalcBulletText(sal_Int32 nPara,bool bRecalcLevel,bool bRecalcChildren)1805 void Outliner::ImplCalcBulletText( sal_Int32 nPara, bool bRecalcLevel, bool bRecalcChildren )
1806 {
1807 
1808     Paragraph* pPara = pParaList->GetParagraph( nPara );
1809 
1810     while ( pPara )
1811     {
1812         OUString aBulletText;
1813         const SvxNumberFormat* pFmt = GetNumberFormat( nPara );
1814         if( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) )
1815         {
1816             aBulletText += pFmt->GetPrefix();
1817             if( pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL )
1818             {
1819                 sal_UCS4 cChar = pFmt->GetBulletChar();
1820                 aBulletText += OUString(&cChar, 1);
1821             }
1822             else if( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE )
1823             {
1824                 aBulletText += pFmt->GetNumStr( ImplGetNumbering( nPara, pFmt ) );
1825             }
1826             aBulletText += pFmt->GetSuffix();
1827         }
1828 
1829         if (pPara->GetText() != aBulletText)
1830             pPara->SetText( aBulletText );
1831 
1832         if ( bRecalcLevel )
1833         {
1834             sal_Int16 nDepth = pPara->GetDepth();
1835             pPara = pParaList->GetParagraph( ++nPara );
1836             if ( !bRecalcChildren )
1837             {
1838                 while ( pPara && ( pPara->GetDepth() > nDepth ) )
1839                     pPara = pParaList->GetParagraph( ++nPara );
1840             }
1841 
1842             if ( pPara && ( pPara->GetDepth() < nDepth ) )
1843                 pPara = nullptr;
1844         }
1845         else
1846         {
1847             pPara = nullptr;
1848         }
1849     }
1850 }
1851 
Clear()1852 void Outliner::Clear()
1853 {
1854 
1855     if( !bFirstParaIsEmpty )
1856     {
1857         ImplBlockInsertionCallbacks( true );
1858         pEditEngine->Clear();
1859         pParaList->Clear();
1860         pParaList->Append( std::unique_ptr<Paragraph>(new Paragraph( gnMinDepth )));
1861         bFirstParaIsEmpty = true;
1862         ImplBlockInsertionCallbacks( false );
1863     }
1864     else
1865     {
1866             Paragraph* pPara = pParaList->GetParagraph( 0 );
1867             if(pPara)
1868                 pPara->SetDepth( gnMinDepth );
1869     }
1870 }
1871 
SetFlatMode(bool bFlat)1872 void Outliner::SetFlatMode( bool bFlat )
1873 {
1874 
1875     if( bFlat != pEditEngine->IsFlatMode() )
1876     {
1877         for ( sal_Int32 nPara = pParaList->GetParagraphCount(); nPara; )
1878             pParaList->GetParagraph( --nPara )->aBulSize.setWidth( -1 );
1879 
1880         pEditEngine->SetFlatMode( bFlat );
1881     }
1882 }
1883 
ImplGetBulletText(sal_Int32 nPara)1884 OUString Outliner::ImplGetBulletText( sal_Int32 nPara )
1885 {
1886     OUString aRes;
1887     Paragraph* pPara = pParaList->GetParagraph( nPara );
1888     if (pPara)
1889     {
1890         ImplCalcBulletText( nPara, false, false );
1891         aRes = pPara->GetText();
1892     }
1893     return aRes;
1894 }
1895 
1896 // this is needed for StarOffice Api
SetLevelDependentStyleSheet(sal_Int32 nPara)1897 void Outliner::SetLevelDependentStyleSheet( sal_Int32 nPara )
1898 {
1899     SfxItemSet aOldAttrs( pEditEngine->GetParaAttribs( nPara ) );
1900     ImplSetLevelDependentStyleSheet( nPara );
1901     pEditEngine->SetParaAttribs( nPara, aOldAttrs );
1902 }
1903 
ImplBlockInsertionCallbacks(bool b)1904 void Outliner::ImplBlockInsertionCallbacks( bool b )
1905 {
1906     if ( b )
1907     {
1908         nBlockInsCallback++;
1909     }
1910     else
1911     {
1912         DBG_ASSERT( nBlockInsCallback, "ImplBlockInsertionCallbacks ?!" );
1913         nBlockInsCallback--;
1914         if ( !nBlockInsCallback )
1915         {
1916             // Call blocked notify events...
1917             while(!pEditEngine->aNotifyCache.empty())
1918             {
1919                 EENotify aNotify(pEditEngine->aNotifyCache.front());
1920                 // Remove from list before calling, maybe we enter LeaveBlockNotifications while calling the handler...
1921                 pEditEngine->aNotifyCache.erase(pEditEngine->aNotifyCache.begin());
1922                 pEditEngine->aOutlinerNotifyHdl.Call( aNotify );
1923             }
1924         }
1925     }
1926 }
1927 
IMPL_LINK(Outliner,EditEngineNotifyHdl,EENotify &,rNotify,void)1928 IMPL_LINK( Outliner, EditEngineNotifyHdl, EENotify&, rNotify, void )
1929 {
1930     if ( !nBlockInsCallback )
1931         pEditEngine->aOutlinerNotifyHdl.Call( rNotify );
1932     else
1933         pEditEngine->aNotifyCache.push_back(rNotify);
1934 }
1935 
1936 /** sets a link that is called at the beginning of a drag operation at an edit view */
SetBeginDropHdl(const Link<EditView *,void> & rLink)1937 void Outliner::SetBeginDropHdl( const Link<EditView*,void>& rLink )
1938 {
1939     pEditEngine->SetBeginDropHdl( rLink );
1940 }
1941 
1942 /** sets a link that is called at the end of a drag operation at an edit view */
SetEndDropHdl(const Link<EditView *,void> & rLink)1943 void Outliner::SetEndDropHdl( const Link<EditView*,void>& rLink )
1944 {
1945     pEditEngine->SetEndDropHdl( rLink );
1946 }
1947 
1948 /** sets a link that is called before a drop or paste operation. */
SetBeginPasteOrDropHdl(const Link<PasteOrDropInfos *,void> & rLink)1949 void Outliner::SetBeginPasteOrDropHdl( const Link<PasteOrDropInfos*,void>& rLink )
1950 {
1951     maBeginPasteOrDropHdl = rLink;
1952 }
1953 
1954 /** sets a link that is called after a drop or paste operation. */
SetEndPasteOrDropHdl(const Link<PasteOrDropInfos *,void> & rLink)1955 void Outliner::SetEndPasteOrDropHdl( const Link<PasteOrDropInfos*,void>& rLink )
1956 {
1957     maEndPasteOrDropHdl = rLink;
1958 }
1959 
SetParaFlag(Paragraph * pPara,ParaFlag nFlag)1960 void Outliner::SetParaFlag( Paragraph* pPara,  ParaFlag nFlag )
1961 {
1962     if( pPara && !pPara->HasFlag( nFlag ) )
1963     {
1964         if( IsUndoEnabled() && !IsInUndo() )
1965             InsertUndo( std::make_unique<OutlinerUndoChangeParaFlags>( this, GetAbsPos( pPara ), pPara->nFlags, pPara->nFlags|nFlag ) );
1966 
1967         pPara->SetFlag( nFlag );
1968     }
1969 }
1970 
HasParaFlag(const Paragraph * pPara,ParaFlag nFlag)1971 bool Outliner::HasParaFlag( const Paragraph* pPara, ParaFlag nFlag )
1972 {
1973     return pPara && pPara->HasFlag( nFlag );
1974 }
1975 
1976 
IsPageOverflow()1977 bool Outliner::IsPageOverflow()
1978 {
1979     return pEditEngine->IsPageOverflow();
1980 }
1981 
GetNonOverflowingText() const1982 std::optional<NonOverflowingText> Outliner::GetNonOverflowingText() const
1983 {
1984     /* XXX:
1985      * nCount should be the number of paragraphs of the non overflowing text
1986      * nStart should be the starting paragraph of the non overflowing text (XXX: Always 0?)
1987     */
1988 
1989     if ( GetParagraphCount() < 1 )
1990         return {};
1991 
1992     // last non-overflowing paragraph is before the first overflowing one
1993     sal_Int32 nCount = pEditEngine->GetOverflowingParaNum();
1994     sal_Int32 nOverflowLine = pEditEngine->GetOverflowingLineNum(); // XXX: Unused for now
1995 
1996     // Defensive check: overflowing para index beyond actual # of paragraphs?
1997     if ( nCount > GetParagraphCount()-1) {
1998         SAL_INFO("editeng.chaining",
1999                  "[Overflowing] Ops, trying to retrieve para "
2000                  << nCount << " when max index is " << GetParagraphCount()-1 );
2001         return {};
2002     }
2003 
2004     if (nCount < 0)
2005     {
2006         SAL_INFO("editeng.chaining",
2007                  "[Overflowing] No Overflowing text but GetNonOverflowinText called?!");
2008         return {};
2009     }
2010 
2011     // NOTE: We want the selection of the overflowing text from here
2012     //       At the same time we may want to consider the beginning of such text
2013     //       in a more fine grained way (i.e. as GetNonOverflowingText did)
2014 
2015 /*
2016     sal_Int32 nHeadPara = pEditEngine->GetOverflowingParaNum();
2017     sal_uInt32 nParaCount = GetParagraphCount();
2018 
2019     sal_uInt32 nLen = 0;
2020     for ( sal_Int32 nLine = 0;
2021           nLine < pEditEngine->GetOverflowingLineNum();
2022           nLine++) {
2023         nLen += GetLineLen(nHeadPara, nLine);
2024     }
2025 
2026     sal_uInt32 nOverflowingPara = pEditEngine->GetOverflowingParaNum();
2027     ESelection aOverflowingTextSel;
2028     sal_Int32 nLastPara = nParaCount-1;
2029     sal_Int32 nLastParaLen = GetText(GetParagraph(nLastPara)).getLength();
2030     aOverflowingTextSel = ESelection(nOverflowingPara, nLen,
2031                                      nLastPara, nLastParaLen);
2032     bool bLastParaInterrupted =
2033             pEditEngine->GetOverflowingLineNum() > 0;
2034 
2035     return new NonOverflowingText(aOverflowingTextSel, bLastParaInterrupted);
2036     **/
2037 
2038 
2039     // Only overflowing text, i.e. 1st line of 1st paragraph overflowing
2040     bool bItAllOverflew = nCount == 0 && nOverflowLine == 0;
2041     if ( bItAllOverflew )
2042     {
2043         ESelection aEmptySel(0,0,0,0);
2044         //EditTextObject *pTObj = pEditEngine->CreateTextObject(aEmptySel);
2045         bool const bLastParaInterrupted = true; // Last Para was interrupted since everything overflew
2046         return NonOverflowingText(aEmptySel, bLastParaInterrupted);
2047     } else { // Get the lines that of the overflowing para fit in the box
2048 
2049         sal_Int32 nOverflowingPara = nCount;
2050         sal_uInt32 nLen = 0;
2051 
2052         for ( sal_Int32 nLine = 0;
2053               nLine < pEditEngine->GetOverflowingLineNum();
2054               nLine++)
2055         {
2056             nLen += GetLineLen(nOverflowingPara, nLine);
2057         }
2058 
2059         //sal_Int32 nStartPara = 0;
2060         //sal_Int32 nStartPos = 0;
2061         ESelection aOverflowingTextSelection;
2062 
2063         const sal_Int32 nEndPara = GetParagraphCount()-1;
2064         const sal_Int32 nEndPos = pEditEngine->GetTextLen(nEndPara);
2065 
2066         if (nLen == 0) {
2067             // XXX: What happens inside this case might be dependent on the joining paragraph or not-thingy
2068             // Overflowing paragraph is empty or first line overflowing: it's not "Non-Overflowing" text then
2069             sal_Int32 nParaLen = GetText(GetParagraph(nOverflowingPara-1)).getLength();
2070             aOverflowingTextSelection =
2071                 ESelection(nOverflowingPara-1, nParaLen, nEndPara, nEndPos);
2072         } else {
2073             // We take until we have to from the overflowing paragraph
2074             aOverflowingTextSelection =
2075                 ESelection(nOverflowingPara, nLen, nEndPara, nEndPos);
2076         }
2077         //EditTextObject *pTObj = pEditEngine->CreateTextObject(aNonOverflowingTextSelection);
2078 
2079         //sal_Int32 nLastLine = GetLineCount(nOverflowingPara)-1;
2080         bool bLastParaInterrupted =
2081             pEditEngine->GetOverflowingLineNum() > 0;
2082 
2083         return NonOverflowingText(aOverflowingTextSelection, bLastParaInterrupted);
2084     }
2085 }
2086 
GetEmptyParaObject() const2087 OutlinerParaObject Outliner::GetEmptyParaObject() const
2088 {
2089     std::unique_ptr<EditTextObject> pEmptyText = pEditEngine->GetEmptyTextObject();
2090     OutlinerParaObject aPObj( std::move(pEmptyText) );
2091     aPObj.SetOutlinerMode(GetOutlinerMode());
2092     return aPObj;
2093 }
2094 
GetOverflowingText() const2095 std::optional<OverflowingText> Outliner::GetOverflowingText() const
2096 {
2097     if ( pEditEngine->GetOverflowingParaNum() < 0)
2098         return {};
2099 
2100 
2101     // Defensive check: overflowing para index beyond actual # of paragraphs?
2102     if ( pEditEngine->GetOverflowingParaNum() > GetParagraphCount()-1) {
2103         SAL_INFO("editeng.chaining",
2104                  "[Overflowing] Ops, trying to retrieve para "
2105                  << pEditEngine->GetOverflowingParaNum() << " when max index is "
2106                  << GetParagraphCount()-1 );
2107         return {};
2108     }
2109 
2110     sal_Int32 nHeadPara = pEditEngine->GetOverflowingParaNum();
2111     sal_uInt32 nParaCount = GetParagraphCount();
2112 
2113     sal_uInt32 nLen = 0;
2114     for ( sal_Int32 nLine = 0;
2115           nLine < pEditEngine->GetOverflowingLineNum();
2116           nLine++) {
2117         nLen += GetLineLen(nHeadPara, nLine);
2118     }
2119 
2120     sal_uInt32 nOverflowingPara = pEditEngine->GetOverflowingParaNum();
2121     ESelection aOverflowingTextSel;
2122     sal_Int32 nLastPara = nParaCount-1;
2123     sal_Int32 nLastParaLen = GetText(GetParagraph(nLastPara)).getLength();
2124     aOverflowingTextSel = ESelection(nOverflowingPara, nLen,
2125                                      nLastPara, nLastParaLen);
2126     return OverflowingText(pEditEngine->CreateTransferable(aOverflowingTextSel));
2127 
2128 }
2129 
ClearOverflowingParaNum()2130 void Outliner::ClearOverflowingParaNum()
2131 {
2132     pEditEngine->ClearOverflowingParaNum();
2133 }
2134 
dumpAsXml(xmlTextWriterPtr pWriter) const2135 void Outliner::dumpAsXml(xmlTextWriterPtr pWriter) const
2136 {
2137     bool bOwns = false;
2138     if (!pWriter)
2139     {
2140         pWriter = xmlNewTextWriterFilename("outliner.xml", 0);
2141         xmlTextWriterSetIndent(pWriter,1);
2142         (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST("  "));
2143         (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr);
2144         bOwns = true;
2145     }
2146 
2147     (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Outliner"));
2148     pParaList->dumpAsXml(pWriter);
2149     (void)xmlTextWriterEndElement(pWriter);
2150 
2151     if (bOwns)
2152     {
2153        (void)xmlTextWriterEndDocument(pWriter);
2154        xmlFreeTextWriter(pWriter);
2155     }
2156 }
2157 
2158 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2159