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 
21 #include <vcl/svapp.hxx>
22 #include <vcl/window.hxx>
23 
24 #include <svl/srchitem.hxx>
25 #include <editeng/lspcitem.hxx>
26 #include <editeng/adjustitem.hxx>
27 #include <editeng/tstpitem.hxx>
28 
29 #include "eertfpar.hxx"
30 #include <editeng/editeng.hxx>
31 #include "impedit.hxx"
32 #include <editeng/editview.hxx>
33 #include "eehtml.hxx"
34 #include "editobj2.hxx"
35 #include <i18nlangtag/lang.h>
36 #include <sal/log.hxx>
37 #include <o3tl/safeint.hxx>
38 #include <osl/diagnose.h>
39 
40 #include <editxml.hxx>
41 
42 #include <editeng/autokernitem.hxx>
43 #include <editeng/contouritem.hxx>
44 #include <editeng/colritem.hxx>
45 #include <editeng/crossedoutitem.hxx>
46 #include <editeng/escapementitem.hxx>
47 #include <editeng/fhgtitem.hxx>
48 #include <editeng/fontitem.hxx>
49 #include <editeng/kernitem.hxx>
50 #include <editeng/lrspitem.hxx>
51 #include <editeng/postitem.hxx>
52 #include <editeng/shdditem.hxx>
53 #include <editeng/udlnitem.hxx>
54 #include <editeng/ulspitem.hxx>
55 #include <editeng/wghtitem.hxx>
56 #include <editeng/langitem.hxx>
57 #include <editeng/charreliefitem.hxx>
58 #include <editeng/frmdiritem.hxx>
59 #include <editeng/emphasismarkitem.hxx>
60 #include "textconv.hxx"
61 #include <rtl/tencinfo.h>
62 #include <svtools/rtfout.hxx>
63 #include <tools/stream.hxx>
64 #include <edtspell.hxx>
65 #include <editeng/unolingu.hxx>
66 #include <com/sun/star/linguistic2/XThesaurus.hpp>
67 #include <com/sun/star/i18n/ScriptType.hpp>
68 #include <com/sun/star/i18n/WordType.hpp>
69 #include <unotools/transliterationwrapper.hxx>
70 #include <unotools/textsearch.hxx>
71 #include <comphelper/processfactory.hxx>
72 #include <vcl/help.hxx>
73 #include <vcl/metric.hxx>
74 #include <svtools/rtfkeywd.hxx>
75 #include <editeng/edtdlg.hxx>
76 
77 #include <memory>
78 #include <unordered_map>
79 #include <vector>
80 
81 using namespace ::com::sun::star;
82 using namespace ::com::sun::star::uno;
83 using namespace ::com::sun::star::beans;
84 using namespace ::com::sun::star::linguistic2;
85 
86 
Read(SvStream & rInput,const OUString & rBaseURL,EETextFormat eFormat,const EditSelection & rSel,SvKeyValueIterator * pHTTPHeaderAttrs)87 EditPaM ImpEditEngine::Read(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, const EditSelection& rSel, SvKeyValueIterator* pHTTPHeaderAttrs)
88 {
89     bool _bUpdate = GetUpdateMode();
90     SetUpdateMode( false );
91     EditPaM aPaM;
92     if ( eFormat == EETextFormat::Text )
93         aPaM = ReadText( rInput, rSel );
94     else if ( eFormat == EETextFormat::Rtf )
95         aPaM = ReadRTF( rInput, rSel );
96     else if ( eFormat == EETextFormat::Xml )
97         aPaM = ReadXML( rInput, rSel );
98     else if ( eFormat == EETextFormat::Html )
99         aPaM = ReadHTML( rInput, rBaseURL, rSel, pHTTPHeaderAttrs );
100     else
101     {
102         OSL_FAIL( "Read: Unknown Format" );
103     }
104 
105     FormatFullDoc();        // perhaps a simple format is enough?
106     SetUpdateMode( _bUpdate );
107 
108     return aPaM;
109 }
110 
ReadText(SvStream & rInput,EditSelection aSel)111 EditPaM ImpEditEngine::ReadText( SvStream& rInput, EditSelection aSel )
112 {
113     if ( aSel.HasRange() )
114         aSel = ImpDeleteSelection( aSel );
115     EditPaM aPaM = aSel.Max();
116 
117     OUString aTmpStr;
118     bool bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() );
119     while ( bDone )
120     {
121         if (aTmpStr.getLength() > MAXCHARSINPARA)
122         {
123             aTmpStr = aTmpStr.copy(0, MAXCHARSINPARA);
124         }
125         aPaM = ImpInsertText( EditSelection( aPaM, aPaM ), aTmpStr );
126         aPaM = ImpInsertParaBreak( aPaM );
127         bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() );
128     }
129     return aPaM;
130 }
131 
ReadXML(SvStream & rInput,EditSelection aSel)132 EditPaM ImpEditEngine::ReadXML( SvStream& rInput, EditSelection aSel )
133 {
134     if ( aSel.HasRange() )
135         aSel = ImpDeleteSelection( aSel );
136 
137     ESelection aESel = CreateESel( aSel );
138 
139     return ::SvxReadXML( *GetEditEnginePtr(), rInput, aESel );
140 }
141 
ReadRTF(SvStream & rInput,EditSelection aSel)142 EditPaM ImpEditEngine::ReadRTF( SvStream& rInput, EditSelection aSel )
143 {
144     if ( aSel.HasRange() )
145         aSel = ImpDeleteSelection( aSel );
146 
147     // The SvRTF parser expects the Which-mapping passed on in the pool, not
148     // dependent on a secondary.
149     SfxItemPool* pPool = &aEditDoc.GetItemPool();
150     while (pPool->GetSecondaryPool() && pPool->GetName() != "EditEngineItemPool")
151    {
152         pPool = pPool->GetSecondaryPool();
153 
154     }
155 
156     DBG_ASSERT(pPool && pPool->GetName() == "EditEngineItemPool",
157         "ReadRTF: no EditEnginePool!");
158 
159     EditRTFParserRef xPrsr = new EditRTFParser(rInput, aSel, *pPool, pEditEngine);
160     SvParserState eState = xPrsr->CallParser();
161     if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) )
162     {
163         rInput.SetError( EE_READWRITE_WRONGFORMAT );
164         return aSel.Min();
165     }
166     return xPrsr->GetCurPaM();
167 }
168 
ReadHTML(SvStream & rInput,const OUString & rBaseURL,EditSelection aSel,SvKeyValueIterator * pHTTPHeaderAttrs)169 EditPaM ImpEditEngine::ReadHTML( SvStream& rInput, const OUString& rBaseURL, EditSelection aSel, SvKeyValueIterator* pHTTPHeaderAttrs )
170 {
171     if ( aSel.HasRange() )
172         aSel = ImpDeleteSelection( aSel );
173 
174     EditHTMLParserRef xPrsr = new EditHTMLParser( rInput, rBaseURL, pHTTPHeaderAttrs );
175     SvParserState eState = xPrsr->CallParser(pEditEngine, aSel.Max());
176     if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) )
177     {
178         rInput.SetError( EE_READWRITE_WRONGFORMAT );
179         return aSel.Min();
180     }
181     return xPrsr->GetCurSelection().Max();
182 }
183 
Write(SvStream & rOutput,EETextFormat eFormat,const EditSelection & rSel)184 void ImpEditEngine::Write(SvStream& rOutput, EETextFormat eFormat, const EditSelection& rSel)
185 {
186     if ( !rOutput.IsWritable() )
187         rOutput.SetError( SVSTREAM_WRITE_ERROR );
188 
189     if ( rOutput.GetError() )
190         return;
191 
192     if ( eFormat == EETextFormat::Text )
193         WriteText( rOutput, rSel );
194     else if ( eFormat == EETextFormat::Rtf )
195         WriteRTF( rOutput, rSel );
196     else if ( eFormat == EETextFormat::Xml )
197         WriteXML( rOutput, rSel );
198     else if ( eFormat == EETextFormat::Html )
199         ;
200     else
201     {
202         OSL_FAIL( "Write: Unknown Format" );
203     }
204 }
205 
WriteText(SvStream & rOutput,EditSelection aSel)206 ErrCode ImpEditEngine::WriteText( SvStream& rOutput, EditSelection aSel )
207 {
208     sal_Int32 nStartNode, nEndNode;
209     bool bRange = aSel.HasRange();
210     if ( bRange )
211     {
212         aSel.Adjust( aEditDoc );
213         nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() );
214         nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() );
215     }
216     else
217     {
218         nStartNode = 0;
219         nEndNode = aEditDoc.Count()-1;
220     }
221 
222     // iterate over the paragraphs ...
223     for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++  )
224     {
225         ContentNode* pNode = aEditDoc.GetObject( nNode );
226         DBG_ASSERT( pNode, "Node not found: Search&Replace" );
227 
228         sal_Int32 nStartPos = 0;
229         sal_Int32 nEndPos = pNode->Len();
230         if ( bRange )
231         {
232             if ( nNode == nStartNode )
233                 nStartPos = aSel.Min().GetIndex();
234             if ( nNode == nEndNode ) // can also be == nStart!
235                 nEndPos = aSel.Max().GetIndex();
236         }
237         OUString aTmpStr = EditDoc::GetParaAsString( pNode, nStartPos, nEndPos );
238         rOutput.WriteByteStringLine( aTmpStr, rOutput.GetStreamCharSet() );
239     }
240 
241     return rOutput.GetError();
242 }
243 
WriteItemListAsRTF(ItemList & rLst,SvStream & rOutput,sal_Int32 nPara,sal_Int32 nPos,std::vector<std::unique_ptr<SvxFontItem>> & rFontTable,SvxColorList & rColorList)244 bool ImpEditEngine::WriteItemListAsRTF( ItemList& rLst, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos,
245                         std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList )
246 {
247     const SfxPoolItem* pAttrItem = rLst.First();
248     while ( pAttrItem )
249     {
250         WriteItemAsRTF( *pAttrItem, rOutput, nPara, nPos,rFontTable, rColorList );
251         pAttrItem = rLst.Next();
252     }
253     return rLst.Count() != 0;
254 }
255 
lcl_FindValidAttribs(ItemList & rLst,ContentNode * pNode,sal_Int32 nIndex,sal_uInt16 nScriptType)256 static void lcl_FindValidAttribs( ItemList& rLst, ContentNode* pNode, sal_Int32 nIndex, sal_uInt16 nScriptType )
257 {
258     sal_uInt16 nAttr = 0;
259     EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
260     while ( pAttr && ( pAttr->GetStart() <= nIndex ) )
261     {
262         // Start is checked in while ...
263         if ( pAttr->GetEnd() > nIndex )
264         {
265             if ( IsScriptItemValid( pAttr->GetItem()->Which(), nScriptType ) )
266                 rLst.Insert( pAttr->GetItem() );
267         }
268         nAttr++;
269         pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
270     }
271 }
272 
WriteXML(SvStream & rOutput,const EditSelection & rSel)273 void ImpEditEngine::WriteXML(SvStream& rOutput, const EditSelection& rSel)
274 {
275     ESelection aESel = CreateESel(rSel);
276 
277     SvxWriteXML( *GetEditEnginePtr(), rOutput, aESel );
278 }
279 
WriteRTF(SvStream & rOutput,EditSelection aSel)280 ErrCode ImpEditEngine::WriteRTF( SvStream& rOutput, EditSelection aSel )
281 {
282     DBG_ASSERT( GetUpdateMode(), "WriteRTF for UpdateMode = sal_False!" );
283     CheckIdleFormatter();
284     if ( !IsFormatted() )
285         FormatDoc();
286 
287     sal_Int32 nStartNode, nEndNode;
288     aSel.Adjust( aEditDoc );
289 
290     nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() );
291     nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() );
292 
293     // RTF header ...
294     rOutput.WriteChar( '{' ) ;
295 
296     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RTF );
297 
298     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ANSI );
299     rtl_TextEncoding eDestEnc = RTL_TEXTENCODING_MS_1252;
300 
301     // Generate and write out Font table  ...
302     std::vector<std::unique_ptr<SvxFontItem>> aFontTable;
303     // default font must be up front, so DEF font in RTF
304     aFontTable.emplace_back( new SvxFontItem( aEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO ) ) );
305     aFontTable.emplace_back( new SvxFontItem( aEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CJK ) ) );
306     aFontTable.emplace_back( new SvxFontItem( aEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CTL ) ) );
307     for ( sal_uInt16 nScriptType = 0; nScriptType < 3; nScriptType++ )
308     {
309         sal_uInt16 nWhich = EE_CHAR_FONTINFO;
310         if ( nScriptType == 1 )
311             nWhich = EE_CHAR_FONTINFO_CJK;
312         else if ( nScriptType == 2 )
313             nWhich = EE_CHAR_FONTINFO_CTL;
314 
315         for (const SfxPoolItem* pItem : aEditDoc.GetItemPool().GetItemSurrogates(nWhich))
316         {
317             SvxFontItem const*const pFontItem = static_cast<const SvxFontItem*>(pItem);
318             bool bAlreadyExist = false;
319             sal_uLong nTestMax = nScriptType ? aFontTable.size() : 1;
320             for ( sal_uLong nTest = 0; !bAlreadyExist && ( nTest < nTestMax ); nTest++ )
321             {
322                 bAlreadyExist = *aFontTable[ nTest ] == *pFontItem;
323             }
324 
325             if ( !bAlreadyExist )
326                 aFontTable.emplace_back( new SvxFontItem( *pFontItem ) );
327         }
328     }
329 
330     rOutput << endl;
331     rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FONTTBL );
332     for ( std::vector<SvxFontItem*>::size_type j = 0; j < aFontTable.size(); j++ )
333     {
334         SvxFontItem* pFontItem = aFontTable[ j ].get();
335         rOutput.WriteChar( '{' );
336         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_F );
337         rOutput.WriteUInt32AsString( j );
338         switch ( pFontItem->GetFamily()  )
339         {
340             case FAMILY_DONTKNOW:       rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FNIL );
341                                         break;
342             case FAMILY_DECORATIVE:     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FDECOR );
343                                         break;
344             case FAMILY_MODERN:         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FMODERN );
345                                         break;
346             case FAMILY_ROMAN:          rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FROMAN );
347                                         break;
348             case FAMILY_SCRIPT:         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FSCRIPT );
349                                         break;
350             case FAMILY_SWISS:          rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FSWISS );
351                                         break;
352             default:
353                 break;
354         }
355         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FPRQ );
356         sal_uInt16 nVal = 0;
357         switch( pFontItem->GetPitch() )
358         {
359             case PITCH_FIXED:       nVal = 1;       break;
360             case PITCH_VARIABLE:    nVal = 2;       break;
361             default:
362                 break;
363         }
364         rOutput.WriteUInt32AsString( nVal );
365 
366         rtl_TextEncoding eChrSet = pFontItem->GetCharSet();
367         DBG_ASSERT( eChrSet != 9, "SystemCharSet?!" );
368         if( RTL_TEXTENCODING_DONTKNOW == eChrSet )
369             eChrSet = osl_getThreadTextEncoding();
370         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FCHARSET );
371         rOutput.WriteUInt32AsString( rtl_getBestWindowsCharsetFromTextEncoding( eChrSet ) );
372 
373         rOutput.WriteChar( ' ' );
374         RTFOutFuncs::Out_String( rOutput, pFontItem->GetFamilyName(), eDestEnc );
375         rOutput.WriteCharPtr( ";}" );
376     }
377     rOutput.WriteChar( '}' );
378     rOutput << endl;
379 
380     // Write out ColorList  ...
381     SvxColorList aColorList;
382     // COL_AUTO should be the default color, always put it first
383     aColorList.emplace_back(COL_AUTO);
384     SvxColorItem const& rDefault(aEditDoc.GetItemPool().GetDefaultItem(EE_CHAR_COLOR));
385     if (rDefault.GetValue() != COL_AUTO) // is the default always AUTO?
386     {
387         aColorList.push_back(rDefault.GetValue());
388     }
389     for (const SfxPoolItem* pItem : aEditDoc.GetItemPool().GetItemSurrogates(EE_CHAR_COLOR))
390     {
391         auto pColorItem(dynamic_cast<SvxColorItem const*>(pItem));
392         if (pColorItem && pColorItem->GetValue() != COL_AUTO) // may be null!
393         {
394             aColorList.push_back(pColorItem->GetValue());
395         }
396     }
397 
398     rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_COLORTBL );
399     for ( SvxColorList::size_type j = 0; j < aColorList.size(); j++ )
400     {
401         Color const color = aColorList[j];
402         if (color != COL_AUTO) // auto is represented by "empty" element
403         {
404             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RED );
405             rOutput.WriteUInt32AsString( color.GetRed() );
406             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_GREEN );
407             rOutput.WriteUInt32AsString( color.GetGreen() );
408             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_BLUE );
409             rOutput.WriteUInt32AsString( color.GetBlue() );
410         }
411         rOutput.WriteChar( ';' );
412     }
413     rOutput.WriteChar( '}' );
414     rOutput << endl;
415 
416     std::unordered_map<SfxStyleSheetBase*, sal_uInt32> aStyleSheetToIdMap;
417     // StyleSheets...
418     if ( GetStyleSheetPool() )
419     {
420         std::shared_ptr<SfxStyleSheetIterator> aSSSIterator = std::make_shared<SfxStyleSheetIterator>(GetStyleSheetPool(),
421                 SfxStyleFamily::All);
422         // fill aStyleSheetToIdMap
423         sal_uInt32 nId = 1;
424         for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle;
425                                  pStyle = aSSSIterator->Next() )
426         {
427             aStyleSheetToIdMap[pStyle] = nId;
428             nId++;
429         }
430 
431         if ( aSSSIterator->Count() )
432         {
433 
434             sal_uInt32 nStyle = 0;
435             rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_STYLESHEET );
436 
437             for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle;
438                                      pStyle = aSSSIterator->Next() )
439             {
440 
441                 rOutput << endl;
442                 rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_S );
443                 sal_uInt32 nNumber = nStyle + 1;
444                 rOutput.WriteUInt32AsString( nNumber );
445 
446                 // Attribute, also from Parent!
447                 for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ )
448                 {
449                     if ( pStyle->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET )
450                     {
451                         const SfxPoolItem& rItem = pStyle->GetItemSet().Get( nParAttr );
452                         WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList );
453                     }
454                 }
455 
456                 // Parent ... (only if necessary)
457                 if ( !pStyle->GetParent().isEmpty() && ( pStyle->GetParent() != pStyle->GetName() ) )
458                 {
459                     SfxStyleSheet* pParent = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetParent(), pStyle->GetFamily() ));
460                     DBG_ASSERT( pParent, "Parent not found!" );
461                     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SBASEDON );
462                     nNumber = aStyleSheetToIdMap.find(pParent)->second;
463                     rOutput.WriteUInt32AsString( nNumber );
464                 }
465 
466                 // Next Style... (more)
467                 // we assume that we have only SfxStyleSheet in the pool
468                 SfxStyleSheet* pNext = static_cast<SfxStyleSheet*>(pStyle);
469                 if ( !pStyle->GetFollow().isEmpty() && ( pStyle->GetFollow() != pStyle->GetName() ) )
470                     pNext = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetFollow(), pStyle->GetFamily() ));
471 
472                 DBG_ASSERT( pNext, "Next not found!" );
473                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SNEXT );
474                 nNumber = aStyleSheetToIdMap.find(pNext)->second;
475                 rOutput.WriteUInt32AsString( nNumber );
476 
477                 // Name of the template...
478                 rOutput.WriteCharPtr( " " );
479                 RTFOutFuncs::Out_String( rOutput, pStyle->GetName(), eDestEnc );
480                 rOutput.WriteCharPtr( ";}" );
481                 nStyle++;
482             }
483             rOutput.WriteChar( '}' );
484             rOutput << endl;
485         }
486     }
487 
488     // Write the pool defaults in advance ...
489     rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_IGNORE ).WriteCharPtr( "\\EditEnginePoolDefaults" );
490     for ( sal_uInt16 nPoolDefItem = EE_PARA_START; nPoolDefItem <= EE_CHAR_END; nPoolDefItem++)
491     {
492         const SfxPoolItem& rItem = aEditDoc.GetItemPool().GetDefaultItem( nPoolDefItem );
493         WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList );
494     }
495     rOutput.WriteChar( '}' ) << endl;
496 
497     // DefTab:
498     MapMode aTwpMode( MapUnit::MapTwip );
499     sal_uInt16 nDefTabTwps = static_cast<sal_uInt16>(GetRefDevice()->LogicToLogic(
500                                         Point( aEditDoc.GetDefTab(), 0 ),
501                                         &GetRefMapMode(), &aTwpMode ).X());
502     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_DEFTAB );
503     rOutput.WriteUInt32AsString( nDefTabTwps );
504     rOutput << endl;
505 
506     // iterate over the paragraphs ...
507     rOutput.WriteChar( '{' ) << endl;
508     for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++  )
509     {
510         ContentNode* pNode = aEditDoc.GetObject( nNode );
511         DBG_ASSERT( pNode, "Node not found: Search&Replace" );
512 
513         // The paragraph attributes in advance ...
514         bool bAttr = false;
515 
516         // Template?
517         if ( pNode->GetStyleSheet() )
518         {
519             // Number of template
520             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_S );
521             sal_uInt32 nNumber = aStyleSheetToIdMap.find(pNode->GetStyleSheet())->second;
522             rOutput.WriteUInt32AsString( nNumber );
523 
524             // All Attribute
525             // Attribute, also from Parent!
526             for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ )
527             {
528                 if ( pNode->GetStyleSheet()->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET )
529                 {
530                     const SfxPoolItem& rItem = pNode->GetStyleSheet()->GetItemSet().Get( nParAttr );
531                     WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList );
532                     bAttr = true;
533                 }
534             }
535         }
536 
537         for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ )
538         {
539             // Now where stylesheet processing, only hard paragraph attributes!
540             if ( pNode->GetContentAttribs().GetItems().GetItemState( nParAttr ) == SfxItemState::SET )
541             {
542                 const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItems().Get( nParAttr );
543                 WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList );
544                 bAttr = true;
545             }
546         }
547         if ( bAttr )
548             rOutput.WriteChar( ' ' ); // Separator
549 
550         ItemList aAttribItems;
551         ParaPortion& rParaPortion = FindParaPortion( pNode );
552 
553         sal_Int32 nIndex = 0;
554         sal_Int32 nStartPos = 0;
555         sal_Int32 nEndPos = pNode->Len();
556         sal_Int32 nStartPortion = 0;
557         sal_Int32 nEndPortion = rParaPortion.GetTextPortions().Count() - 1;
558         bool bFinishPortion = false;
559         sal_Int32 nPortionStart;
560 
561         if ( nNode == nStartNode )
562         {
563             nStartPos = aSel.Min().GetIndex();
564             nStartPortion = rParaPortion.GetTextPortions().FindPortion( nStartPos, nPortionStart );
565             if ( nStartPos != 0 )
566             {
567                 aAttribItems.Clear();
568                 lcl_FindValidAttribs( aAttribItems, pNode, nStartPos, GetI18NScriptType( EditPaM( pNode, 0 ) ) );
569                 if ( aAttribItems.Count() )
570                 {
571                     // These attributes may not apply to the entire paragraph:
572                     rOutput.WriteChar( '{' );
573                     WriteItemListAsRTF( aAttribItems, rOutput, nNode, nStartPos, aFontTable, aColorList );
574                     bFinishPortion = true;
575                 }
576                 aAttribItems.Clear();
577             }
578         }
579         if ( nNode == nEndNode ) // can also be == nStart!
580         {
581             nEndPos = aSel.Max().GetIndex();
582             nEndPortion = rParaPortion.GetTextPortions().FindPortion( nEndPos, nPortionStart );
583         }
584 
585         const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature(nIndex);
586         // start at 0, so the index is right ...
587         for ( sal_Int32 n = 0; n <= nEndPortion; n++ )
588         {
589             const TextPortion& rTextPortion = rParaPortion.GetTextPortions()[n];
590             if ( n < nStartPortion )
591             {
592                 nIndex = nIndex + rTextPortion.GetLen();
593                 continue;
594             }
595 
596             if ( pNextFeature && ( pNextFeature->GetStart() == nIndex ) && ( pNextFeature->GetItem()->Which() != EE_FEATURE_FIELD ) )
597             {
598                 WriteItemAsRTF( *pNextFeature->GetItem(), rOutput, nNode, nIndex, aFontTable, aColorList );
599                 pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 );
600             }
601             else
602             {
603                 aAttribItems.Clear();
604                 sal_uInt16 nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) );
605                 SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
606                 if ( !n || IsScriptChange( EditPaM( pNode, nIndex ) ) )
607                 {
608                     SfxItemSet aAttribs = GetAttribs( nNode, nIndex+1, nIndex+1 );
609                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) ) );
610                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) );
611                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ) ) );
612                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ) ) );
613                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ) ) );
614                 }
615                 // Insert hard attribs AFTER CJK attribs...
616                 lcl_FindValidAttribs( aAttribItems, pNode, nIndex, nScriptTypeI18N );
617 
618                 rOutput.WriteChar( '{' );
619                 if ( WriteItemListAsRTF( aAttribItems, rOutput, nNode, nIndex, aFontTable, aColorList ) )
620                     rOutput.WriteChar( ' ' );
621 
622                 sal_Int32 nS = nIndex;
623                 sal_Int32 nE = nIndex + rTextPortion.GetLen();
624                 if ( n == nStartPortion )
625                     nS = nStartPos;
626                 if ( n == nEndPortion )
627                     nE = nEndPos;
628 
629                 OUString aRTFStr = EditDoc::GetParaAsString( pNode, nS, nE);
630                 RTFOutFuncs::Out_String( rOutput, aRTFStr, eDestEnc );
631                 rOutput.WriteChar( '}' );
632             }
633             if ( bFinishPortion )
634             {
635                 rOutput.WriteChar( '}' );
636                 bFinishPortion = false;
637             }
638 
639             nIndex = nIndex + rTextPortion.GetLen();
640         }
641 
642         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PAR ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PARD ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PLAIN );
643         rOutput << endl;
644     }
645     // RTF-trailer ...
646     rOutput.WriteCharPtr( "}}" );    // 1xparentheses paragraphs, 1xparentheses RTF document
647     rOutput.Flush();
648 
649     aFontTable.clear();
650 
651     return rOutput.GetError();
652 }
653 
654 
WriteItemAsRTF(const SfxPoolItem & rItem,SvStream & rOutput,sal_Int32 nPara,sal_Int32 nPos,std::vector<std::unique_ptr<SvxFontItem>> & rFontTable,SvxColorList & rColorList)655 void ImpEditEngine::WriteItemAsRTF( const SfxPoolItem& rItem, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos,
656                             std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList )
657 {
658     sal_uInt16 nWhich = rItem.Which();
659     switch ( nWhich )
660     {
661         case EE_PARA_WRITINGDIR:
662         {
663             const SvxFrameDirectionItem& rWritingMode = static_cast<const SvxFrameDirectionItem&>(rItem);
664             if ( rWritingMode.GetValue() == SvxFrameDirection::Horizontal_RL_TB )
665                 rOutput.WriteCharPtr( "\\rtlpar" );
666             else
667                 rOutput.WriteCharPtr( "\\ltrpar" );
668         }
669         break;
670         case EE_PARA_OUTLLEVEL:
671         {
672             sal_Int32 nLevel = static_cast<const SfxInt16Item&>(rItem).GetValue();
673             if( nLevel >= 0 )
674             {
675                 rOutput.WriteCharPtr( "\\level" );
676                 rOutput.WriteInt32AsString( nLevel );
677             }
678         }
679         break;
680         case EE_PARA_OUTLLRSPACE:
681         case EE_PARA_LRSPACE:
682         {
683             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FI );
684             sal_Int32 nTxtFirst = static_cast<const SvxLRSpaceItem&>(rItem).GetTextFirstLineOffset();
685             nTxtFirst = LogicToTwips( nTxtFirst );
686             rOutput.WriteInt32AsString( nTxtFirst );
687             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_LI );
688             sal_uInt32 nTxtLeft = static_cast< sal_uInt32 >(static_cast<const SvxLRSpaceItem&>(rItem).GetTextLeft());
689             nTxtLeft = static_cast<sal_uInt32>(LogicToTwips( nTxtLeft ));
690             rOutput.WriteInt32AsString( nTxtLeft );
691             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RI );
692             sal_uInt32 nTxtRight = static_cast<const SvxLRSpaceItem&>(rItem).GetRight();
693             nTxtRight = LogicToTwips( nTxtRight);
694             rOutput.WriteUInt32AsString( nTxtRight );
695         }
696         break;
697         case EE_PARA_ULSPACE:
698         {
699             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SB );
700             sal_uInt32 nUpper = static_cast<const SvxULSpaceItem&>(rItem).GetUpper();
701             nUpper = static_cast<sal_uInt32>(LogicToTwips( nUpper ));
702             rOutput.WriteUInt32AsString( nUpper );
703             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SA );
704             sal_uInt32 nLower = static_cast<const SvxULSpaceItem&>(rItem).GetLower();
705             nLower = LogicToTwips( nLower );
706             rOutput.WriteUInt32AsString( nLower );
707         }
708         break;
709         case EE_PARA_SBL:
710         {
711             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SL );
712             sal_Int32 nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetLineHeight();
713             char cMult = '0';
714             if ( static_cast<const SvxLineSpacingItem&>(rItem).GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
715             {
716                 // From where do I get the value now?
717                 // The SwRTF parser is based on a 240 Font!
718                 nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetPropLineSpace();
719                 nVal *= 240;
720                 nVal /= 100;
721                 cMult = '1';
722             }
723             rOutput.WriteInt32AsString( nVal );
724             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SLMULT ).WriteChar( cMult );
725         }
726         break;
727         case EE_PARA_JUST:
728         {
729             SvxAdjust eJustification = static_cast<const SvxAdjustItem&>(rItem).GetAdjust();
730             switch ( eJustification )
731             {
732                 case SvxAdjust::Center: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_QC );
733                                         break;
734                 case SvxAdjust::Right:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_QR );
735                                         break;
736                 default:                rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_QL );
737                                         break;
738             }
739         }
740         break;
741         case EE_PARA_TABS:
742         {
743             const SvxTabStopItem& rTabs = static_cast<const SvxTabStopItem&>(rItem);
744             for ( sal_uInt16 i = 0; i < rTabs.Count(); i++ )
745             {
746                 const SvxTabStop& rTab = rTabs[i];
747                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TX );
748                 rOutput.WriteInt32AsString( LogicToTwips( rTab.GetTabPos() ) );
749             }
750         }
751         break;
752         case EE_CHAR_COLOR:
753         {
754             SvxColorList::const_iterator const iter = std::find(
755                     rColorList.begin(), rColorList.end(),
756                     static_cast<SvxColorItem const&>(rItem).GetValue());
757             assert(iter != rColorList.end());
758             sal_uInt32 const n = iter - rColorList.begin();
759             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_CF );
760             rOutput.WriteUInt32AsString( n );
761         }
762         break;
763         case EE_CHAR_FONTINFO:
764         case EE_CHAR_FONTINFO_CJK:
765         case EE_CHAR_FONTINFO_CTL:
766         {
767             sal_uInt32 n = 0;
768             for (size_t i = 0; i < rFontTable.size(); ++i)
769             {
770                 if (*rFontTable[i] == rItem)
771                 {
772                     n = i;
773                     break;
774                 }
775             }
776 
777             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_F );
778             rOutput.WriteUInt32AsString( n );
779         }
780         break;
781         case EE_CHAR_FONTHEIGHT:
782         case EE_CHAR_FONTHEIGHT_CJK:
783         case EE_CHAR_FONTHEIGHT_CTL:
784         {
785             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FS );
786             sal_Int32 nHeight = static_cast<const SvxFontHeightItem&>(rItem).GetHeight();
787             nHeight = LogicToTwips( nHeight );
788             // Twips => HalfPoints
789             nHeight /= 10;
790             rOutput.WriteInt32AsString( nHeight );
791         }
792         break;
793         case EE_CHAR_WEIGHT:
794         case EE_CHAR_WEIGHT_CJK:
795         case EE_CHAR_WEIGHT_CTL:
796         {
797             FontWeight e = static_cast<const SvxWeightItem&>(rItem).GetWeight();
798             switch ( e )
799             {
800                 case WEIGHT_BOLD:   rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_B );                break;
801                 default:            rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_B ).WriteChar( '0' );     break;
802             }
803         }
804         break;
805         case EE_CHAR_UNDERLINE:
806         {
807             // Must underlined if in WordLineMode, but the information is
808             // missing here
809             FontLineStyle e = static_cast<const SvxUnderlineItem&>(rItem).GetLineStyle();
810             switch ( e )
811             {
812                 case LINESTYLE_NONE:    rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ULNONE );       break;
813                 case LINESTYLE_SINGLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_UL );       break;
814                 case LINESTYLE_DOUBLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ULDB );     break;
815                 case LINESTYLE_DOTTED:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ULD );      break;
816                 default:
817                     break;
818             }
819         }
820         break;
821         case EE_CHAR_OVERLINE:
822         {
823             FontLineStyle e = static_cast<const SvxOverlineItem&>(rItem).GetLineStyle();
824             switch ( e )
825             {
826                 case LINESTYLE_NONE:    rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OLNONE );       break;
827                 case LINESTYLE_SINGLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OL );       break;
828                 case LINESTYLE_DOUBLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OLDB );     break;
829                 case LINESTYLE_DOTTED:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OLD );      break;
830                 default:
831                     break;
832             }
833         }
834         break;
835         case EE_CHAR_STRIKEOUT:
836         {
837             FontStrikeout e = static_cast<const SvxCrossedOutItem&>(rItem).GetStrikeout();
838             switch ( e )
839             {
840                 case STRIKEOUT_SINGLE:
841                 case STRIKEOUT_DOUBLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_STRIKE );       break;
842                 case STRIKEOUT_NONE:    rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_STRIKE ).WriteChar( '0' );    break;
843                 default:
844                     break;
845             }
846         }
847         break;
848         case EE_CHAR_ITALIC:
849         case EE_CHAR_ITALIC_CJK:
850         case EE_CHAR_ITALIC_CTL:
851         {
852             FontItalic e = static_cast<const SvxPostureItem&>(rItem).GetPosture();
853             switch ( e )
854             {
855                 case ITALIC_OBLIQUE:
856                 case ITALIC_NORMAL: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_I );        break;
857                 case ITALIC_NONE:   rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_I ).WriteChar( '0' ); break;
858                 default:
859                     break;
860             }
861         }
862         break;
863         case EE_CHAR_OUTLINE:
864         {
865             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OUTL );
866             if ( !static_cast<const SvxContourItem&>(rItem).GetValue() )
867                 rOutput.WriteChar( '0' );
868         }
869         break;
870         case EE_CHAR_RELIEF:
871         {
872             FontRelief nRelief = static_cast<const SvxCharReliefItem&>(rItem).GetValue();
873             if ( nRelief == FontRelief::Embossed )
874                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_EMBO );
875             if ( nRelief == FontRelief::Engraved )
876                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_IMPR );
877         }
878         break;
879         case EE_CHAR_EMPHASISMARK:
880         {
881             FontEmphasisMark nMark = static_cast<const SvxEmphasisMarkItem&>(rItem).GetEmphasisMark();
882             if ( nMark == FontEmphasisMark::NONE )
883                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ACCNONE );
884             else if ( nMark == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove) )
885                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ACCCOMMA );
886             else
887                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ACCDOT );
888         }
889         break;
890         case EE_CHAR_SHADOW:
891         {
892             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SHAD );
893             if ( !static_cast<const SvxShadowedItem&>(rItem).GetValue() )
894                 rOutput.WriteChar( '0' );
895         }
896         break;
897         case EE_FEATURE_TAB:
898         {
899             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TAB );
900         }
901         break;
902         case EE_FEATURE_LINEBR:
903         {
904             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SL );
905         }
906         break;
907         case EE_CHAR_KERNING:
908         {
909             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_EXPNDTW );
910             rOutput.WriteInt32AsString( LogicToTwips(
911                 static_cast<const SvxKerningItem&>(rItem).GetValue() ) );
912         }
913         break;
914         case EE_CHAR_PAIRKERNING:
915         {
916             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_KERNING );
917             rOutput.WriteUInt32AsString( static_cast<const SvxAutoKernItem&>(rItem).GetValue() ? 1 : 0 );
918         }
919         break;
920         case EE_CHAR_ESCAPEMENT:
921         {
922             SvxFont aFont;
923             ContentNode* pNode = aEditDoc.GetObject( nPara );
924             SeekCursor( pNode, nPos, aFont );
925             MapMode aPntMode( MapUnit::MapPoint );
926             tools::Long nFontHeight = GetRefDevice()->LogicToLogic(
927                     aFont.GetFontSize(), &GetRefMapMode(), &aPntMode ).Height();
928             nFontHeight *=2;    // Half Points
929             sal_uInt16 const nProp = static_cast<const SvxEscapementItem&>(rItem).GetProportionalHeight();
930             sal_uInt16 nProp100 = nProp*100;    // For SWG-Token Prop in 100th percent.
931             short nEsc = static_cast<const SvxEscapementItem&>(rItem).GetEsc();
932             const FontMetric& rFontMetric = GetRefDevice()->GetFontMetric();
933             double fFontHeight = rFontMetric.GetAscent() + rFontMetric.GetDescent();
934             double fAutoAscent = .8;
935             double fAutoDescent = .2;
936             if ( fFontHeight )
937             {
938                 fAutoAscent = rFontMetric.GetAscent() / fFontHeight;
939                 fAutoDescent = rFontMetric.GetDescent() / fFontHeight;
940             }
941             if ( nEsc == DFLT_ESC_AUTO_SUPER )
942             {
943                 nEsc =  fAutoAscent * (100 - nProp);
944                 nProp100++; // A 1 afterwards means 'automatic'.
945             }
946             else if ( nEsc == DFLT_ESC_AUTO_SUB )
947             {
948                 nEsc =  fAutoDescent * -(100 - nProp);
949                 nProp100++;
950             }
951             // SWG:
952             if ( nEsc )
953             {
954                 rOutput.WriteCharPtr( "{\\*\\updnprop" ).WriteCharPtr( OString::number(
955                     nProp100).getStr() ).WriteChar( '}' );
956             }
957             tools::Long nUpDown = nFontHeight * std::abs( nEsc ) / 100;
958             if ( nEsc < 0 )
959                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_DN );
960             else if ( nEsc > 0 )
961                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_UP );
962             rOutput.WriteOString( OString::number(nUpDown) );
963         }
964         break;
965     }
966 }
967 
GetEmptyTextObject()968 std::unique_ptr<EditTextObject> ImpEditEngine::GetEmptyTextObject()
969 {
970     EditSelection aEmptySel;
971     aEmptySel.Min() = aEditDoc.GetStartPaM();
972     aEmptySel.Max() = aEditDoc.GetStartPaM();
973 
974     return CreateTextObject( aEmptySel );
975 }
976 
CreateTextObject()977 std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject()
978 {
979     EditSelection aCompleteSelection;
980     aCompleteSelection.Min() = aEditDoc.GetStartPaM();
981     aCompleteSelection.Max() = aEditDoc.GetEndPaM();
982 
983     return CreateTextObject( aCompleteSelection );
984 }
985 
CreateTextObject(const EditSelection & rSel)986 std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject(const EditSelection& rSel)
987 {
988     return CreateTextObject(rSel, GetEditTextObjectPool(), aStatus.AllowBigObjects(), nBigTextObjectStart);
989 }
990 
CreateTextObject(EditSelection aSel,SfxItemPool * pPool,bool bAllowBigObjects,sal_Int32 nBigObjectStart)991 std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject( EditSelection aSel, SfxItemPool* pPool, bool bAllowBigObjects, sal_Int32 nBigObjectStart )
992 {
993     std::unique_ptr<EditTextObject> pTxtObj(new EditTextObject(pPool));
994     pTxtObj->SetVertical( GetDirectVertical() );
995     pTxtObj->SetRotation( GetRotation() );
996     MapUnit eMapUnit = aEditDoc.GetItemPool().GetMetric( DEF_METRIC );
997     pTxtObj->mpImpl->SetMetric( static_cast<sal_uInt16>(eMapUnit) );
998     if ( pTxtObj->mpImpl->IsOwnerOfPool() )
999         pTxtObj->mpImpl->GetPool()->SetDefaultMetric( eMapUnit );
1000 
1001     sal_Int32 nStartNode, nEndNode;
1002     sal_Int32 nTextPortions = 0;
1003 
1004     aSel.Adjust( aEditDoc );
1005     nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() );
1006     nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() );
1007 
1008     bool bOnlyFullParagraphs = !( aSel.Min().GetIndex() ||
1009         ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() ) );
1010 
1011     // Templates are not saved!
1012     // (Only the name and family, template itself must be in App!)
1013     pTxtObj->mpImpl->SetScriptType(GetItemScriptType(aSel));
1014 
1015     // iterate over the paragraphs ...
1016     sal_Int32 nNode;
1017     for ( nNode = nStartNode; nNode <= nEndNode; nNode++  )
1018     {
1019         ContentNode* pNode = aEditDoc.GetObject( nNode );
1020         DBG_ASSERT( pNode, "Node not found: Search&Replace" );
1021 
1022         if ( bOnlyFullParagraphs )
1023         {
1024             const ParaPortion& rParaPortion = GetParaPortions()[nNode];
1025             nTextPortions += rParaPortion.GetTextPortions().Count();
1026         }
1027 
1028         sal_Int32 nStartPos = 0;
1029         sal_Int32 nEndPos = pNode->Len();
1030 
1031         bool bEmptyPara = nEndPos == 0;
1032 
1033         if ( ( nNode == nStartNode ) && !bOnlyFullParagraphs )
1034             nStartPos = aSel.Min().GetIndex();
1035         if ( ( nNode == nEndNode ) && !bOnlyFullParagraphs )
1036             nEndPos = aSel.Max().GetIndex();
1037 
1038 
1039         ContentInfo *pC = pTxtObj->mpImpl->CreateAndInsertContent();
1040 
1041         // The paragraph attributes ...
1042         pC->GetParaAttribs().Set( pNode->GetContentAttribs().GetItems() );
1043 
1044         // The StyleSheet...
1045         if ( pNode->GetStyleSheet() )
1046         {
1047             pC->SetStyle(pNode->GetStyleSheet()->GetName());
1048             pC->SetFamily(pNode->GetStyleSheet()->GetFamily());
1049         }
1050 
1051         // The Text...
1052         pC->SetText(pNode->Copy(nStartPos, nEndPos-nStartPos));
1053         auto& rCAttriblist = pC->GetCharAttribs();
1054 
1055         // and the Attribute...
1056         sal_uInt16 nAttr = 0;
1057         EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
1058         while ( pAttr )
1059         {
1060             // In a blank paragraph keep the attributes!
1061             if ( bEmptyPara ||
1062                  ( ( pAttr->GetEnd() > nStartPos ) && ( pAttr->GetStart() < nEndPos ) ) )
1063             {
1064                 XEditAttribute aX = pTxtObj->mpImpl->CreateAttrib(*pAttr->GetItem(), pAttr->GetStart(), pAttr->GetEnd());
1065                 // Possibly Correct ...
1066                 if ( ( nNode == nStartNode ) && ( nStartPos != 0 ) )
1067                 {
1068                     aX.GetStart() = ( aX.GetStart() > nStartPos ) ? aX.GetStart()-nStartPos : 0;
1069                     aX.GetEnd() = aX.GetEnd() - nStartPos;
1070 
1071                 }
1072                 if ( nNode == nEndNode )
1073                 {
1074                     if ( aX.GetEnd() > (nEndPos-nStartPos) )
1075                         aX.GetEnd() = nEndPos-nStartPos;
1076                 }
1077                 DBG_ASSERT( aX.GetEnd() <= (nEndPos-nStartPos), "CreateBinTextObject: Attribute too long!" );
1078                 if ( !aX.GetLen() && !bEmptyPara )
1079                     pTxtObj->mpImpl->DestroyAttrib(aX);
1080                 else
1081                     rCAttriblist.push_back(std::move(aX));
1082             }
1083             nAttr++;
1084             pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
1085         }
1086 
1087         // If possible online spelling
1088         if ( bAllowBigObjects && bOnlyFullParagraphs && pNode->GetWrongList() )
1089             pC->SetWrongList( pNode->GetWrongList()->Clone() );
1090 
1091     }
1092 
1093     // Remember the portions info in case of large text objects:
1094     // sleeper set up when Olli paragraphs not hacked!
1095     if ( bAllowBigObjects && bOnlyFullParagraphs && IsFormatted() && GetUpdateMode() && ( nTextPortions >= nBigObjectStart ) )
1096     {
1097         XParaPortionList* pXList = new XParaPortionList( GetRefDevice(), GetColumnWidth(aPaperSize), nStretchX, nStretchY );
1098         pTxtObj->mpImpl->SetPortionInfo(std::unique_ptr<XParaPortionList>(pXList));
1099         for ( nNode = nStartNode; nNode <= nEndNode; nNode++  )
1100         {
1101             const ParaPortion& rParaPortion = GetParaPortions()[nNode];
1102             XParaPortion* pX = new XParaPortion;
1103             pXList->push_back(pX);
1104 
1105             pX->nHeight = rParaPortion.GetHeight();
1106             pX->nFirstLineOffset = rParaPortion.GetFirstLineOffset();
1107 
1108             // The TextPortions
1109             sal_uInt16 nCount = rParaPortion.GetTextPortions().Count();
1110             sal_uInt16 n;
1111             for ( n = 0; n < nCount; n++ )
1112             {
1113                 const TextPortion& rTextPortion = rParaPortion.GetTextPortions()[n];
1114                 TextPortion* pNew = new TextPortion( rTextPortion );
1115                 pX->aTextPortions.Append(pNew);
1116             }
1117 
1118             // The lines
1119             nCount = rParaPortion.GetLines().Count();
1120             for ( n = 0; n < nCount; n++ )
1121             {
1122                 const EditLine& rLine = rParaPortion.GetLines()[n];
1123                 EditLine* pNew = rLine.Clone();
1124                 pX->aLines.Append(pNew);
1125             }
1126 #ifdef DBG_UTIL
1127             sal_uInt16 nTest;
1128             int nTPLen = 0, nTxtLen = 0;
1129             for ( nTest = rParaPortion.GetTextPortions().Count(); nTest; )
1130                 nTPLen += rParaPortion.GetTextPortions()[--nTest].GetLen();
1131             for ( nTest = rParaPortion.GetLines().Count(); nTest; )
1132                 nTxtLen += rParaPortion.GetLines()[--nTest].GetLen();
1133             DBG_ASSERT( ( nTPLen == rParaPortion.GetNode()->Len() ) && ( nTxtLen == rParaPortion.GetNode()->Len() ), "CreateBinTextObject: ParaPortion not completely formatted!" );
1134 #endif
1135         }
1136     }
1137     return pTxtObj;
1138 }
1139 
SetText(const EditTextObject & rTextObject)1140 void ImpEditEngine::SetText( const EditTextObject& rTextObject )
1141 {
1142     // Since setting a text object is not undo-able!
1143     ResetUndoManager();
1144     bool _bUpdate = GetUpdateMode();
1145     bool _bUndo = IsUndoEnabled();
1146 
1147     SetText( OUString() );
1148     EditPaM aPaM = aEditDoc.GetStartPaM();
1149 
1150     SetUpdateMode( false );
1151     EnableUndo( false );
1152 
1153     InsertText( rTextObject, EditSelection( aPaM, aPaM ) );
1154     SetVertical(rTextObject.GetDirectVertical());
1155     SetRotation(rTextObject.GetRotation());
1156 
1157     DBG_ASSERT( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "From where comes the Undo in SetText ?!" );
1158     SetUpdateMode( _bUpdate );
1159     EnableUndo( _bUndo );
1160 }
1161 
InsertText(const EditTextObject & rTextObject,EditSelection aSel)1162 EditSelection ImpEditEngine::InsertText( const EditTextObject& rTextObject, EditSelection aSel )
1163 {
1164     aSel.Adjust( aEditDoc );
1165     if ( aSel.HasRange() )
1166         aSel = ImpDeleteSelection( aSel );
1167     EditSelection aNewSel = InsertTextObject( rTextObject, aSel.Max() );
1168     return aNewSel;
1169 }
1170 
InsertTextObject(const EditTextObject & rTextObject,EditPaM aPaM)1171 EditSelection ImpEditEngine::InsertTextObject( const EditTextObject& rTextObject, EditPaM aPaM )
1172 {
1173     // Optimize: No getPos undFindParaportion, instead calculate index!
1174     EditSelection aSel( aPaM, aPaM );
1175     DBG_ASSERT( !aSel.DbgIsBuggy( aEditDoc ), "InsertBibTextObject: Selection broken!(1)" );
1176 
1177     bool bUsePortionInfo = false;
1178     XParaPortionList* pPortionInfo = rTextObject.mpImpl->GetPortionInfo();
1179 
1180     if ( pPortionInfo && ( static_cast<tools::Long>(pPortionInfo->GetPaperWidth()) == GetColumnWidth(aPaperSize) )
1181             && ( pPortionInfo->GetRefMapMode() == GetRefDevice()->GetMapMode() )
1182             && ( pPortionInfo->GetStretchX() == nStretchX )
1183             && ( pPortionInfo->GetStretchY() == nStretchY ) )
1184     {
1185         if ( (pPortionInfo->GetRefDevPtr() == GetRefDevice()) ||
1186              (pPortionInfo->RefDevIsVirtual() && GetRefDevice()->IsVirtual()) )
1187             bUsePortionInfo = true;
1188     }
1189 
1190     bool bConvertMetricOfItems = false;
1191     MapUnit eSourceUnit = MapUnit(), eDestUnit = MapUnit();
1192     if (rTextObject.mpImpl->HasMetric())
1193     {
1194         eSourceUnit = static_cast<MapUnit>(rTextObject.mpImpl->GetMetric());
1195         eDestUnit = aEditDoc.GetItemPool().GetMetric( DEF_METRIC );
1196         if ( eSourceUnit != eDestUnit )
1197             bConvertMetricOfItems = true;
1198     }
1199 
1200     // Before, paragraph count was of type sal_uInt16 so if nContents exceeded
1201     // 0xFFFF this wouldn't have worked anyway, given that nPara is used to
1202     // number paragraphs and is fearlessly incremented.
1203     sal_Int32 nContents = static_cast<sal_Int32>(rTextObject.mpImpl->GetContents().size());
1204     SAL_WARN_IF( nContents < 0, "editeng", "ImpEditEngine::InsertTextObject - contents overflow " << nContents);
1205     sal_Int32 nPara = aEditDoc.GetPos( aPaM.GetNode() );
1206 
1207     for (sal_Int32 n = 0; n < nContents; ++n, ++nPara)
1208     {
1209         const ContentInfo* pC = rTextObject.mpImpl->GetContents()[n].get();
1210         bool bNewContent = aPaM.GetNode()->Len() == 0;
1211         const sal_Int32 nStartPos = aPaM.GetIndex();
1212 
1213         aPaM = ImpFastInsertText( aPaM, pC->GetText() );
1214 
1215         ParaPortion& rPortion = FindParaPortion( aPaM.GetNode() );
1216         rPortion.MarkInvalid( nStartPos, pC->GetText().getLength() );
1217 
1218         // Character attributes ...
1219         bool bAllreadyHasAttribs = aPaM.GetNode()->GetCharAttribs().Count() != 0;
1220         size_t nNewAttribs = pC->GetCharAttribs().size();
1221         if ( nNewAttribs )
1222         {
1223             bool bUpdateFields = false;
1224             for (size_t nAttr = 0; nAttr < nNewAttribs; ++nAttr)
1225             {
1226                 const XEditAttribute& rX = pC->GetCharAttribs()[nAttr];
1227                 // Can happen when paragraphs > 16K, it is simply wrapped.
1228                     //TODO! Still true, still needed?
1229                 if ( rX.GetEnd() <= aPaM.GetNode()->Len() )
1230                 {
1231                     if ( !bAllreadyHasAttribs || rX.IsFeature() )
1232                     {
1233                         // Normal attributes then go faster ...
1234                         // Features shall not be inserted through
1235                         // EditDoc:: InsertAttrib, using FastInsertText they are
1236                         // already in the flow
1237                         DBG_ASSERT( rX.GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute too large!" );
1238                         EditCharAttrib* pAttr;
1239                         if ( !bConvertMetricOfItems )
1240                             pAttr = MakeCharAttrib( aEditDoc.GetItemPool(), *(rX.GetItem()), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos );
1241                         else
1242                         {
1243                             std::unique_ptr<SfxPoolItem> pNew(rX.GetItem()->Clone());
1244                             ConvertItem( pNew, eSourceUnit, eDestUnit );
1245                             pAttr = MakeCharAttrib( aEditDoc.GetItemPool(), *pNew, rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos );
1246                         }
1247                         DBG_ASSERT( pAttr->GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (1)" );
1248                         aPaM.GetNode()->GetCharAttribs().InsertAttrib( pAttr );
1249                         if ( pAttr->Which() == EE_FEATURE_FIELD )
1250                             bUpdateFields = true;
1251                     }
1252                     else
1253                     {
1254                         DBG_ASSERT( rX.GetEnd()+nStartPos <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (2)" );
1255                         // Tabs and other Features can not be inserted through InsertAttrib:
1256                         aEditDoc.InsertAttrib( aPaM.GetNode(), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos, *rX.GetItem() );
1257                     }
1258                 }
1259             }
1260             if ( bUpdateFields )
1261                 UpdateFields();
1262 
1263             // Otherwise, quick format => no attributes!
1264             rPortion.MarkSelectionInvalid( nStartPos );
1265         }
1266 
1267 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1268         CharAttribList::DbgCheckAttribs(aPaM.GetNode()->GetCharAttribs());
1269 #endif
1270 
1271         bool bParaAttribs = false;
1272         if ( bNewContent || ( ( n > 0 ) && ( n < (nContents-1) ) ) )
1273         {
1274             // only style and ParaAttribs when new paragraph, or
1275             // completely internal ...
1276             bParaAttribs = pC->GetParaAttribs().Count() != 0;
1277             if ( GetStyleSheetPool() && pC->GetStyle().getLength() )
1278             {
1279                 SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pC->GetStyle(), pC->GetFamily() ));
1280                 DBG_ASSERT( pStyle, "InsertBinTextObject - Style not found!" );
1281                 SetStyleSheet( nPara, pStyle );
1282             }
1283             if ( !bConvertMetricOfItems )
1284                 SetParaAttribs( aEditDoc.GetPos( aPaM.GetNode() ), pC->GetParaAttribs() );
1285             else
1286             {
1287                 SfxItemSet aAttribs( GetEmptyItemSet() );
1288                 ConvertAndPutItems( aAttribs, pC->GetParaAttribs(), &eSourceUnit, &eDestUnit );
1289                 SetParaAttribs( aEditDoc.GetPos( aPaM.GetNode() ), aAttribs );
1290             }
1291             if ( bNewContent && bUsePortionInfo )
1292             {
1293                 const XParaPortion& rXP = (*pPortionInfo)[n];
1294                 ParaPortion& rParaPortion = GetParaPortions()[ nPara ];
1295                 rParaPortion.nHeight = rXP.nHeight;
1296                 rParaPortion.nFirstLineOffset = rXP.nFirstLineOffset;
1297                 rParaPortion.bForceRepaint = true;
1298                 rParaPortion.SetValid();   // Do not format
1299 
1300                 // The Text Portions
1301                 rParaPortion.GetTextPortions().Reset();
1302                 sal_uInt16 nCount = rXP.aTextPortions.Count();
1303                 for ( sal_uInt16 _n = 0; _n < nCount; _n++ )
1304                 {
1305                     const TextPortion& rTextPortion = rXP.aTextPortions[_n];
1306                     TextPortion* pNew = new TextPortion( rTextPortion );
1307                     rParaPortion.GetTextPortions().Insert(_n, pNew);
1308                 }
1309 
1310                 // The lines
1311                 rParaPortion.GetLines().Reset();
1312                 nCount = rXP.aLines.Count();
1313                 for ( sal_uInt16 m = 0; m < nCount; m++ )
1314                 {
1315                     const EditLine& rLine = rXP.aLines[m];
1316                     EditLine* pNew = rLine.Clone();
1317                     pNew->SetInvalid(); // Paint again!
1318                     rParaPortion.GetLines().Insert(m, pNew);
1319                 }
1320 #ifdef DBG_UTIL
1321                 sal_uInt16 nTest;
1322                 int nTPLen = 0, nTxtLen = 0;
1323                 for ( nTest = rParaPortion.GetTextPortions().Count(); nTest; )
1324                     nTPLen += rParaPortion.GetTextPortions()[--nTest].GetLen();
1325                 for ( nTest = rParaPortion.GetLines().Count(); nTest; )
1326                     nTxtLen += rParaPortion.GetLines()[--nTest].GetLen();
1327                 DBG_ASSERT( ( nTPLen == rParaPortion.GetNode()->Len() ) && ( nTxtLen == rParaPortion.GetNode()->Len() ), "InsertBinTextObject: ParaPortion not completely formatted!" );
1328 #endif
1329             }
1330         }
1331         if ( !bParaAttribs ) // DefFont is not calculated for FastInsertParagraph
1332         {
1333             aPaM.GetNode()->GetCharAttribs().GetDefFont() = aEditDoc.GetDefFont();
1334             if ( aStatus.UseCharAttribs() )
1335                 aPaM.GetNode()->CreateDefFont();
1336         }
1337 
1338         if ( bNewContent && GetStatus().DoOnlineSpelling() && pC->GetWrongList() )
1339         {
1340             aPaM.GetNode()->SetWrongList( pC->GetWrongList()->Clone() );
1341         }
1342 
1343         // Wrap when followed by other ...
1344         if ( n < ( nContents-1) )
1345         {
1346             if ( bNewContent )
1347                 aPaM = ImpFastInsertParagraph( nPara+1 );
1348             else
1349                 aPaM = ImpInsertParaBreak( aPaM, false );
1350         }
1351     }
1352 
1353     aSel.Max() = aPaM;
1354     DBG_ASSERT( !aSel.DbgIsBuggy( aEditDoc ), "InsertBibTextObject: Selection broken!(1)" );
1355     return aSel;
1356 }
1357 
GetAllMisspellRanges(std::vector<editeng::MisspellRanges> & rRanges) const1358 void ImpEditEngine::GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const
1359 {
1360     std::vector<editeng::MisspellRanges> aRanges;
1361     const EditDoc& rDoc = GetEditDoc();
1362     for (sal_Int32 i = 0, n = rDoc.Count(); i < n; ++i)
1363     {
1364         const ContentNode* pNode = rDoc.GetObject(i);
1365         const WrongList* pWrongList = pNode->GetWrongList();
1366         if (!pWrongList)
1367             continue;
1368 
1369         aRanges.emplace_back(i, pWrongList->GetRanges());
1370     }
1371 
1372     aRanges.swap(rRanges);
1373 }
1374 
SetAllMisspellRanges(const std::vector<editeng::MisspellRanges> & rRanges)1375 void ImpEditEngine::SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges )
1376 {
1377     EditDoc& rDoc = GetEditDoc();
1378     for (auto const& rParaRanges : rRanges)
1379     {
1380         ContentNode* pNode = rDoc.GetObject(rParaRanges.mnParagraph);
1381         if (!pNode)
1382             continue;
1383 
1384         pNode->CreateWrongList();
1385         WrongList* pWrongList = pNode->GetWrongList();
1386         pWrongList->SetRanges(rParaRanges.maRanges);
1387     }
1388 }
1389 
GetLanguage(const EditPaM & rPaM,sal_Int32 * pEndPos) const1390 LanguageType ImpEditEngine::GetLanguage( const EditPaM& rPaM, sal_Int32* pEndPos ) const
1391 {
1392     short nScriptTypeI18N = GetI18NScriptType( rPaM, pEndPos ); // pEndPos will be valid now, pointing to ScriptChange or NodeLen
1393     SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
1394     sal_uInt16 nLangId = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType );
1395     const SvxLanguageItem* pLangItem = &static_cast<const SvxLanguageItem&>(rPaM.GetNode()->GetContentAttribs().GetItem( nLangId ));
1396     const EditCharAttrib* pAttr = rPaM.GetNode()->GetCharAttribs().FindAttrib( nLangId, rPaM.GetIndex() );
1397     if ( pAttr )
1398         pLangItem = static_cast<const SvxLanguageItem*>(pAttr->GetItem());
1399 
1400     if ( pEndPos && pAttr && ( pAttr->GetEnd() < *pEndPos ) )
1401         *pEndPos = pAttr->GetEnd();
1402 
1403     return pLangItem->GetLanguage();
1404 }
1405 
GetLocale(const EditPaM & rPaM) const1406 css::lang::Locale ImpEditEngine::GetLocale( const EditPaM& rPaM ) const
1407 {
1408     return LanguageTag( GetLanguage( rPaM ) ).getLocale();
1409 }
1410 
GetSpeller()1411 Reference< XSpellChecker1 > const & ImpEditEngine::GetSpeller()
1412 {
1413     if ( !xSpeller.is() )
1414         xSpeller = LinguMgr::GetSpellChecker();
1415     return xSpeller;
1416 }
1417 
1418 
CreateSpellInfo(bool bMultipleDocs)1419 void ImpEditEngine::CreateSpellInfo( bool bMultipleDocs )
1420 {
1421     if (!pSpellInfo)
1422         pSpellInfo.reset( new SpellInfo );
1423     else
1424         *pSpellInfo = SpellInfo();  // reset to default values
1425 
1426     pSpellInfo->bMultipleDoc = bMultipleDocs;
1427     // always spell draw objects completely, starting at the top.
1428     // (spelling in only a selection or not starting with the top requires
1429     // further changes elsewhere to work properly)
1430     pSpellInfo->aSpellStart = EPaM();
1431     pSpellInfo->aSpellTo    = EPaM( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND );
1432 }
1433 
1434 
Spell(EditView * pEditView,weld::Widget * pDialogParent,bool bMultipleDoc)1435 EESpellState ImpEditEngine::Spell(EditView* pEditView, weld::Widget* pDialogParent, bool bMultipleDoc)
1436 {
1437     SAL_WARN_IF( !xSpeller.is(), "editeng", "No Spell checker set!" );
1438 
1439     if ( !xSpeller.is() )
1440         return EESpellState::NoSpeller;
1441 
1442     aOnlineSpellTimer.Stop();
1443 
1444     // In MultipleDoc always from the front / rear ...
1445     if ( bMultipleDoc )
1446     {
1447         pEditView->pImpEditView->SetEditSelection( aEditDoc.GetStartPaM() );
1448     }
1449 
1450     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
1451     CreateSpellInfo( bMultipleDoc );
1452 
1453     bool bIsStart = false;
1454     if ( bMultipleDoc )
1455         bIsStart = true;    // Accessible from the front or from behind ...
1456     else if ( CreateEPaM( aEditDoc.GetStartPaM() ) == pSpellInfo->aSpellStart )
1457         bIsStart = true;
1458 
1459     {
1460         EditSpellWrapper aWrp(pDialogParent, bIsStart, pEditView );
1461         aWrp.SpellDocument();
1462     }
1463 
1464     if ( !bMultipleDoc )
1465     {
1466         pEditView->pImpEditView->DrawSelectionXOR();
1467         if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() )
1468             aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() );
1469         aCurSel.Min() = aCurSel.Max();
1470         pEditView->pImpEditView->SetEditSelection( aCurSel );
1471         pEditView->pImpEditView->DrawSelectionXOR();
1472         pEditView->ShowCursor( true, false );
1473     }
1474     EESpellState eState = pSpellInfo->eState;
1475     pSpellInfo.reset();
1476     return eState;
1477 }
1478 
1479 
HasConvertibleTextPortion(LanguageType nSrcLang)1480 bool ImpEditEngine::HasConvertibleTextPortion( LanguageType nSrcLang )
1481 {
1482     bool    bHasConvTxt = false;
1483 
1484     sal_Int32 nParas = pEditEngine->GetParagraphCount();
1485     for (sal_Int32 k = 0;  k < nParas;  ++k)
1486     {
1487         std::vector<sal_Int32> aPortions;
1488         pEditEngine->GetPortions( k, aPortions );
1489         for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos )
1490         {
1491             sal_Int32 nEnd   = aPortions[ nPos ];
1492             sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0;
1493 
1494             // if the paragraph is not empty we need to increase the index
1495             // by one since the attribute of the character left to the
1496             // specified position is evaluated.
1497             if (nEnd > nStart)  // empty para?
1498                 ++nStart;
1499             LanguageType nLangFound = pEditEngine->GetLanguage( k, nStart );
1500 #ifdef DEBUG
1501             lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) );
1502 #endif
1503             bHasConvTxt =   (nSrcLang == nLangFound) ||
1504                             (editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
1505                              editeng::HangulHanjaConversion::IsChinese( nSrcLang ));
1506             if (bHasConvTxt)
1507                 return bHasConvTxt;
1508        }
1509     }
1510 
1511     return bHasConvTxt;
1512 }
1513 
Convert(EditView * pEditView,weld::Widget * pDialogParent,LanguageType nSrcLang,LanguageType nDestLang,const vcl::Font * pDestFont,sal_Int32 nOptions,bool bIsInteractive,bool bMultipleDoc)1514 void ImpEditEngine::Convert( EditView* pEditView, weld::Widget* pDialogParent,
1515         LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont,
1516         sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc )
1517 {
1518     // modified version of ImpEditEngine::Spell
1519 
1520     // In MultipleDoc always from the front / rear ...
1521     if ( bMultipleDoc )
1522         pEditView->pImpEditView->SetEditSelection( aEditDoc.GetStartPaM() );
1523 
1524 
1525     // initialize pConvInfo
1526     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
1527     aCurSel.Adjust( aEditDoc );
1528     pConvInfo.reset(new ConvInfo);
1529     pConvInfo->bMultipleDoc = bMultipleDoc;
1530     pConvInfo->aConvStart = CreateEPaM( aCurSel.Min() );
1531 
1532     // if it is not just a selection and we are about to begin
1533     // with the current conversion for the very first time
1534     // we need to find the start of the current (initial)
1535     // convertible unit in order for the text conversion to give
1536     // the correct result for that. Since it is easier to obtain
1537     // the start of the word we use that though.
1538     if (!aCurSel.HasRange() && ImplGetBreakIterator().is())
1539     {
1540         EditPaM aWordStartPaM(  SelectWord( aCurSel, i18n::WordType::DICTIONARY_WORD ).Min() );
1541 
1542         // since #118246 / #117803 still occurs if the cursor is placed
1543         // between the two chinese characters to be converted (because both
1544         // of them are words on their own!) using the word boundary here does
1545         // not work. Thus since chinese conversion is not interactive we start
1546         // at the begin of the paragraph to solve the problem, i.e. have the
1547         // TextConversion service get those characters together in the same call.
1548         pConvInfo->aConvStart.nIndex = editeng::HangulHanjaConversion::IsChinese( nSrcLang )
1549             ? 0 : aWordStartPaM.GetIndex();
1550     }
1551 
1552     pConvInfo->aConvContinue = pConvInfo->aConvStart;
1553 
1554     bool bIsStart = false;
1555     if ( bMultipleDoc )
1556         bIsStart = true;    // Accessible from the front or from behind ...
1557     else if ( CreateEPaM( aEditDoc.GetStartPaM() ) == pConvInfo->aConvStart )
1558         bIsStart = true;
1559 
1560     TextConvWrapper aWrp( pDialogParent,
1561                           ::comphelper::getProcessComponentContext(),
1562                           LanguageTag::convertToLocale( nSrcLang ),
1563                           LanguageTag::convertToLocale( nDestLang ),
1564                           pDestFont,
1565                           nOptions, bIsInteractive,
1566                           bIsStart, pEditView );
1567 
1568 
1569     //!! optimization does not work since when update mode is false
1570     //!! the object is 'lying' about it portions, paragraphs,
1571     //!! EndPaM... later on.
1572     //!! Should not be a great problem since text boxes or cells in
1573     //!! Calc usually have only a rather short text.
1574     //
1575     // disallow formatting, updating the view, ... while
1576     // non-interactively converting the document. (saves time)
1577     //if (!bIsInteractive)
1578     //  SetUpdateMode( sal_False );
1579 
1580     aWrp.Convert();
1581 
1582     //if (!bIsInteractive)
1583     //SetUpdateMode( sal_True, 0, sal_True );
1584 
1585     if ( !bMultipleDoc )
1586     {
1587         pEditView->pImpEditView->DrawSelectionXOR();
1588         if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() )
1589             aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() );
1590         aCurSel.Min() = aCurSel.Max();
1591         pEditView->pImpEditView->SetEditSelection( aCurSel );
1592         pEditView->pImpEditView->DrawSelectionXOR();
1593         pEditView->ShowCursor( true, false );
1594     }
1595     pConvInfo.reset();
1596 }
1597 
1598 
SetLanguageAndFont(const ESelection & rESel,LanguageType nLang,sal_uInt16 nLangWhichId,const vcl::Font * pFont,sal_uInt16 nFontWhichId)1599 void ImpEditEngine::SetLanguageAndFont(
1600     const ESelection &rESel,
1601     LanguageType nLang, sal_uInt16 nLangWhichId,
1602     const vcl::Font *pFont,  sal_uInt16 nFontWhichId )
1603 {
1604     ESelection aOldSel = pActiveView->GetSelection();
1605     pActiveView->SetSelection( rESel );
1606 
1607     // set new language attribute
1608     SfxItemSet aNewSet( pActiveView->GetEmptyItemSet() );
1609     aNewSet.Put( SvxLanguageItem( nLang, nLangWhichId ) );
1610 
1611     // new font to be set?
1612     DBG_ASSERT( pFont, "target font missing?" );
1613     if (pFont)
1614     {
1615         // set new font attribute
1616         SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aNewSet.Get( nFontWhichId ) );
1617         aFontItem.SetFamilyName( pFont->GetFamilyName());
1618         aFontItem.SetFamily( pFont->GetFamilyType());
1619         aFontItem.SetStyleName( pFont->GetStyleName());
1620         aFontItem.SetPitch( pFont->GetPitch());
1621         aFontItem.SetCharSet( pFont->GetCharSet() );
1622         aNewSet.Put( aFontItem );
1623     }
1624 
1625     // apply new attributes
1626     pActiveView->SetAttribs( aNewSet );
1627 
1628     pActiveView->SetSelection( aOldSel );
1629 }
1630 
1631 
ImpConvert(OUString & rConvTxt,LanguageType & rConvTxtLang,EditView * pEditView,LanguageType nSrcLang,const ESelection & rConvRange,bool bAllowImplicitChangesForNotConvertibleText,LanguageType nTargetLang,const vcl::Font * pTargetFont)1632 void ImpEditEngine::ImpConvert( OUString &rConvTxt, LanguageType &rConvTxtLang,
1633         EditView* pEditView, LanguageType nSrcLang, const ESelection &rConvRange,
1634         bool bAllowImplicitChangesForNotConvertibleText,
1635         LanguageType nTargetLang, const vcl::Font *pTargetFont  )
1636 {
1637     // modified version of ImpEditEngine::ImpSpell
1638 
1639     // looks for next convertible text portion to be passed on to the wrapper
1640 
1641     OUString aRes;
1642     LanguageType nResLang = LANGUAGE_NONE;
1643 
1644     EditPaM aPos( CreateEditPaM( pConvInfo->aConvContinue ) );
1645     EditSelection aCurSel( aPos, aPos );
1646 
1647     OUString aWord;
1648 
1649     while (aRes.isEmpty())
1650     {
1651         // empty paragraph found that needs to have language and font set?
1652         if (bAllowImplicitChangesForNotConvertibleText &&
1653             pEditEngine->GetText( pConvInfo->aConvContinue.nPara ).isEmpty())
1654         {
1655             sal_Int32 nPara = pConvInfo->aConvContinue.nPara;
1656             ESelection aESel( nPara, 0, nPara, 0 );
1657             // see comment for below same function call
1658             SetLanguageAndFont( aESel,
1659                     nTargetLang, EE_CHAR_LANGUAGE_CJK,
1660                     pTargetFont, EE_CHAR_FONTINFO_CJK );
1661         }
1662 
1663 
1664         if (pConvInfo->aConvContinue.nPara  == pConvInfo->aConvTo.nPara &&
1665             pConvInfo->aConvContinue.nIndex >= pConvInfo->aConvTo.nIndex)
1666             break;
1667 
1668         sal_Int32 nAttribStart = -1;
1669         sal_Int32 nAttribEnd   = -1;
1670         sal_Int32 nCurPos      = -1;
1671         EPaM aCurStart = CreateEPaM( aCurSel.Min() );
1672         std::vector<sal_Int32> aPortions;
1673         pEditEngine->GetPortions( aCurStart.nPara, aPortions );
1674         for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos )
1675         {
1676             const sal_Int32 nEnd   = aPortions[ nPos ];
1677             const sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0;
1678 
1679             // the language attribute is obtained from the left character
1680             // (like usually all other attributes)
1681             // thus we usually have to add 1 in order to get the language
1682             // of the text right to the cursor position
1683             const sal_Int32 nLangIdx = nEnd > nStart ? nStart + 1 : nStart;
1684             LanguageType nLangFound = pEditEngine->GetLanguage( aCurStart.nPara, nLangIdx );
1685 #ifdef DEBUG
1686             lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) );
1687 #endif
1688             bool bLangOk =  (nLangFound == nSrcLang) ||
1689                                 (editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
1690                                  editeng::HangulHanjaConversion::IsChinese( nSrcLang ));
1691 
1692             if (nAttribEnd>=0) // start already found?
1693             {
1694                 DBG_ASSERT(nEnd >= aCurStart.nIndex, "error while scanning attributes (a)" );
1695                 DBG_ASSERT(nEnd >= nAttribEnd, "error while scanning attributes (b)" );
1696                 if (/*nEnd >= aCurStart.nIndex &&*/ nLangFound == nResLang)
1697                     nAttribEnd = nEnd;
1698                 else  // language attrib has changed
1699                     break;
1700             }
1701             if (nAttribStart<0 && // start not yet found?
1702                 nEnd > aCurStart.nIndex && bLangOk)
1703             {
1704                 nAttribStart = nStart;
1705                 nAttribEnd   = nEnd;
1706                 nResLang = nLangFound;
1707             }
1708             //! the list of portions may have changed compared to the previous
1709             //! call to this function (because of possibly changed language
1710             //! attribute!)
1711             //! But since we don't want to start in the already processed part
1712             //! we clip the start accordingly.
1713             if (nAttribStart >= 0 && nAttribStart < aCurStart.nIndex)
1714             {
1715                 nAttribStart = aCurStart.nIndex;
1716             }
1717 
1718             // check script type to the right of the start of the current portion
1719             EditPaM aPaM( CreateEditPaM( EPaM(aCurStart.nPara, nLangIdx) ) );
1720             bool bIsAsianScript = (i18n::ScriptType::ASIAN == GetI18NScriptType( aPaM ));
1721             // not yet processed text part with for conversion
1722             // not suitable language found that needs to be changed?
1723             if (bAllowImplicitChangesForNotConvertibleText &&
1724                 !bLangOk && !bIsAsianScript && nEnd > aCurStart.nIndex)
1725             {
1726                 ESelection aESel( aCurStart.nPara, nStart, aCurStart.nPara, nEnd );
1727                 // set language and font to target language and font of conversion
1728                 //! Now this especially includes all non convertible text e.g.
1729                 //! spaces, empty paragraphs and western text.
1730                 // This is in order for every *new* text entered at *any* position to
1731                 // have the correct language and font attributes set.
1732                 SetLanguageAndFont( aESel,
1733                         nTargetLang, EE_CHAR_LANGUAGE_CJK,
1734                         pTargetFont, EE_CHAR_FONTINFO_CJK );
1735             }
1736 
1737             nCurPos = nEnd;
1738         }
1739 
1740         if (nAttribStart>=0 && nAttribEnd>=0)
1741         {
1742             aCurSel.Min().SetIndex( nAttribStart );
1743             aCurSel.Max().SetIndex( nAttribEnd );
1744         }
1745         else if (nCurPos>=0)
1746         {
1747             // set selection to end of scanned text
1748             // (used to set the position where to continue from later on)
1749             aCurSel.Min().SetIndex( nCurPos );
1750             aCurSel.Max().SetIndex( nCurPos );
1751         }
1752 
1753         if ( !pConvInfo->bConvToEnd )
1754         {
1755             EPaM aEPaM( CreateEPaM( aCurSel.Min() ) );
1756             if ( !( aEPaM < pConvInfo->aConvTo ) )
1757                 break;
1758         }
1759 
1760         // clip selected word to the converted area
1761         // (main use when conversion starts/ends **within** a word)
1762         EditPaM aPaM( CreateEditPaM( pConvInfo->aConvStart ) );
1763         if (pConvInfo->bConvToEnd &&
1764             aCurSel.Min().GetNode() == aPaM.GetNode() &&
1765             aCurSel.Min().GetIndex() < aPaM.GetIndex())
1766                 aCurSel.Min().SetIndex( aPaM.GetIndex() );
1767         aPaM = CreateEditPaM( pConvInfo->aConvContinue );
1768         if (aCurSel.Min().GetNode() == aPaM.GetNode() &&
1769             aCurSel.Min().GetIndex() < aPaM.GetIndex())
1770                 aCurSel.Min().SetIndex( aPaM.GetIndex() );
1771         aPaM = CreateEditPaM( pConvInfo->aConvTo );
1772         if ((!pConvInfo->bConvToEnd || rConvRange.HasRange())&&
1773             aCurSel.Max().GetNode() == aPaM.GetNode() &&
1774             aCurSel.Max().GetIndex() > aPaM.GetIndex())
1775                 aCurSel.Max().SetIndex( aPaM.GetIndex() );
1776 
1777         aWord = GetSelected( aCurSel );
1778 
1779         if ( !aWord.isEmpty() /* && bLangOk */)
1780             aRes = aWord;
1781 
1782         // move to next word/paragraph if necessary
1783         if ( aRes.isEmpty() )
1784             aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD );
1785 
1786         pConvInfo->aConvContinue = CreateEPaM( aCurSel.Max() );
1787     }
1788 
1789     pEditView->pImpEditView->DrawSelectionXOR();
1790     pEditView->pImpEditView->SetEditSelection( aCurSel );
1791     pEditView->pImpEditView->DrawSelectionXOR();
1792     pEditView->ShowCursor( true, false );
1793 
1794     rConvTxt = aRes;
1795     if ( !rConvTxt.isEmpty() )
1796         rConvTxtLang = nResLang;
1797 }
1798 
1799 
ImpSpell(EditView * pEditView)1800 Reference< XSpellAlternatives > ImpEditEngine::ImpSpell( EditView* pEditView )
1801 {
1802     DBG_ASSERT( xSpeller.is(), "No spell checker set!" );
1803 
1804     ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 );
1805     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
1806     aCurSel.Min() = aCurSel.Max();
1807 
1808     Reference< XSpellAlternatives > xSpellAlt;
1809     Sequence< PropertyValue > aEmptySeq;
1810     while (!xSpellAlt.is())
1811     {
1812         // Known (most likely) bug: If SpellToCurrent, the current has to be
1813         // corrected at each replacement, otherwise it may not fit exactly in
1814         // the end ...
1815         if ( pSpellInfo->bSpellToEnd || pSpellInfo->bMultipleDoc )
1816         {
1817             if ( aCurSel.Max().GetNode() == pLastNode )
1818             {
1819                 if ( aCurSel.Max().GetIndex() >= pLastNode->Len() )
1820                     break;
1821             }
1822         }
1823         else if ( !pSpellInfo->bSpellToEnd )
1824         {
1825             EPaM aEPaM( CreateEPaM( aCurSel.Max() ) );
1826             if ( !( aEPaM < pSpellInfo->aSpellTo ) )
1827                 break;
1828         }
1829 
1830         aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
1831         OUString aWord = GetSelected( aCurSel );
1832 
1833         // If afterwards a dot, this must be handed over!
1834         // If an abbreviation ...
1835         if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) )
1836         {
1837             sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() );
1838             if ( cNext == '.' )
1839             {
1840                 aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 );
1841                 aWord += OUStringChar(cNext);
1842             }
1843         }
1844 
1845         if ( !aWord.isEmpty() )
1846         {
1847             LanguageType eLang = GetLanguage( aCurSel.Max() );
1848             SvxSpellWrapper::CheckSpellLang( xSpeller, eLang );
1849             xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq );
1850         }
1851 
1852         if ( !xSpellAlt.is() )
1853             aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD );
1854         else
1855             pSpellInfo->eState = EESpellState::ErrorFound;
1856     }
1857 
1858     pEditView->pImpEditView->DrawSelectionXOR();
1859     pEditView->pImpEditView->SetEditSelection( aCurSel );
1860     pEditView->pImpEditView->DrawSelectionXOR();
1861     pEditView->ShowCursor( true, false );
1862     return xSpellAlt;
1863 }
1864 
ImpFindNextError(EditSelection & rSelection)1865 Reference< XSpellAlternatives > ImpEditEngine::ImpFindNextError(EditSelection& rSelection)
1866 {
1867     EditSelection aCurSel( rSelection.Min() );
1868 
1869     Reference< XSpellAlternatives > xSpellAlt;
1870     Sequence< PropertyValue > aEmptySeq;
1871     while (!xSpellAlt.is())
1872     {
1873         //check if the end of the selection has been reached
1874         {
1875             EPaM aEPaM( CreateEPaM( aCurSel.Max() ) );
1876             if ( !( aEPaM < CreateEPaM( rSelection.Max()) ) )
1877                 break;
1878         }
1879 
1880         aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
1881         OUString aWord = GetSelected( aCurSel );
1882 
1883         // If afterwards a dot, this must be handed over!
1884         // If an abbreviation ...
1885         if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) )
1886         {
1887             sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() );
1888             if ( cNext == '.' )
1889             {
1890                 aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 );
1891                 aWord += OUStringChar(cNext);
1892             }
1893         }
1894 
1895         if ( !aWord.isEmpty() )
1896             xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(GetLanguage( aCurSel.Max() )), aEmptySeq );
1897 
1898         if ( !xSpellAlt.is() )
1899             aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD );
1900         else
1901         {
1902             pSpellInfo->eState = EESpellState::ErrorFound;
1903             rSelection = aCurSel;
1904         }
1905     }
1906     return xSpellAlt;
1907 }
1908 
SpellSentence(EditView const & rEditView,svx::SpellPortions & rToFill)1909 bool ImpEditEngine::SpellSentence(EditView const & rEditView,
1910     svx::SpellPortions& rToFill )
1911 {
1912     bool bRet = false;
1913     EditSelection aCurSel( rEditView.pImpEditView->GetEditSelection() );
1914     if(!pSpellInfo)
1915         CreateSpellInfo( true );
1916     pSpellInfo->aCurSentenceStart = aCurSel.Min();
1917     DBG_ASSERT( xSpeller.is(), "No spell checker set!" );
1918     pSpellInfo->aLastSpellPortions.clear();
1919     pSpellInfo->aLastSpellContentSelections.clear();
1920     rToFill.clear();
1921     //if no selection previously exists the range is extended to the end of the object
1922     if (!aCurSel.HasRange())
1923     {
1924         ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1);
1925         aCurSel.Max() = EditPaM(pLastNode, pLastNode->Len());
1926     }
1927     // check for next error in aCurSel and set aCurSel to that one if any was found
1928     Reference< XSpellAlternatives > xAlt = ImpFindNextError(aCurSel);
1929     if (xAlt.is())
1930     {
1931         bRet = true;
1932         //find the sentence boundaries
1933         EditSelection aSentencePaM = SelectSentence(aCurSel);
1934         //make sure that the sentence is never smaller than the error range!
1935         if(aSentencePaM.Max().GetIndex() < aCurSel.Max().GetIndex())
1936             aSentencePaM.Max() = aCurSel.Max();
1937         //add the portion preceding the error
1938         EditSelection aStartSelection(aSentencePaM.Min(), aCurSel.Min());
1939         if(aStartSelection.HasRange())
1940             AddPortionIterated(rEditView, aStartSelection, nullptr, rToFill);
1941         //add the error portion
1942         AddPortionIterated(rEditView, aCurSel, xAlt, rToFill);
1943         //find the end of the sentence
1944         //search for all errors in the rest of the sentence and add all the portions
1945         do
1946         {
1947             EditSelection aNextSel(aCurSel.Max(), aSentencePaM.Max());
1948             xAlt = ImpFindNextError(aNextSel);
1949             if(xAlt.is())
1950             {
1951                 //add the part between the previous and the current error
1952                 AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aNextSel.Min()), nullptr, rToFill);
1953                 //add the current error
1954                 AddPortionIterated(rEditView, aNextSel, xAlt, rToFill);
1955             }
1956             else
1957                 AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aSentencePaM.Max()), xAlt, rToFill);
1958             aCurSel = aNextSel;
1959         }
1960         while( xAlt.is() );
1961 
1962         //set the selection to the end of the current sentence
1963         rEditView.pImpEditView->SetEditSelection(aSentencePaM.Max());
1964     }
1965     return bRet;
1966 }
1967 
1968 // Adds one portion to the SpellPortions
AddPortion(const EditSelection & rSel,const uno::Reference<XSpellAlternatives> & xAlt,svx::SpellPortions & rToFill,bool bIsField)1969 void ImpEditEngine::AddPortion(
1970                             const EditSelection& rSel,
1971                             const uno::Reference< XSpellAlternatives >& xAlt,
1972                             svx::SpellPortions& rToFill,
1973                             bool bIsField)
1974 {
1975     if(!rSel.HasRange())
1976         return;
1977 
1978     svx::SpellPortion aPortion;
1979     aPortion.sText = GetSelected( rSel );
1980     aPortion.eLanguage = GetLanguage( rSel.Min() );
1981     aPortion.xAlternatives = xAlt;
1982     aPortion.bIsField = bIsField;
1983     rToFill.push_back(aPortion);
1984 
1985     //save the spelled portions for later use
1986     pSpellInfo->aLastSpellPortions.push_back(aPortion);
1987     pSpellInfo->aLastSpellContentSelections.push_back(rSel);
1988 }
1989 
1990 // Adds one or more portions of text to the SpellPortions depending on language changes
AddPortionIterated(EditView const & rEditView,const EditSelection & rSel,const Reference<XSpellAlternatives> & xAlt,svx::SpellPortions & rToFill)1991 void ImpEditEngine::AddPortionIterated(
1992                             EditView const & rEditView,
1993                             const EditSelection& rSel,
1994                             const Reference< XSpellAlternatives >& xAlt,
1995                             svx::SpellPortions& rToFill)
1996 {
1997     if (!rSel.HasRange())
1998         return;
1999 
2000     if(xAlt.is())
2001     {
2002         AddPortion(rSel, xAlt, rToFill, false);
2003     }
2004     else
2005     {
2006         //iterate and search for language attribute changes
2007         //save the start and end positions
2008         bool bTest = rSel.Min().GetIndex() <= rSel.Max().GetIndex();
2009         EditPaM aStart(bTest ? rSel.Min() : rSel.Max());
2010         EditPaM aEnd(bTest ? rSel.Max() : rSel.Min());
2011         //iterate over the text to find changes in language
2012         //set the mark equal to the point
2013         EditPaM aCursor(aStart);
2014         rEditView.pImpEditView->SetEditSelection( aCursor );
2015         LanguageType eStartLanguage = GetLanguage( aCursor );
2016         //search for a field attribute at the beginning - only the end position
2017         //of this field is kept to end a portion at that position
2018         const EditCharAttrib* pFieldAttr = aCursor.GetNode()->GetCharAttribs().
2019                                                 FindFeature( aCursor.GetIndex() );
2020         bool bIsField = pFieldAttr &&
2021                 pFieldAttr->GetStart() == aCursor.GetIndex() &&
2022                 pFieldAttr->GetStart() != pFieldAttr->GetEnd() &&
2023                 pFieldAttr->Which() == EE_FEATURE_FIELD;
2024         sal_Int32 nEndField = bIsField ? pFieldAttr->GetEnd() : -1;
2025         do
2026         {
2027             aCursor = CursorRight( aCursor);
2028             //determine whether a field and has been reached
2029             bool bIsEndField = nEndField == aCursor.GetIndex();
2030             //search for a new field attribute
2031             const EditCharAttrib* _pFieldAttr = aCursor.GetNode()->GetCharAttribs().
2032                                                     FindFeature( aCursor.GetIndex() );
2033             bIsField = _pFieldAttr &&
2034                     _pFieldAttr->GetStart() == aCursor.GetIndex() &&
2035                     _pFieldAttr->GetStart() != _pFieldAttr->GetEnd() &&
2036                     _pFieldAttr->Which() == EE_FEATURE_FIELD;
2037             //on every new field move the end position
2038             if (bIsField)
2039                 nEndField = _pFieldAttr->GetEnd();
2040 
2041             LanguageType eCurLanguage = GetLanguage( aCursor );
2042             if(eCurLanguage != eStartLanguage || bIsField || bIsEndField)
2043             {
2044                 eStartLanguage = eCurLanguage;
2045                 //go one step back - the cursor currently selects the first character
2046                 //with a different language
2047                 //create a selection from start to the current Cursor
2048                 EditSelection aSelection(aStart, aCursor);
2049                 AddPortion(aSelection, xAlt, rToFill, bIsEndField);
2050                 aStart = aCursor;
2051             }
2052         }
2053         while(aCursor.GetIndex() < aEnd.GetIndex());
2054         EditSelection aSelection(aStart, aCursor);
2055         AddPortion(aSelection, xAlt, rToFill, bIsField);
2056     }
2057 }
2058 
ApplyChangedSentence(EditView const & rEditView,const svx::SpellPortions & rNewPortions,bool bRecheck)2059 void ImpEditEngine::ApplyChangedSentence(EditView const & rEditView,
2060     const svx::SpellPortions& rNewPortions,
2061     bool bRecheck )
2062 {
2063     // Note: rNewPortions.size() == 0 is valid and happens when the whole
2064     // sentence got removed in the dialog
2065 
2066     DBG_ASSERT(pSpellInfo, "pSpellInfo not initialized");
2067     if (!pSpellInfo || pSpellInfo->aLastSpellPortions.empty())  // no portions -> no text to be changed
2068         return;
2069 
2070     // get current paragraph length to calculate later on how the sentence length changed,
2071     // in order to place the cursor at the end of the sentence again
2072     EditSelection aOldSel( rEditView.pImpEditView->GetEditSelection() );
2073     sal_Int32 nOldLen = aOldSel.Max().GetNode()->Len();
2074 
2075     UndoActionStart( EDITUNDO_INSERT );
2076     if(pSpellInfo->aLastSpellPortions.size() == rNewPortions.size())
2077     {
2078         DBG_ASSERT( !rNewPortions.empty(), "rNewPortions should not be empty here" );
2079         DBG_ASSERT( pSpellInfo->aLastSpellPortions.size() == pSpellInfo->aLastSpellContentSelections.size(),
2080                 "aLastSpellPortions and aLastSpellContentSelections size mismatch" );
2081 
2082         //the simple case: the same number of elements on both sides
2083         //each changed element has to be applied to the corresponding source element
2084         svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end();
2085         svx::SpellPortions::const_iterator aCurrentOldPortion = pSpellInfo->aLastSpellPortions.end();
2086         SpellContentSelections::const_iterator aCurrentOldPosition = pSpellInfo->aLastSpellContentSelections.end();
2087         bool bSetToEnd = false;
2088         do
2089         {
2090             --aCurrentNewPortion;
2091             --aCurrentOldPortion;
2092             --aCurrentOldPosition;
2093             //set the cursor to the end of the sentence - necessary to
2094             //resume there at the next step
2095             if(!bSetToEnd)
2096             {
2097                 bSetToEnd = true;
2098                 rEditView.pImpEditView->SetEditSelection( aCurrentOldPosition->Max() );
2099             }
2100 
2101             SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( aCurrentNewPortion->eLanguage );
2102             sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE;
2103             switch(nScriptType)
2104             {
2105                 case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break;
2106                 case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break;
2107                 default: break;
2108             }
2109             if(aCurrentNewPortion->sText != aCurrentOldPortion->sText)
2110             {
2111                 //change text and apply language
2112                 SfxItemSet aSet( aEditDoc.GetItemPool(), {{nLangWhichId, nLangWhichId}});
2113                 aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId));
2114                 SetAttribs( *aCurrentOldPosition, aSet );
2115                 ImpInsertText( *aCurrentOldPosition, aCurrentNewPortion->sText );
2116             }
2117             else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
2118             {
2119                 //apply language
2120                 SfxItemSet aSet( aEditDoc.GetItemPool(), {{nLangWhichId, nLangWhichId}});
2121                 aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId));
2122                 SetAttribs( *aCurrentOldPosition, aSet );
2123             }
2124         }
2125         while(aCurrentNewPortion != rNewPortions.begin());
2126     }
2127     else
2128     {
2129         DBG_ASSERT( !pSpellInfo->aLastSpellContentSelections.empty(), "aLastSpellContentSelections should not be empty here" );
2130 
2131         //select the complete sentence
2132         SpellContentSelections::const_iterator aCurrentEndPosition = pSpellInfo->aLastSpellContentSelections.end();
2133         --aCurrentEndPosition;
2134         SpellContentSelections::const_iterator aCurrentStartPosition = pSpellInfo->aLastSpellContentSelections.begin();
2135         EditSelection aAllSentence(aCurrentStartPosition->Min(), aCurrentEndPosition->Max());
2136 
2137         //delete the sentence completely
2138         ImpDeleteSelection( aAllSentence );
2139         EditPaM aCurrentPaM = aAllSentence.Min();
2140         for(const auto& rCurrentNewPortion : rNewPortions)
2141         {
2142             //set the language attribute
2143             LanguageType eCurLanguage = GetLanguage( aCurrentPaM );
2144             if(eCurLanguage != rCurrentNewPortion.eLanguage)
2145             {
2146                 SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( rCurrentNewPortion.eLanguage );
2147                 sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE;
2148                 switch(nScriptType)
2149                 {
2150                     case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break;
2151                     case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break;
2152                     default: break;
2153                 }
2154                 SfxItemSet aSet( aEditDoc.GetItemPool(), {{nLangWhichId, nLangWhichId}});
2155                 aSet.Put(SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId));
2156                 SetAttribs( aCurrentPaM, aSet );
2157             }
2158             //insert the new string and set the cursor to the end of the inserted string
2159             aCurrentPaM = ImpInsertText( aCurrentPaM , rCurrentNewPortion.sText );
2160         }
2161     }
2162     UndoActionEnd();
2163 
2164     EditPaM aNext;
2165     if (bRecheck)
2166         aNext = pSpellInfo->aCurSentenceStart;
2167     else
2168     {
2169         // restore cursor position to the end of the modified sentence.
2170         // (This will define the continuation position for spell/grammar checking)
2171         // First: check if the sentence/para length changed
2172         const sal_Int32 nDelta = rEditView.pImpEditView->GetEditSelection().Max().GetNode()->Len() - nOldLen;
2173         const sal_Int32 nEndOfSentence = aOldSel.Max().GetIndex() + nDelta;
2174         aNext = EditPaM( aOldSel.Max().GetNode(), nEndOfSentence );
2175     }
2176     rEditView.pImpEditView->SetEditSelection( aNext );
2177 
2178     FormatAndUpdate();
2179     aEditDoc.SetModified(true);
2180 }
2181 
PutSpellingToSentenceStart(EditView const & rEditView)2182 void ImpEditEngine::PutSpellingToSentenceStart( EditView const & rEditView )
2183 {
2184     if( pSpellInfo && !pSpellInfo->aLastSpellContentSelections.empty() )
2185     {
2186         rEditView.pImpEditView->SetEditSelection( pSpellInfo->aLastSpellContentSelections.begin()->Min() );
2187     }
2188 }
2189 
2190 
DoOnlineSpelling(ContentNode * pThisNodeOnly,bool bSpellAtCursorPos,bool bInterruptible)2191 void ImpEditEngine::DoOnlineSpelling( ContentNode* pThisNodeOnly, bool bSpellAtCursorPos, bool bInterruptible )
2192 {
2193     /*
2194      It will iterate over all the paragraphs, paragraphs with only
2195      invalidated wrong list will be checked ...
2196 
2197      All the words are checked in the invalidated region. Is a word wrong,
2198      but not in the wrong list, or vice versa, the range of the word will be
2199      invalidated
2200      (no Invalidate, but if only transitions wrong from right =>, simple Paint,
2201       even out properly with VDev on transitions from wrong => right)
2202     */
2203 
2204     if ( !xSpeller.is() )
2205         return;
2206 
2207     EditPaM aCursorPos;
2208     if( pActiveView && !bSpellAtCursorPos )
2209     {
2210         aCursorPos = pActiveView->pImpEditView->GetEditSelection().Max();
2211     }
2212 
2213     bool bRestartTimer = false;
2214 
2215     ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count() - 1 );
2216     sal_Int32 nNodes = GetEditDoc().Count();
2217     sal_Int32 nInvalids = 0;
2218     Sequence< PropertyValue > aEmptySeq;
2219     for ( sal_Int32 n = 0; n < nNodes; n++ )
2220     {
2221         ContentNode* pNode = GetEditDoc().GetObject( n );
2222         if ( pThisNodeOnly )
2223             pNode = pThisNodeOnly;
2224 
2225         pNode->EnsureWrongList();
2226         if (!pNode->GetWrongList()->IsValid())
2227         {
2228             WrongList* pWrongList = pNode->GetWrongList();
2229             const size_t nInvStart = pWrongList->GetInvalidStart();
2230             const size_t nInvEnd = pWrongList->GetInvalidEnd();
2231 
2232             sal_Int32 nPaintFrom = -1;
2233             sal_Int32 nPaintTo = 0;
2234             bool bSimpleRepaint = true;
2235 
2236             pWrongList->SetValid();
2237 
2238             EditPaM aPaM( pNode, nInvStart );
2239             EditSelection aSel( aPaM, aPaM );
2240             while ( aSel.Max().GetNode() == pNode )
2241             {
2242                 if ( ( o3tl::make_unsigned(aSel.Min().GetIndex()) > nInvEnd )
2243                         || ( ( aSel.Max().GetNode() == pLastNode ) && ( aSel.Max().GetIndex() >= pLastNode->Len() ) ) )
2244                     break;  // Document end or end of invalid region
2245 
2246                 aSel = SelectWord( aSel, i18n::WordType::DICTIONARY_WORD );
2247                 // If afterwards a dot, this must be handed over!
2248                 // If an abbreviation ...
2249                 bool bDottAdded = false;
2250                 if ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() )
2251                 {
2252                     sal_Unicode cNext = aSel.Max().GetNode()->GetChar( aSel.Max().GetIndex() );
2253                     if ( cNext == '.' )
2254                     {
2255                         aSel.Max().SetIndex( aSel.Max().GetIndex()+1 );
2256                         bDottAdded = true;
2257                     }
2258                 }
2259                 OUString aWord = GetSelected(aSel);
2260 
2261                 bool bChanged = false;
2262                 if (!aWord.isEmpty())
2263                 {
2264                     const sal_Int32 nWStart = aSel.Min().GetIndex();
2265                     const sal_Int32 nWEnd = aSel.Max().GetIndex();
2266                     if ( !xSpeller->isValid( aWord, static_cast<sal_uInt16>(GetLanguage( EditPaM( aSel.Min().GetNode(), nWStart+1 ) )), aEmptySeq ) )
2267                     {
2268                         // Check if already marked correctly...
2269                         const sal_Int32 nXEnd = bDottAdded ? nWEnd -1 : nWEnd;
2270                         if ( !pWrongList->HasWrong( nWStart, nXEnd ) )
2271                         {
2272                             // Mark Word as wrong...
2273                             // But only when not at Cursor-Position...
2274                             bool bCursorPos = false;
2275                             if ( aCursorPos.GetNode() == pNode )
2276                             {
2277                                 if ( ( nWStart <= aCursorPos.GetIndex() ) && nWEnd >= aCursorPos.GetIndex() )
2278                                     bCursorPos = true;
2279                             }
2280                             if ( bCursorPos )
2281                             {
2282                                 // Then continue to mark as invalid ...
2283                                 pWrongList->ResetInvalidRange(nWStart, nWEnd);
2284                                 bRestartTimer = true;
2285                             }
2286                             else
2287                             {
2288                                 // It may be that the Wrongs in the list are not
2289                                 // spanning exactly over words because the
2290                                 // WordDelimiters during expansion are not
2291                                 // evaluated.
2292                                 pWrongList->InsertWrong(nWStart, nXEnd);
2293                                 bChanged = true;
2294                             }
2295                         }
2296                     }
2297                     else
2298                     {
2299                         // Check if not marked as wrong
2300                         if ( pWrongList->HasAnyWrong( nWStart, nWEnd ) )
2301                         {
2302                             pWrongList->ClearWrongs( nWStart, nWEnd, pNode );
2303                             bSimpleRepaint = false;
2304                             bChanged = true;
2305                         }
2306                     }
2307                     if ( bChanged  )
2308                     {
2309                         if ( nPaintFrom<0 )
2310                             nPaintFrom = nWStart;
2311                         nPaintTo = nWEnd;
2312                     }
2313                 }
2314 
2315                 EditPaM aLastEnd( aSel.Max() );
2316                 aSel = WordRight( aSel.Max(), i18n::WordType::DICTIONARY_WORD );
2317                 if ( bChanged && ( aSel.Min().GetNode() == pNode ) &&
2318                         ( aSel.Min().GetIndex()-aLastEnd.GetIndex() > 1 ) )
2319                 {
2320                     // If two words are separated by more than one blank, it
2321                     // can happen that when splitting a Wrongs the start of
2322                     // the second word is before the actually word
2323                     pWrongList->ClearWrongs( aLastEnd.GetIndex(), aSel.Min().GetIndex(), pNode );
2324                 }
2325             }
2326 
2327             // Invalidate?
2328             if ( nPaintFrom>=0 )
2329             {
2330                 aStatus.GetStatusWord() |= EditStatusFlags::WRONGWORDCHANGED;
2331                 CallStatusHdl();
2332 
2333                 if (!aEditViews.empty())
2334                 {
2335                     // For SimpleRepaint one was painted over a range without
2336                     // reaching VDEV, but then one would have to intersect, c
2337                     // clipping, ... over all views. Probably not worthwhile.
2338                     EditPaM aStartPaM( pNode, nPaintFrom );
2339                     EditPaM aEndPaM( pNode, nPaintTo );
2340                     tools::Rectangle aStartCursor( PaMtoEditCursor( aStartPaM ) );
2341                     tools::Rectangle aEndCursor( PaMtoEditCursor( aEndPaM ) );
2342                     DBG_ASSERT( aInvalidRect.IsEmpty(), "InvalidRect set!" );
2343                     aInvalidRect.SetLeft( 0 );
2344                     aInvalidRect.SetRight( GetPaperSize().Width() );
2345                     aInvalidRect.SetTop( aStartCursor.Top() );
2346                     aInvalidRect.SetBottom( aEndCursor.Bottom() );
2347                     if ( pActiveView && pActiveView->HasSelection() )
2348                     {
2349                         // Then no output through VDev.
2350                         UpdateViews();
2351                     }
2352                     else if ( bSimpleRepaint )
2353                     {
2354                         for (EditView* pView : aEditViews)
2355                         {
2356                             tools::Rectangle aClipRect( aInvalidRect );
2357                             aClipRect.Intersection( pView->GetVisArea() );
2358                             if ( !aClipRect.IsEmpty() )
2359                             {
2360                                 // convert to window coordinates...
2361                                 aClipRect.SetPos( pView->pImpEditView->GetWindowPos( aClipRect.TopLeft() ) );
2362                                 pView->pImpEditView->InvalidateAtWindow(aClipRect);
2363                             }
2364                         }
2365                     }
2366                     else
2367                     {
2368                         UpdateViews( pActiveView );
2369                     }
2370                     aInvalidRect = tools::Rectangle();
2371                 }
2372             }
2373             // After two corrected nodes give up the control...
2374             nInvalids++;
2375             if ( bInterruptible && ( nInvalids >= 2 ) )
2376             {
2377                 bRestartTimer = true;
2378                 break;
2379             }
2380         }
2381 
2382         if ( pThisNodeOnly )
2383             break;
2384     }
2385     if ( bRestartTimer )
2386         aOnlineSpellTimer.Start();
2387 }
2388 
2389 
HasSpellErrors()2390 EESpellState ImpEditEngine::HasSpellErrors()
2391 {
2392     DBG_ASSERT( xSpeller.is(), "No spell checker set!" );
2393 
2394     ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count() - 1 );
2395     EditSelection aCurSel( aEditDoc.GetStartPaM() );
2396 
2397     OUString aWord;
2398     Reference< XSpellAlternatives > xSpellAlt;
2399     Sequence< PropertyValue > aEmptySeq;
2400     while ( !xSpellAlt.is() )
2401     {
2402         if ( ( aCurSel.Max().GetNode() == pLastNode ) &&
2403              ( aCurSel.Max().GetIndex() >= pLastNode->Len() ) )
2404         {
2405             return EESpellState::Ok;
2406         }
2407 
2408         aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
2409         aWord = GetSelected( aCurSel );
2410         if ( !aWord.isEmpty() )
2411         {
2412             LanguageType eLang = GetLanguage( aCurSel.Max() );
2413             SvxSpellWrapper::CheckSpellLang( xSpeller, eLang );
2414             xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq );
2415         }
2416         aCurSel = WordRight( aCurSel.Max(), css::i18n::WordType::DICTIONARY_WORD );
2417     }
2418 
2419     return EESpellState::ErrorFound;
2420 }
2421 
ClearSpellErrors()2422 void ImpEditEngine::ClearSpellErrors()
2423 {
2424     aEditDoc.ClearSpellErrors();
2425 }
2426 
StartThesaurus(EditView * pEditView,weld::Widget * pDialogParent)2427 EESpellState ImpEditEngine::StartThesaurus(EditView* pEditView, weld::Widget* pDialogParent)
2428 {
2429     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
2430     if ( !aCurSel.HasRange() )
2431         aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
2432     OUString aWord( GetSelected( aCurSel ) );
2433 
2434     Reference< XThesaurus > xThes( LinguMgr::GetThesaurus() );
2435     if (!xThes.is())
2436         return EESpellState::ErrorFound;
2437 
2438     EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create();
2439     ScopedVclPtr<AbstractThesaurusDialog> xDlg(pFact->CreateThesaurusDialog(pDialogParent, xThes,
2440                                                aWord, GetLanguage( aCurSel.Max() ) ));
2441     if (xDlg->Execute() == RET_OK)
2442     {
2443         // Replace Word...
2444         pEditView->pImpEditView->DrawSelectionXOR();
2445         pEditView->pImpEditView->SetEditSelection( aCurSel );
2446         pEditView->pImpEditView->DrawSelectionXOR();
2447         pEditView->InsertText(xDlg->GetWord());
2448         pEditView->ShowCursor(true, false);
2449     }
2450 
2451     return EESpellState::Ok;
2452 }
2453 
StartSearchAndReplace(EditView * pEditView,const SvxSearchItem & rSearchItem)2454 sal_Int32 ImpEditEngine::StartSearchAndReplace( EditView* pEditView, const SvxSearchItem& rSearchItem )
2455 {
2456     sal_Int32 nFound = 0;
2457 
2458     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
2459 
2460     // FIND_ALL is not possible without multiple selection.
2461     if ( ( rSearchItem.GetCommand() == SvxSearchCmd::FIND ) ||
2462          ( rSearchItem.GetCommand() == SvxSearchCmd::FIND_ALL ) )
2463     {
2464         if ( Search( rSearchItem, pEditView ) )
2465             nFound++;
2466     }
2467     else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE )
2468     {
2469         // The word is selected if the user not altered the selection
2470         // in between:
2471         if ( aCurSel.HasRange() )
2472         {
2473             pEditView->InsertText( rSearchItem.GetReplaceString() );
2474             nFound = 1;
2475         }
2476         else
2477             if( Search( rSearchItem, pEditView ) )
2478                 nFound = 1;
2479     }
2480     else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL )
2481     {
2482         // The Writer replaces all front beginning to end ...
2483         SvxSearchItem aTmpItem( rSearchItem );
2484         aTmpItem.SetBackward( false );
2485 
2486         pEditView->pImpEditView->DrawSelectionXOR();
2487 
2488         aCurSel.Adjust( aEditDoc );
2489         EditPaM aStartPaM = aTmpItem.GetSelection() ? aCurSel.Min() : aEditDoc.GetStartPaM();
2490         EditSelection aFoundSel( aCurSel.Max() );
2491         bool bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel );
2492         if ( bFound )
2493             UndoActionStart( EDITUNDO_REPLACEALL );
2494         while ( bFound )
2495         {
2496             nFound++;
2497             aStartPaM = ImpInsertText( aFoundSel, rSearchItem.GetReplaceString() );
2498             bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel );
2499         }
2500         if ( nFound )
2501         {
2502             EditPaM aNewPaM( aFoundSel.Max() );
2503             if ( aNewPaM.GetIndex() > aNewPaM.GetNode()->Len() )
2504                 aNewPaM.SetIndex( aNewPaM.GetNode()->Len() );
2505             pEditView->pImpEditView->SetEditSelection( aNewPaM );
2506             FormatAndUpdate( pEditView );
2507             UndoActionEnd();
2508         }
2509         else
2510         {
2511             pEditView->pImpEditView->DrawSelectionXOR();
2512             pEditView->ShowCursor( true, false );
2513         }
2514     }
2515     return nFound;
2516 }
2517 
Search(const SvxSearchItem & rSearchItem,EditView * pEditView)2518 bool ImpEditEngine::Search( const SvxSearchItem& rSearchItem, EditView* pEditView )
2519 {
2520     EditSelection aSel( pEditView->pImpEditView->GetEditSelection() );
2521     aSel.Adjust( aEditDoc );
2522     EditPaM aStartPaM( aSel.Max() );
2523     if ( rSearchItem.GetSelection() && !rSearchItem.GetBackward() )
2524         aStartPaM = aSel.Min();
2525 
2526     EditSelection aFoundSel;
2527     bool bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel );
2528     if ( bFound && ( aFoundSel == aSel ) )  // For backwards-search
2529     {
2530         aStartPaM = aSel.Min();
2531         bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel );
2532     }
2533 
2534     pEditView->pImpEditView->DrawSelectionXOR();
2535     if ( bFound )
2536     {
2537         // First, set the minimum, so the whole word is in the visible range.
2538         pEditView->pImpEditView->SetEditSelection( aFoundSel.Min() );
2539         pEditView->ShowCursor( true, false );
2540         pEditView->pImpEditView->SetEditSelection( aFoundSel );
2541     }
2542     else
2543         pEditView->pImpEditView->SetEditSelection( aSel.Max() );
2544 
2545     pEditView->pImpEditView->DrawSelectionXOR();
2546     pEditView->ShowCursor( true, false );
2547     return bFound;
2548 }
2549 
ImpSearch(const SvxSearchItem & rSearchItem,const EditSelection & rSearchSelection,const EditPaM & rStartPos,EditSelection & rFoundSel)2550 bool ImpEditEngine::ImpSearch( const SvxSearchItem& rSearchItem,
2551     const EditSelection& rSearchSelection, const EditPaM& rStartPos, EditSelection& rFoundSel )
2552 {
2553     i18nutil::SearchOptions2 aSearchOptions( rSearchItem.GetSearchOptions() );
2554     aSearchOptions.Locale = GetLocale( rStartPos );
2555 
2556     bool bBack = rSearchItem.GetBackward();
2557     bool bSearchInSelection = rSearchItem.GetSelection();
2558     sal_Int32 nStartNode = aEditDoc.GetPos( rStartPos.GetNode() );
2559     sal_Int32 nEndNode;
2560     if ( bSearchInSelection )
2561     {
2562         nEndNode = aEditDoc.GetPos( bBack ? rSearchSelection.Min().GetNode() : rSearchSelection.Max().GetNode() );
2563     }
2564     else
2565     {
2566         nEndNode = bBack ? 0 : aEditDoc.Count()-1;
2567     }
2568 
2569     utl::TextSearch aSearcher( aSearchOptions );
2570 
2571     // iterate over the paragraphs ...
2572     for ( sal_Int32 nNode = nStartNode;
2573             bBack ? ( nNode >= nEndNode ) : ( nNode <= nEndNode) ;
2574             bBack ? nNode-- : nNode++ )
2575     {
2576         // For backwards-search if nEndNode = 0:
2577         if ( nNode < 0 )
2578             return false;
2579 
2580         ContentNode* pNode = aEditDoc.GetObject( nNode );
2581 
2582         sal_Int32 nStartPos = 0;
2583         sal_Int32 nEndPos = pNode->GetExpandedLen();
2584         if ( nNode == nStartNode )
2585         {
2586             if ( bBack )
2587                 nEndPos = rStartPos.GetIndex();
2588             else
2589                 nStartPos = rStartPos.GetIndex();
2590         }
2591         if ( ( nNode == nEndNode ) && bSearchInSelection )
2592         {
2593             if ( bBack )
2594                 nStartPos = rSearchSelection.Min().GetIndex();
2595             else
2596                 nEndPos = rSearchSelection.Max().GetIndex();
2597         }
2598 
2599         // Searching ...
2600         OUString aParaStr( pNode->GetExpandedText() );
2601         bool bFound = false;
2602         if ( bBack )
2603         {
2604             sal_Int32 nTemp;
2605             nTemp = nStartPos;
2606             nStartPos = nEndPos;
2607             nEndPos = nTemp;
2608 
2609             bFound = aSearcher.SearchBackward( aParaStr, &nStartPos, &nEndPos);
2610         }
2611         else
2612         {
2613             bFound = aSearcher.SearchForward( aParaStr, &nStartPos, &nEndPos);
2614         }
2615         if ( bFound )
2616         {
2617             pNode->UnExpandPositions( nStartPos, nEndPos );
2618 
2619             rFoundSel.Min().SetNode( pNode );
2620             rFoundSel.Min().SetIndex( nStartPos );
2621             rFoundSel.Max().SetNode( pNode );
2622             rFoundSel.Max().SetIndex( nEndPos );
2623             return true;
2624         }
2625     }
2626     return false;
2627 }
2628 
HasText(const SvxSearchItem & rSearchItem)2629 bool ImpEditEngine::HasText( const SvxSearchItem& rSearchItem )
2630 {
2631     SvxSearchItem aTmpItem( rSearchItem );
2632     aTmpItem.SetBackward( false );
2633     aTmpItem.SetSelection( false );
2634 
2635     EditPaM aStartPaM( aEditDoc.GetStartPaM() );
2636     EditSelection aDummySel( aStartPaM );
2637     EditSelection aFoundSel;
2638     return ImpSearch( aTmpItem, aDummySel, aStartPaM, aFoundSel );
2639 }
2640 
SetAutoCompleteText(const OUString & rStr,bool bClearTipWindow)2641 void ImpEditEngine::SetAutoCompleteText(const OUString& rStr, bool bClearTipWindow)
2642 {
2643     aAutoCompleteText = rStr;
2644     if ( bClearTipWindow && pActiveView )
2645         Help::ShowQuickHelp( pActiveView->GetWindow(), tools::Rectangle(), OUString() );
2646 }
2647 
2648 namespace
2649 {
2650     struct eeTransliterationChgData
2651     {
2652         sal_Int32                   nStart;
2653         sal_Int32                   nLen;
2654         EditSelection               aSelection;
2655         OUString                    aNewText;
2656         uno::Sequence< sal_Int32 >  aOffsets;
2657     };
2658 }
2659 
TransliterateText(const EditSelection & rSelection,TransliterationFlags nTransliterationMode)2660 EditSelection ImpEditEngine::TransliterateText( const EditSelection& rSelection, TransliterationFlags nTransliterationMode )
2661 {
2662     uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
2663     if (!_xBI.is())
2664         return rSelection;
2665 
2666     EditSelection aSel( rSelection );
2667     aSel.Adjust( aEditDoc );
2668 
2669     if ( !aSel.HasRange() )
2670         aSel = SelectWord( aSel );
2671 
2672     // tdf#107176: if there's still no range, just return aSel
2673     if ( !aSel.HasRange() )
2674         return aSel;
2675 
2676     EditSelection aNewSel( aSel );
2677 
2678     const sal_Int32 nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() );
2679     const sal_Int32 nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() );
2680 
2681     bool bChanges = false;
2682     bool bLenChanged = false;
2683     std::unique_ptr<EditUndoTransliteration> pUndo;
2684 
2685     utl::TransliterationWrapper aTransliterationWrapper( ::comphelper::getProcessComponentContext(), nTransliterationMode );
2686     bool bConsiderLanguage = aTransliterationWrapper.needLanguageForTheMode();
2687 
2688     for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
2689     {
2690         ContentNode* pNode = aEditDoc.GetObject( nNode );
2691         const OUString& aNodeStr = pNode->GetString();
2692         const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0;
2693         const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : aNodeStr.getLength(); // can also be == nStart!
2694 
2695         sal_Int32 nCurrentStart = nStartPos;
2696         sal_Int32 nCurrentEnd = nEndPos;
2697         LanguageType nLanguage = LANGUAGE_SYSTEM;
2698 
2699         // since we don't use Hiragana/Katakana or half-width/full-width transliterations here
2700         // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will
2701         // occasionally miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES
2702         // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the
2703         // proper thing to do.
2704         const sal_Int16 nWordType = i18n::WordType::ANYWORD_IGNOREWHITESPACES;
2705 
2706         //! In order to have less trouble with changing text size, e.g. because
2707         //! of ligatures or German small sz being resolved, we need to process
2708         //! the text replacements from end to start.
2709         //! This way the offsets for the yet to be changed words will be
2710         //! left unchanged by the already replaced text.
2711         //! For this we temporarily save the changes to be done in this vector
2712         std::vector< eeTransliterationChgData >   aChanges;
2713         eeTransliterationChgData                  aChgData;
2714 
2715         if (nTransliterationMode == TransliterationFlags::TITLE_CASE)
2716         {
2717             // for 'capitalize every word' we need to iterate over each word
2718 
2719             i18n::Boundary aSttBndry;
2720             i18n::Boundary aEndBndry;
2721             aSttBndry = _xBI->getWordBoundary(
2722                         aNodeStr, nStartPos,
2723                         GetLocale( EditPaM( pNode, nStartPos + 1 ) ),
2724                         nWordType, true /*prefer forward direction*/);
2725             aEndBndry = _xBI->getWordBoundary(
2726                         aNodeStr, nEndPos,
2727                         GetLocale( EditPaM( pNode, nEndPos + 1 ) ),
2728                         nWordType, false /*prefer backward direction*/);
2729 
2730             // prevent backtracking to the previous word if selection is at word boundary
2731             if (aSttBndry.endPos <= nStartPos)
2732             {
2733                 aSttBndry = _xBI->nextWord(
2734                         aNodeStr, aSttBndry.endPos,
2735                         GetLocale( EditPaM( pNode, aSttBndry.endPos + 1 ) ),
2736                         nWordType);
2737             }
2738             // prevent advancing to the next word if selection is at word boundary
2739             if (aEndBndry.startPos >= nEndPos)
2740             {
2741                 aEndBndry = _xBI->previousWord(
2742                         aNodeStr, aEndBndry.startPos,
2743                         GetLocale( EditPaM( pNode, aEndBndry.startPos + 1 ) ),
2744                         nWordType);
2745             }
2746 
2747             i18n::Boundary aCurWordBndry( aSttBndry );
2748             while (aCurWordBndry.endPos && aCurWordBndry.startPos <= aEndBndry.startPos)
2749             {
2750                 nCurrentStart = aCurWordBndry.startPos;
2751                 nCurrentEnd   = aCurWordBndry.endPos;
2752                 sal_Int32 nLen = nCurrentEnd - nCurrentStart;
2753                 DBG_ASSERT( nLen > 0, "invalid word length of 0" );
2754 
2755                 Sequence< sal_Int32 > aOffsets;
2756                 OUString aNewText( aTransliterationWrapper.transliterate(aNodeStr,
2757                         GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ),
2758                         nCurrentStart, nLen, &aOffsets ));
2759 
2760                 if (aNodeStr != aNewText)
2761                 {
2762                     aChgData.nStart     = nCurrentStart;
2763                     aChgData.nLen       = nLen;
2764                     aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) );
2765                     aChgData.aNewText   = aNewText;
2766                     aChgData.aOffsets   = aOffsets;
2767                     aChanges.push_back( aChgData );
2768                 }
2769 #if OSL_DEBUG_LEVEL > 1
2770                 OUString aSelTxt ( GetSelected( aChgData.aSelection ) );
2771                 (void) aSelTxt;
2772 #endif
2773 
2774                 aCurWordBndry = _xBI->nextWord(aNodeStr, nCurrentStart,
2775                         GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ),
2776                         nWordType);
2777             }
2778             DBG_ASSERT( nCurrentEnd >= aEndBndry.endPos, "failed to reach end of transliteration" );
2779         }
2780         else if (nTransliterationMode == TransliterationFlags::SENTENCE_CASE)
2781         {
2782             // for 'sentence case' we need to iterate sentence by sentence
2783 
2784             sal_Int32 nLastStart = _xBI->beginOfSentence(
2785                     aNodeStr, nEndPos,
2786                     GetLocale( EditPaM( pNode, nEndPos + 1 ) ) );
2787             sal_Int32 nLastEnd = _xBI->endOfSentence(
2788                     aNodeStr, nLastStart,
2789                     GetLocale( EditPaM( pNode, nLastStart + 1 ) ) );
2790 
2791             // extend nCurrentStart, nCurrentEnd to the current sentence boundaries
2792             nCurrentStart = _xBI->beginOfSentence(
2793                     aNodeStr, nStartPos,
2794                     GetLocale( EditPaM( pNode, nStartPos + 1 ) ) );
2795             nCurrentEnd = _xBI->endOfSentence(
2796                     aNodeStr, nCurrentStart,
2797                     GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) );
2798 
2799             // prevent backtracking to the previous sentence if selection starts at end of a sentence
2800             if (nCurrentEnd <= nStartPos)
2801             {
2802                 // now nCurrentStart is probably located on a non-letter word. (unless we
2803                 // are in Asian text with no spaces...)
2804                 // Thus to get the real sentence start we should locate the next real word,
2805                 // that is one found by DICTIONARY_WORD
2806                 i18n::Boundary aBndry = _xBI->nextWord( aNodeStr, nCurrentEnd,
2807                         GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ),
2808                         i18n::WordType::DICTIONARY_WORD);
2809 
2810                 // now get new current sentence boundaries
2811                 nCurrentStart = _xBI->beginOfSentence(
2812                         aNodeStr, aBndry.startPos,
2813                         GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) );
2814                 nCurrentEnd = _xBI->endOfSentence(
2815                         aNodeStr, nCurrentStart,
2816                         GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) );
2817             }
2818             // prevent advancing to the next sentence if selection ends at start of a sentence
2819             if (nLastStart >= nEndPos)
2820             {
2821                 // now nCurrentStart is probably located on a non-letter word. (unless we
2822                 // are in Asian text with no spaces...)
2823                 // Thus to get the real sentence start we should locate the previous real word,
2824                 // that is one found by DICTIONARY_WORD
2825                 i18n::Boundary aBndry = _xBI->previousWord( aNodeStr, nLastStart,
2826                         GetLocale( EditPaM( pNode, nLastStart + 1 ) ),
2827                         i18n::WordType::DICTIONARY_WORD);
2828                 nLastEnd = _xBI->endOfSentence(
2829                         aNodeStr, aBndry.startPos,
2830                         GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) );
2831                 if (nCurrentEnd > nLastEnd)
2832                     nCurrentEnd = nLastEnd;
2833             }
2834 
2835             while (nCurrentStart < nLastEnd)
2836             {
2837                 const sal_Int32 nLen = nCurrentEnd - nCurrentStart;
2838                 DBG_ASSERT( nLen > 0, "invalid word length of 0" );
2839 
2840                 Sequence< sal_Int32 > aOffsets;
2841                 OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr,
2842                         GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ),
2843                         nCurrentStart, nLen, &aOffsets ));
2844 
2845                 if (aNodeStr != aNewText)
2846                 {
2847                     aChgData.nStart     = nCurrentStart;
2848                     aChgData.nLen       = nLen;
2849                     aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) );
2850                     aChgData.aNewText   = aNewText;
2851                     aChgData.aOffsets   = aOffsets;
2852                     aChanges.push_back( aChgData );
2853                 }
2854 
2855                 i18n::Boundary aFirstWordBndry = _xBI->nextWord(
2856                         aNodeStr, nCurrentEnd,
2857                         GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ),
2858                         nWordType);
2859                 nCurrentStart = aFirstWordBndry.startPos;
2860                 nCurrentEnd = _xBI->endOfSentence(
2861                         aNodeStr, nCurrentStart,
2862                         GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) );
2863             }
2864             DBG_ASSERT( nCurrentEnd >= nLastEnd, "failed to reach end of transliteration" );
2865         }
2866         else
2867         {
2868             do
2869             {
2870                 if ( bConsiderLanguage )
2871                 {
2872                     nLanguage = GetLanguage( EditPaM( pNode, nCurrentStart+1 ), &nCurrentEnd );
2873                     if ( nCurrentEnd > nEndPos )
2874                         nCurrentEnd = nEndPos;
2875                 }
2876 
2877                 const sal_Int32 nLen = nCurrentEnd - nCurrentStart;
2878 
2879                 Sequence< sal_Int32 > aOffsets;
2880                 OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr, nLanguage, nCurrentStart, nLen, &aOffsets ) );
2881 
2882                 if (aNodeStr != aNewText)
2883                 {
2884                     aChgData.nStart     = nCurrentStart;
2885                     aChgData.nLen       = nLen;
2886                     aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) );
2887                     aChgData.aNewText   = aNewText;
2888                     aChgData.aOffsets   = aOffsets;
2889                     aChanges.push_back( aChgData );
2890                 }
2891 
2892                 nCurrentStart = nCurrentEnd;
2893             } while( nCurrentEnd < nEndPos );
2894         }
2895 
2896         if (!aChanges.empty())
2897         {
2898             // Create a single UndoAction on Demand for all the changes ...
2899             if ( !pUndo && IsUndoEnabled() && !IsInUndo() )
2900             {
2901                 // adjust selection to include all changes
2902                 for (const eeTransliterationChgData & aChange : aChanges)
2903                 {
2904                     const EditSelection &rSel = aChange.aSelection;
2905                     if (aSel.Min().GetNode() == rSel.Min().GetNode() &&
2906                         aSel.Min().GetIndex() > rSel.Min().GetIndex())
2907                         aSel.Min().SetIndex( rSel.Min().GetIndex() );
2908                     if (aSel.Max().GetNode() == rSel.Max().GetNode() &&
2909                         aSel.Max().GetIndex() < rSel.Max().GetIndex())
2910                         aSel.Max().SetIndex( rSel.Max().GetIndex() );
2911                 }
2912                 aNewSel = aSel;
2913 
2914                 ESelection aESel( CreateESel( aSel ) );
2915                 pUndo.reset(new EditUndoTransliteration(pEditEngine, aESel, nTransliterationMode));
2916 
2917                 const bool bSingleNode = aSel.Min().GetNode()== aSel.Max().GetNode();
2918                 const bool bHasAttribs = aSel.Min().GetNode()->GetCharAttribs().HasAttrib( aSel.Min().GetIndex(), aSel.Max().GetIndex() );
2919                 if (bSingleNode && !bHasAttribs)
2920                     pUndo->SetText( aSel.Min().GetNode()->Copy( aSel.Min().GetIndex(), aSel.Max().GetIndex()-aSel.Min().GetIndex() ) );
2921                 else
2922                     pUndo->SetText( CreateTextObject( aSel, nullptr ) );
2923             }
2924 
2925             // now apply the changes from end to start to leave the offsets of the
2926             // yet unchanged text parts remain the same.
2927             for (size_t i = 0; i < aChanges.size(); ++i)
2928             {
2929                 eeTransliterationChgData& rData = aChanges[ aChanges.size() - 1 - i ];
2930 
2931                 bChanges = true;
2932                 if (rData.nLen != rData.aNewText.getLength())
2933                     bLenChanged = true;
2934 
2935                 // Change text without losing the attributes
2936                 const sal_Int32 nDiffs =
2937                     ReplaceTextOnly( rData.aSelection.Min().GetNode(),
2938                         rData.nStart, rData.aNewText, rData.aOffsets );
2939 
2940                 // adjust selection in end node to possibly changed size
2941                 if (aSel.Max().GetNode() == rData.aSelection.Max().GetNode())
2942                     aNewSel.Max().SetIndex( aNewSel.Max().GetIndex() + nDiffs );
2943 
2944                 sal_Int32 nSelNode = aEditDoc.GetPos( rData.aSelection.Min().GetNode() );
2945                 ParaPortion& rParaPortion = GetParaPortions()[nSelNode];
2946                 rParaPortion.MarkSelectionInvalid( rData.nStart );
2947             }
2948         }
2949     }
2950 
2951     if ( pUndo )
2952     {
2953         ESelection aESel( CreateESel( aNewSel ) );
2954         pUndo->SetNewSelection( aESel );
2955         InsertUndo( std::move(pUndo) );
2956     }
2957 
2958     if ( bChanges )
2959     {
2960         TextModified();
2961         SetModifyFlag( true );
2962         if ( bLenChanged )
2963             UpdateSelections();
2964         FormatAndUpdate();
2965     }
2966 
2967     return aNewSel;
2968 }
2969 
2970 
ReplaceTextOnly(ContentNode * pNode,sal_Int32 nCurrentStart,std::u16string_view rNewText,const uno::Sequence<sal_Int32> & rOffsets)2971 short ImpEditEngine::ReplaceTextOnly(
2972     ContentNode* pNode,
2973     sal_Int32 nCurrentStart,
2974     std::u16string_view rNewText,
2975     const uno::Sequence< sal_Int32 >& rOffsets )
2976 {
2977     // Change text without losing the attributes
2978     sal_Int32 nCharsAfterTransliteration = rOffsets.getLength();
2979     const sal_Int32* pOffsets = rOffsets.getConstArray();
2980     short nDiffs = 0;
2981     for ( sal_Int32 n = 0; n < nCharsAfterTransliteration; n++ )
2982     {
2983         sal_Int32 nCurrentPos = nCurrentStart+n;
2984         sal_Int32 nDiff = (nCurrentPos-nDiffs) - pOffsets[n];
2985 
2986         if ( !nDiff )
2987         {
2988             DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" );
2989             pNode->SetChar( nCurrentPos, rNewText[n] );
2990         }
2991         else if ( nDiff < 0 )
2992         {
2993             // Replace first char, delete the rest...
2994             DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" );
2995             pNode->SetChar( nCurrentPos, rNewText[n] );
2996 
2997             DBG_ASSERT( (nCurrentPos+1) < pNode->Len(), "TransliterateText - String smaller than expected!" );
2998             GetEditDoc().RemoveChars( EditPaM( pNode, nCurrentPos+1 ), -nDiff);
2999         }
3000         else
3001         {
3002             DBG_ASSERT( nDiff == 1, "TransliterateText - Diff other than expected! But should work..." );
3003             GetEditDoc().InsertText( EditPaM( pNode, nCurrentPos ), OUString(rNewText[n]) );
3004 
3005         }
3006         nDiffs = sal::static_int_cast< short >(nDiffs + nDiff);
3007     }
3008 
3009     return nDiffs;
3010 }
3011 
3012 
SetAsianCompressionMode(CharCompressType n)3013 void ImpEditEngine::SetAsianCompressionMode( CharCompressType n )
3014 {
3015     if ( n != nAsianCompressionMode )
3016     {
3017         nAsianCompressionMode = n;
3018         if ( ImplHasText() )
3019         {
3020             FormatFullDoc();
3021             UpdateViews();
3022         }
3023     }
3024 }
3025 
SetKernAsianPunctuation(bool b)3026 void ImpEditEngine::SetKernAsianPunctuation( bool b )
3027 {
3028     if ( b != bKernAsianPunctuation )
3029     {
3030         bKernAsianPunctuation = b;
3031         if ( ImplHasText() )
3032         {
3033             FormatFullDoc();
3034             UpdateViews();
3035         }
3036     }
3037 }
3038 
SetAddExtLeading(bool bExtLeading)3039 void ImpEditEngine::SetAddExtLeading( bool bExtLeading )
3040 {
3041     if ( IsAddExtLeading() != bExtLeading )
3042     {
3043         bAddExtLeading = bExtLeading;
3044         if ( ImplHasText() )
3045         {
3046             FormatFullDoc();
3047             UpdateViews();
3048         }
3049     }
3050 };
3051 
3052 
ImplHasText() const3053 bool ImpEditEngine::ImplHasText() const
3054 {
3055     return ( ( GetEditDoc().Count() > 1 ) || GetEditDoc().GetObject(0)->Len() );
3056 }
3057 
LogicToTwips(sal_Int32 n)3058 sal_Int32 ImpEditEngine::LogicToTwips(sal_Int32 n)
3059 {
3060     Size aSz(n, 0);
3061     MapMode aTwipsMode( MapUnit::MapTwip );
3062     aSz = pRefDev->LogicToLogic( aSz, nullptr, &aTwipsMode );
3063     return aSz.Width();
3064 }
3065 
3066 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
3067