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