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