1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20 #include <vcl/svapp.hxx> 21 #include <vcl/window.hxx> 22 #include <editeng/lspcitem.hxx> 23 #include <editeng/flditem.hxx> 24 #include "impedit.hxx" 25 #include <editeng/editeng.hxx> 26 #include <editeng/editview.hxx> 27 #include <eerdll2.hxx> 28 #include <editeng/eerdll.hxx> 29 #include <edtspell.hxx> 30 #include "eeobj.hxx" 31 #include <editeng/txtrange.hxx> 32 #include <sfx2/app.hxx> 33 #include <svtools/colorcfg.hxx> 34 #include <svl/ctloptions.hxx> 35 #include <unotools/securityoptions.hxx> 36 #include <editeng/acorrcfg.hxx> 37 #include <editeng/lrspitem.hxx> 38 #include <editeng/ulspitem.hxx> 39 #include <editeng/adjustitem.hxx> 40 #include <editeng/frmdiritem.hxx> 41 #include <editeng/justifyitem.hxx> 42 43 #include <com/sun/star/i18n/CharacterIteratorMode.hpp> 44 #include <com/sun/star/i18n/WordType.hpp> 45 #include <com/sun/star/i18n/ScriptType.hpp> 46 #include <com/sun/star/lang/Locale.hpp> 47 #include <com/sun/star/i18n/InputSequenceCheckMode.hpp> 48 #include <com/sun/star/system/SystemShellExecute.hpp> 49 #include <com/sun/star/system/SystemShellExecuteFlags.hpp> 50 #include <com/sun/star/system/XSystemShellExecute.hpp> 51 #include <com/sun/star/i18n/UnicodeType.hpp> 52 53 #include <rtl/character.hxx> 54 55 #include <sal/log.hxx> 56 #include <o3tl/safeint.hxx> 57 #include <osl/diagnose.h> 58 #include <sot/exchange.hxx> 59 #include <sot/formats.hxx> 60 #include <svl/asiancfg.hxx> 61 #include <i18nutil/unicode.hxx> 62 #include <tools/diagnose_ex.h> 63 #include <comphelper/flagguard.hxx> 64 #include <comphelper/lok.hxx> 65 #include <comphelper/processfactory.hxx> 66 #include <unotools/configmgr.hxx> 67 68 #include <unicode/ubidi.h> 69 #include <algorithm> 70 #include <limits> 71 #include <memory> 72 #include <string_view> 73 #include <fstream> 74 75 using namespace ::com::sun::star; 76 77 static sal_uInt16 lcl_CalcExtraSpace( const SvxLineSpacingItem& rLSItem ) 78 { 79 sal_uInt16 nExtra = 0; 80 if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) 81 { 82 nExtra = rLSItem.GetInterLineSpace(); 83 } 84 85 return nExtra; 86 } 87 88 ImpEditEngine::ImpEditEngine( EditEngine* pEE, SfxItemPool* pItemPool ) : 89 pSharedVCL(EditDLL::Get().GetSharedVclResources()), 90 aPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), 91 aMinAutoPaperSize( 0x0, 0x0 ), 92 aMaxAutoPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), 93 aEditDoc( pItemPool ), 94 pEditEngine(pEE), 95 pActiveView(nullptr), 96 pStylePool(nullptr), 97 pTextObjectPool(nullptr), 98 pUndoManager(nullptr), 99 aWordDelimiters(" .,;:-`'?!_=\"{}()[]"), 100 maBackgroundColor(COL_AUTO), 101 nStretchX(100), 102 nStretchY(100), 103 nAsianCompressionMode(CharCompressType::NONE), 104 eDefaultHorizontalTextDirection(EEHorizontalTextDirection::Default), 105 nBigTextObjectStart(20), 106 eDefLanguage(LANGUAGE_DONTKNOW), 107 nCurTextHeight(0), 108 nCurTextHeightNTP(0), 109 aOnlineSpellTimer( "editeng::ImpEditEngine aOnlineSpellTimer" ), 110 aStatusTimer( "editeng::ImpEditEngine aStatusTimer" ), 111 bKernAsianPunctuation(false), 112 bAddExtLeading(false), 113 bIsFormatting(false), 114 bFormatted(false), 115 bInSelection(false), 116 bIsInUndo(false), 117 bUpdateLayout(true), 118 bUndoEnabled(true), 119 bDowning(false), 120 bUseAutoColor(true), 121 bForceAutoColor(false), 122 bCallParaInsertedOrDeleted(false), 123 bFirstWordCapitalization(true), 124 mbLastTryMerge(false), 125 mbReplaceLeadingSingleQuotationMark(true), 126 mbNbspRunNext(false) 127 { 128 aStatus.GetControlWord() = EEControlBits::USECHARATTRIBS | EEControlBits::DOIDLEFORMAT | 129 EEControlBits::PASTESPECIAL | EEControlBits::UNDOATTRIBS | 130 EEControlBits::ALLOWBIGOBJS | EEControlBits::RTFSTYLESHEETS | 131 EEControlBits::FORMAT100; 132 133 aSelEngine.SetFunctionSet( &aSelFuncSet ); 134 135 aStatusTimer.SetTimeout( 200 ); 136 aStatusTimer.SetInvokeHandler( LINK( this, ImpEditEngine, StatusTimerHdl ) ); 137 138 aIdleFormatter.SetPriority( TaskPriority::REPAINT ); 139 aIdleFormatter.SetInvokeHandler( LINK( this, ImpEditEngine, IdleFormatHdl ) ); 140 141 aOnlineSpellTimer.SetTimeout( 100 ); 142 aOnlineSpellTimer.SetInvokeHandler( LINK( this, ImpEditEngine, OnlineSpellHdl ) ); 143 144 // Access data already from here on! 145 SetRefDevice( nullptr ); 146 InitDoc( false ); 147 148 bCallParaInsertedOrDeleted = true; 149 150 aEditDoc.SetModifyHdl( LINK( this, ImpEditEngine, DocModified ) ); 151 StartListening(*SfxGetpApp()); 152 } 153 154 void ImpEditEngine::Dispose() 155 { 156 SolarMutexGuard g; 157 auto pApp = SfxApplication::Get(); 158 if(pApp) 159 EndListening(*pApp); 160 pVirtDev.disposeAndClear(); 161 mpOwnDev.disposeAndClear(); 162 pSharedVCL.reset(); 163 } 164 165 ImpEditEngine::~ImpEditEngine() 166 { 167 aStatusTimer.Stop(); 168 aOnlineSpellTimer.Stop(); 169 aIdleFormatter.Stop(); 170 171 // Destroying templates may otherwise cause unnecessary formatting, 172 // when a parent template is destroyed. 173 // And this after the destruction of the data! 174 bDowning = true; 175 SetUpdateLayout( false ); 176 177 Dispose(); 178 // it's only legal to delete the pUndoManager if it was created by 179 // ImpEditEngine; if it was set by SetUndoManager() it must be cleared 180 // before destroying the ImpEditEngine! 181 assert(!pUndoManager || typeid(*pUndoManager) == typeid(EditUndoManager)); 182 delete pUndoManager; 183 pTextRanger.reset(); 184 mpIMEInfos.reset(); 185 pCTLOptions.reset(); 186 pSpellInfo.reset(); 187 } 188 189 void ImpEditEngine::SetRefDevice( OutputDevice* pRef ) 190 { 191 if (pRef) 192 pRefDev = pRef; 193 else 194 pRefDev = pSharedVCL->GetVirtualDevice(); 195 196 nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width()); 197 198 if ( IsFormatted() ) 199 { 200 FormatFullDoc(); 201 UpdateViews(); 202 } 203 } 204 205 void ImpEditEngine::SetRefMapMode( const MapMode& rMapMode ) 206 { 207 if ( GetRefDevice()->GetMapMode() == rMapMode ) 208 return; 209 210 mpOwnDev.disposeAndClear(); 211 mpOwnDev = VclPtr<VirtualDevice>::Create(); 212 pRefDev = mpOwnDev; 213 pRefDev->SetMapMode(MapMode(MapUnit::MapTwip)); 214 SetRefDevice( pRefDev ); 215 216 pRefDev->SetMapMode( rMapMode ); 217 nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width()); 218 if ( IsFormatted() ) 219 { 220 FormatFullDoc(); 221 UpdateViews(); 222 } 223 } 224 225 void ImpEditEngine::InitDoc(bool bKeepParaAttribs) 226 { 227 sal_Int32 nParas = aEditDoc.Count(); 228 for ( sal_Int32 n = bKeepParaAttribs ? 1 : 0; n < nParas; n++ ) 229 { 230 if ( aEditDoc[n]->GetStyleSheet() ) 231 EndListening( *aEditDoc[n]->GetStyleSheet() ); 232 } 233 234 if ( bKeepParaAttribs ) 235 aEditDoc.RemoveText(); 236 else 237 aEditDoc.Clear(); 238 239 GetParaPortions().Reset(); 240 241 GetParaPortions().Insert(0, ParaPortion( aEditDoc[0] )); 242 243 bFormatted = false; 244 245 if ( IsCallParaInsertedOrDeleted() ) 246 { 247 GetEditEnginePtr()->ParagraphDeleted( EE_PARA_ALL ); 248 GetEditEnginePtr()->ParagraphInserted( 0 ); 249 } 250 251 if ( GetStatus().DoOnlineSpelling() ) 252 aEditDoc.GetObject( 0 )->CreateWrongList(); 253 } 254 255 EditPaM ImpEditEngine::DeleteSelected(const EditSelection& rSel) 256 { 257 EditPaM aPaM (ImpDeleteSelection(rSel)); 258 return aPaM; 259 } 260 261 OUString ImpEditEngine::GetSelected( const EditSelection& rSel ) const 262 { 263 if ( !rSel.HasRange() ) 264 return OUString(); 265 266 EditSelection aSel( rSel ); 267 aSel.Adjust( aEditDoc ); 268 269 ContentNode* pStartNode = aSel.Min().GetNode(); 270 ContentNode* pEndNode = aSel.Max().GetNode(); 271 sal_Int32 nStartNode = aEditDoc.GetPos( pStartNode ); 272 sal_Int32 nEndNode = aEditDoc.GetPos( pEndNode ); 273 274 OSL_ENSURE( nStartNode <= nEndNode, "Selection not sorted ?" ); 275 276 OUStringBuffer aText(256); 277 const OUString aSep = EditDoc::GetSepStr( LINEEND_LF ); 278 279 // iterate over the paragraphs ... 280 for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) 281 { 282 OSL_ENSURE( aEditDoc.GetObject( nNode ), "Node not found: GetSelected" ); 283 const ContentNode* pNode = aEditDoc.GetObject( nNode ); 284 285 const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; 286 const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart! 287 288 aText.append(EditDoc::GetParaAsString( pNode, nStartPos, nEndPos )); 289 if ( nNode < nEndNode ) 290 aText.append(aSep); 291 } 292 return aText.makeStringAndClear(); 293 } 294 295 bool ImpEditEngine::MouseButtonDown( const MouseEvent& rMEvt, EditView* pView ) 296 { 297 GetSelEngine().SetCurView( pView ); 298 SetActiveView( pView ); 299 300 if (!GetAutoCompleteText().isEmpty()) 301 SetAutoCompleteText( OUString(), true ); 302 303 GetSelEngine().SelMouseButtonDown( rMEvt ); 304 // Special treatment 305 EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); 306 if ( rMEvt.IsShift() ) 307 return true; 308 309 if ( rMEvt.GetClicks() == 2 ) 310 { 311 // So that the SelectionEngine knows about the anchor. 312 aSelEngine.CursorPosChanging( true, false ); 313 314 EditSelection aNewSelection( SelectWord( aCurSel ) ); 315 pView->pImpEditView->DrawSelectionXOR(); 316 pView->pImpEditView->SetEditSelection( aNewSelection ); 317 pView->pImpEditView->DrawSelectionXOR(); 318 pView->ShowCursor(); 319 } 320 else if ( rMEvt.GetClicks() == 3 ) 321 { 322 // So that the SelectionEngine knows about the anchor. 323 aSelEngine.CursorPosChanging( true, false ); 324 325 EditSelection aNewSelection( aCurSel ); 326 aNewSelection.Min().SetIndex( 0 ); 327 aNewSelection.Max().SetIndex( aCurSel.Min().GetNode()->Len() ); 328 pView->pImpEditView->DrawSelectionXOR(); 329 pView->pImpEditView->SetEditSelection( aNewSelection ); 330 pView->pImpEditView->DrawSelectionXOR(); 331 pView->ShowCursor(); 332 } 333 return true; 334 } 335 336 bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) 337 { 338 bool bConsumed = true; 339 340 GetSelEngine().SetCurView( pView ); 341 SetActiveView( pView ); 342 if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput ) 343 { 344 pView->DeleteSelected(); 345 mpIMEInfos.reset(); 346 EditPaM aPaM = pView->GetImpEditView()->GetEditSelection().Max(); 347 OUString aOldTextAfterStartPos = aPaM.GetNode()->Copy( aPaM.GetIndex() ); 348 sal_Int32 nMax = aOldTextAfterStartPos.indexOf( CH_FEATURE ); 349 if ( nMax != -1 ) // don't overwrite features! 350 aOldTextAfterStartPos = aOldTextAfterStartPos.copy( 0, nMax ); 351 mpIMEInfos.reset( new ImplIMEInfos( aPaM, aOldTextAfterStartPos ) ); 352 mpIMEInfos->bWasCursorOverwrite = !pView->IsInsertMode(); 353 UndoActionStart( EDITUNDO_INSERT ); 354 } 355 else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput ) 356 { 357 OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => No start ?" ); 358 if( mpIMEInfos ) 359 { 360 // #102812# convert quotes in IME text 361 // works on the last input character, this is especially in Korean text often done 362 // quotes that are inside of the string are not replaced! 363 // Borrowed from sw: edtwin.cxx 364 if ( mpIMEInfos->nLen ) 365 { 366 EditSelection aSel( mpIMEInfos->aPos ); 367 aSel.Min().SetIndex( aSel.Min().GetIndex() + mpIMEInfos->nLen-1 ); 368 aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen ); 369 // #102812# convert quotes in IME text 370 // works on the last input character, this is especially in Korean text often done 371 // quotes that are inside of the string are not replaced! 372 const sal_Unicode nCharCode = aSel.Min().GetNode()->GetChar( aSel.Min().GetIndex() ); 373 if ( ( GetStatus().DoAutoCorrect() ) && ( ( nCharCode == '\"' ) || ( nCharCode == '\'' ) ) ) 374 { 375 aSel = DeleteSelected( aSel ); 376 aSel = AutoCorrect( aSel, nCharCode, mpIMEInfos->bWasCursorOverwrite ); 377 pView->pImpEditView->SetEditSelection( aSel ); 378 } 379 } 380 381 ParaPortion& rPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); 382 rPortion.MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); 383 384 bool bWasCursorOverwrite = mpIMEInfos->bWasCursorOverwrite; 385 386 mpIMEInfos.reset(); 387 388 FormatAndLayout( pView ); 389 390 pView->SetInsertMode( !bWasCursorOverwrite ); 391 } 392 UndoActionEnd(); 393 } 394 else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput ) 395 { 396 OSL_ENSURE( mpIMEInfos, "CommandEventId::ExtTextInput => No Start ?" ); 397 if( mpIMEInfos ) 398 { 399 const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData(); 400 401 if ( !pData->IsOnlyCursorChanged() ) 402 { 403 EditSelection aSel( mpIMEInfos->aPos ); 404 aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen ); 405 aSel = DeleteSelected( aSel ); 406 aSel = ImpInsertText( aSel, pData->GetText() ); 407 408 if ( mpIMEInfos->bWasCursorOverwrite ) 409 { 410 sal_Int32 nOldIMETextLen = mpIMEInfos->nLen; 411 sal_Int32 nNewIMETextLen = pData->GetText().getLength(); 412 413 if ( ( nOldIMETextLen > nNewIMETextLen ) && 414 ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) 415 { 416 // restore old characters 417 sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen; 418 EditPaM aPaM( mpIMEInfos->aPos ); 419 aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen ); 420 ImpInsertText( aPaM, mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) ); 421 } 422 else if ( ( nOldIMETextLen < nNewIMETextLen ) && 423 ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) 424 { 425 // overwrite 426 sal_Int32 nOverwrite = nNewIMETextLen - nOldIMETextLen; 427 if ( ( nOldIMETextLen + nOverwrite ) > mpIMEInfos->aOldTextAfterStartPos.getLength() ) 428 nOverwrite = mpIMEInfos->aOldTextAfterStartPos.getLength() - nOldIMETextLen; 429 OSL_ENSURE( nOverwrite && (nOverwrite < 0xFF00), "IME Overwrite?!" ); 430 EditPaM aPaM( mpIMEInfos->aPos ); 431 aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen ); 432 EditSelection _aSel( aPaM ); 433 _aSel.Max().SetIndex( _aSel.Max().GetIndex() + nOverwrite ); 434 DeleteSelected( _aSel ); 435 } 436 } 437 if ( pData->GetTextAttr() ) 438 { 439 mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() ); 440 } 441 else 442 { 443 mpIMEInfos->DestroyAttribs(); 444 mpIMEInfos->nLen = pData->GetText().getLength(); 445 } 446 447 ParaPortion& rPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); 448 rPortion.MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); 449 FormatAndLayout( pView ); 450 } 451 452 EditSelection aNewSel = EditPaM( mpIMEInfos->aPos.GetNode(), mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() ); 453 pView->SetSelection( CreateESel( aNewSel ) ); 454 pView->SetInsertMode( !pData->IsCursorOverwrite() ); 455 456 if ( pData->IsCursorVisible() ) 457 pView->ShowCursor(); 458 else 459 pView->HideCursor(); 460 } 461 } 462 else if ( rCEvt.GetCommand() == CommandEventId::InputContextChange ) 463 { 464 } 465 else if ( rCEvt.GetCommand() == CommandEventId::CursorPos ) 466 { 467 if (mpIMEInfos) 468 { 469 EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); 470 tools::Rectangle aR1 = PaMtoEditCursor( aPaM ); 471 472 sal_Int32 nInputEnd = mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen; 473 474 if ( !IsFormatted() ) 475 FormatDoc(); 476 477 ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) ); 478 if (pParaPortion) 479 { 480 sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true ); 481 const EditLine& rLine = pParaPortion->GetLines()[nLine]; 482 if ( nInputEnd > rLine.GetEnd() ) 483 nInputEnd = rLine.GetEnd(); 484 tools::Rectangle aR2 = PaMtoEditCursor( EditPaM( aPaM.GetNode(), nInputEnd ), GetCursorFlags::EndOfLine ); 485 tools::Rectangle aRect = pView->GetImpEditView()->GetWindowPos( aR1 ); 486 auto nExtTextInputWidth = aR2.Left() - aR1.Right(); 487 if (EditViewCallbacks* pEditViewCallbacks = pView->getEditViewCallbacks()) 488 pEditViewCallbacks->EditViewCursorRect(aRect, nExtTextInputWidth); 489 else if (vcl::Window* pWindow = pView->GetWindow()) 490 pWindow->SetCursorRect(&aRect, nExtTextInputWidth); 491 } 492 } 493 else 494 { 495 if (vcl::Window* pWindow = pView->GetWindow()) 496 pWindow->SetCursorRect(); 497 } 498 } 499 else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange ) 500 { 501 const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData(); 502 503 ESelection aSelection = pView->GetSelection(); 504 aSelection.Adjust(); 505 506 if( pView->HasSelection() ) 507 { 508 aSelection.nEndPos = aSelection.nStartPos; 509 aSelection.nStartPos += pData->GetStart(); 510 aSelection.nEndPos += pData->GetEnd(); 511 } 512 else 513 { 514 aSelection.nStartPos = pData->GetStart(); 515 aSelection.nEndPos = pData->GetEnd(); 516 } 517 pView->SetSelection( aSelection ); 518 } 519 else if ( rCEvt.GetCommand() == CommandEventId::PrepareReconversion ) 520 { 521 if ( pView->HasSelection() ) 522 { 523 ESelection aSelection = pView->GetSelection(); 524 aSelection.Adjust(); 525 526 if ( aSelection.nStartPara != aSelection.nEndPara ) 527 { 528 sal_Int32 aParaLen = pEditEngine->GetTextLen( aSelection.nStartPara ); 529 aSelection.nEndPara = aSelection.nStartPara; 530 aSelection.nEndPos = aParaLen; 531 pView->SetSelection( aSelection ); 532 } 533 } 534 } 535 else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition ) 536 { 537 if (mpIMEInfos) 538 { 539 EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); 540 if ( !IsFormatted() ) 541 FormatDoc(); 542 543 sal_Int32 nPortionPos = GetEditDoc().GetPos(aPaM.GetNode()); 544 ParaPortion* pParaPortion = GetParaPortions().SafeGetObject(nPortionPos); 545 if (pParaPortion) 546 { 547 const sal_Int32 nMinPos = mpIMEInfos->aPos.GetIndex(); 548 const sal_Int32 nMaxPos = nMinPos + mpIMEInfos->nLen - 1; 549 std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen); 550 551 auto CollectCharPositions = [&](const LineAreaInfo& rInfo) { 552 if (!rInfo.pLine) // Start of ParaPortion 553 { 554 if (rInfo.nPortion < nPortionPos) 555 return CallbackResult::SkipThisPortion; 556 if (rInfo.nPortion > nPortionPos) 557 return CallbackResult::Stop; 558 assert(&rInfo.rPortion == pParaPortion); 559 } 560 else // This is the needed ParaPortion 561 { 562 if (rInfo.pLine->GetStart() > nMaxPos) 563 return CallbackResult::Stop; 564 if (rInfo.pLine->GetEnd() < nMinPos) 565 return CallbackResult::Continue; 566 for (sal_Int32 n = nMinPos; n <= nMaxPos; ++n) 567 { 568 if (rInfo.pLine->IsIn(n)) 569 { 570 tools::Rectangle aR = GetEditCursor(pParaPortion, rInfo.pLine, n, 571 GetCursorFlags::NONE); 572 aR.Move(getTopLeftDocOffset(rInfo.aArea)); 573 aRects[n - nMinPos] = pView->GetImpEditView()->GetWindowPos(aR); 574 } 575 } 576 } 577 return CallbackResult::Continue; 578 }; 579 IterateLineAreas(CollectCharPositions, IterFlag::none); 580 581 if (vcl::Window* pWindow = pView->GetWindow()) 582 pWindow->SetCompositionCharRect(aRects.data(), aRects.size()); 583 } 584 } 585 } 586 else 587 bConsumed = false; 588 589 return GetSelEngine().Command(rCEvt) || bConsumed; 590 } 591 592 bool ImpEditEngine::MouseButtonUp( const MouseEvent& rMEvt, EditView* pView ) 593 { 594 GetSelEngine().SetCurView( pView ); 595 GetSelEngine().SelMouseButtonUp( rMEvt ); 596 597 // in the tiled rendering case, setting bInSelection here has unexpected 598 // consequences - further tiles painting removes the selection 599 // FIXME I believe resetting bInSelection should not be here even in the 600 // non-tiled-rendering case, but it has been here since 2000 (and before) 601 // so who knows what corner case it was supposed to solve back then 602 if (!comphelper::LibreOfficeKit::isActive()) 603 bInSelection = false; 604 605 // Special treatments 606 EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); 607 if ( aCurSel.HasRange() ) 608 return true; 609 610 if ( ( rMEvt.GetClicks() != 1 ) || !rMEvt.IsLeft() || rMEvt.IsMod2() ) 611 return true; 612 613 const OutputDevice& rOutDev = pView->getEditViewCallbacks() ? pView->getEditViewCallbacks()->EditViewOutputDevice() : *pView->GetWindow()->GetOutDev(); 614 Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel()); 615 const SvxFieldItem* pFld = pView->GetField(aLogicClick); 616 if (!pFld) 617 return true; 618 619 // tdf#121039 When in edit mode, editeng is responsible for opening the URL on mouse click 620 bool bUrlOpened = GetEditEnginePtr()->FieldClicked( *pFld ); 621 if (bUrlOpened) 622 return true; 623 624 if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField())) 625 { 626 bool bCtrlClickHappened = rMEvt.IsMod1(); 627 bool bCtrlClickSecOption 628 = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink); 629 if ((bCtrlClickHappened && bCtrlClickSecOption) 630 || (!bCtrlClickHappened && !bCtrlClickSecOption)) 631 { 632 css::uno::Reference<css::system::XSystemShellExecute> exec( 633 css::system::SystemShellExecute::create( 634 comphelper::getProcessComponentContext())); 635 exec->execute(pUrlField->GetURL(), OUString(), 636 css::system::SystemShellExecuteFlags::DEFAULTS); 637 } 638 } 639 return true; 640 } 641 642 void ImpEditEngine::ReleaseMouse() 643 { 644 GetSelEngine().ReleaseMouse(); 645 } 646 647 bool ImpEditEngine::MouseMove( const MouseEvent& rMEvt, EditView* pView ) 648 { 649 // MouseMove is called directly after ShowQuickHelp()! 650 GetSelEngine().SetCurView( pView ); 651 GetSelEngine().SelMouseMove( rMEvt ); 652 return true; 653 } 654 655 EditPaM ImpEditEngine::InsertText(const EditSelection& aSel, const OUString& rStr) 656 { 657 EditPaM aPaM = ImpInsertText( aSel, rStr ); 658 return aPaM; 659 } 660 661 void ImpEditEngine::Clear() 662 { 663 InitDoc( false ); 664 665 EditPaM aPaM = aEditDoc.GetStartPaM(); 666 EditSelection aSel( aPaM ); 667 668 nCurTextHeight = 0; 669 nCurTextHeightNTP = 0; 670 671 ResetUndoManager(); 672 673 for (size_t nView = aEditViews.size(); nView; ) 674 { 675 EditView* pView = aEditViews[--nView]; 676 pView->pImpEditView->SetEditSelection( aSel ); 677 } 678 } 679 680 EditPaM ImpEditEngine::RemoveText() 681 { 682 InitDoc( true ); 683 684 EditPaM aStartPaM = aEditDoc.GetStartPaM(); 685 EditSelection aEmptySel( aStartPaM, aStartPaM ); 686 for (EditView* pView : aEditViews) 687 { 688 pView->pImpEditView->SetEditSelection( aEmptySel ); 689 } 690 ResetUndoManager(); 691 return aEditDoc.GetStartPaM(); 692 } 693 694 695 void ImpEditEngine::SetText(const OUString& rText) 696 { 697 // RemoveText deletes the undo list! 698 EditPaM aStartPaM = RemoveText(); 699 bool bUndoCurrentlyEnabled = IsUndoEnabled(); 700 // The text inserted manually can not be made reversible by the user 701 EnableUndo( false ); 702 703 EditSelection aEmptySel( aStartPaM, aStartPaM ); 704 EditPaM aPaM = aStartPaM; 705 if (!rText.isEmpty()) 706 aPaM = ImpInsertText( aEmptySel, rText ); 707 708 for (EditView* pView : aEditViews) 709 { 710 pView->pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) ); 711 // If no text then also no Format&Update 712 // => The text remains. 713 if (rText.isEmpty() && IsUpdateLayout()) 714 { 715 tools::Rectangle aTmpRect( pView->GetOutputArea().TopLeft(), 716 Size( aPaperSize.Width(), nCurTextHeight ) ); 717 aTmpRect.Intersection( pView->GetOutputArea() ); 718 pView->InvalidateWindow( aTmpRect ); 719 } 720 } 721 if (rText.isEmpty()) { // otherwise it must be invalidated later, !bFormatted is enough. 722 nCurTextHeight = 0; 723 nCurTextHeightNTP = 0; 724 } 725 EnableUndo( bUndoCurrentlyEnabled ); 726 OSL_ENSURE( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "Undo after SetText?" ); 727 } 728 729 730 const SfxItemSet& ImpEditEngine::GetEmptyItemSet() const 731 { 732 if ( !pEmptyItemSet ) 733 { 734 pEmptyItemSet = std::make_unique<SfxItemSetFixed<EE_ITEMS_START, EE_ITEMS_END>>(const_cast<SfxItemPool&>(aEditDoc.GetItemPool())); 735 for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++) 736 { 737 pEmptyItemSet->ClearItem( nWhich ); 738 } 739 } 740 return *pEmptyItemSet; 741 } 742 743 744 // MISC 745 746 void ImpEditEngine::CursorMoved( const ContentNode* pPrevNode ) 747 { 748 // Delete empty attributes, but only if paragraph is not empty! 749 if (pPrevNode->GetCharAttribs().HasEmptyAttribs() && pPrevNode->Len()) 750 { 751 const_cast<ContentNode*>(pPrevNode)->GetCharAttribs().DeleteEmptyAttribs(aEditDoc.GetItemPool()); 752 } 753 } 754 755 void ImpEditEngine::TextModified() 756 { 757 bFormatted = false; 758 759 if ( GetNotifyHdl().IsSet() ) 760 { 761 EENotify aNotify( EE_NOTIFY_TEXTMODIFIED ); 762 GetNotifyHdl().Call( aNotify ); 763 } 764 } 765 766 767 void ImpEditEngine::ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreUndoCheck ) 768 { 769 assert(pNode && "ParaAttribsChanged: Which one?"); 770 771 aEditDoc.SetModified( true ); 772 bFormatted = false; 773 774 ParaPortion& rPortion = FindParaPortion( pNode ); 775 rPortion.MarkSelectionInvalid( 0 ); 776 777 sal_Int32 nPara = aEditDoc.GetPos( pNode ); 778 if ( bIgnoreUndoCheck || pEditEngine->IsInUndo() ) 779 pEditEngine->ParaAttribsChanged( nPara ); 780 781 ParaPortion* pNextPortion = GetParaPortions().SafeGetObject( nPara+1 ); 782 // => is formatted again anyway, if Invalid. 783 if ( pNextPortion && !pNextPortion->IsInvalid() ) 784 CalcHeight( pNextPortion ); 785 } 786 787 788 // Cursor movements 789 790 791 EditSelection const & ImpEditEngine::MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView ) 792 { 793 // Actually, only necessary for up/down, but whatever. 794 CheckIdleFormatter(); 795 796 EditPaM aPaM( pEditView->pImpEditView->GetEditSelection().Max() ); 797 798 EditPaM aOldPaM( aPaM ); 799 800 TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom; 801 if (IsEffectivelyVertical() && IsTopToBottom()) 802 eTextDirection = TextDirectionality::TopToBottom_RightToLeft; 803 else if (IsEffectivelyVertical() && !IsTopToBottom()) 804 eTextDirection = TextDirectionality::BottomToTop_LeftToRight; 805 else if ( IsRightToLeft( GetEditDoc().GetPos( aPaM.GetNode() ) ) ) 806 eTextDirection = TextDirectionality::RightToLeft_TopToBottom; 807 808 KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection ); 809 810 bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1(); 811 sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode(); 812 813 if ( DoVisualCursorTraveling() ) 814 { 815 // Only for simple cursor movement... 816 if ( !bCtrl && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) ) 817 { 818 aPaM = CursorVisualLeftRight( pEditView, aPaM, rKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL, rKeyEvent.GetKeyCode().GetCode() == KEY_LEFT ); 819 nCode = 0; // skip switch statement 820 } 821 } 822 823 bool bKeyModifySelection = aTranslatedKeyEvent.GetKeyCode().IsShift(); 824 switch ( nCode ) 825 { 826 case KEY_UP: aPaM = CursorUp( aPaM, pEditView ); 827 break; 828 case KEY_DOWN: aPaM = CursorDown( aPaM, pEditView ); 829 break; 830 case KEY_LEFT: aPaM = bCtrl ? WordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL ); 831 break; 832 case KEY_RIGHT: aPaM = bCtrl ? WordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL ); 833 break; 834 case KEY_HOME: aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM ); 835 break; 836 case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM ); 837 break; 838 case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM, pEditView ); 839 break; 840 case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM, pEditView ); 841 break; 842 case css::awt::Key::MOVE_TO_BEGIN_OF_LINE: 843 aPaM = CursorStartOfLine( aPaM ); 844 bKeyModifySelection = false; 845 break; 846 case css::awt::Key::MOVE_TO_END_OF_LINE: 847 aPaM = CursorEndOfLine( aPaM ); 848 bKeyModifySelection = false; 849 break; 850 case css::awt::Key::MOVE_WORD_BACKWARD: 851 aPaM = WordLeft( aPaM ); 852 bKeyModifySelection = false; 853 break; 854 case css::awt::Key::MOVE_WORD_FORWARD: 855 aPaM = WordRight( aPaM ); 856 bKeyModifySelection = false; 857 break; 858 case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH: 859 aPaM = CursorStartOfParagraph( aPaM ); 860 if( aPaM == aOldPaM ) 861 { 862 aPaM = CursorLeft( aPaM ); 863 aPaM = CursorStartOfParagraph( aPaM ); 864 } 865 bKeyModifySelection = false; 866 break; 867 case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH: 868 aPaM = CursorEndOfParagraph( aPaM ); 869 if( aPaM == aOldPaM ) 870 { 871 aPaM = CursorRight( aPaM ); 872 aPaM = CursorEndOfParagraph( aPaM ); 873 } 874 bKeyModifySelection = false; 875 break; 876 case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT: 877 aPaM = CursorStartOfDoc(); 878 bKeyModifySelection = false; 879 break; 880 case css::awt::Key::MOVE_TO_END_OF_DOCUMENT: 881 aPaM = CursorEndOfDoc(); 882 bKeyModifySelection = false; 883 break; 884 case css::awt::Key::SELECT_TO_BEGIN_OF_LINE: 885 aPaM = CursorStartOfLine( aPaM ); 886 bKeyModifySelection = true; 887 break; 888 case css::awt::Key::SELECT_TO_END_OF_LINE: 889 aPaM = CursorEndOfLine( aPaM ); 890 bKeyModifySelection = true; 891 break; 892 case css::awt::Key::SELECT_BACKWARD: 893 aPaM = CursorLeft( aPaM ); 894 bKeyModifySelection = true; 895 break; 896 case css::awt::Key::SELECT_FORWARD: 897 aPaM = CursorRight( aPaM ); 898 bKeyModifySelection = true; 899 break; 900 case css::awt::Key::SELECT_WORD_BACKWARD: 901 aPaM = WordLeft( aPaM ); 902 bKeyModifySelection = true; 903 break; 904 case css::awt::Key::SELECT_WORD_FORWARD: 905 aPaM = WordRight( aPaM ); 906 bKeyModifySelection = true; 907 break; 908 case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH: 909 aPaM = CursorStartOfParagraph( aPaM ); 910 if( aPaM == aOldPaM ) 911 { 912 aPaM = CursorLeft( aPaM ); 913 aPaM = CursorStartOfParagraph( aPaM ); 914 } 915 bKeyModifySelection = true; 916 break; 917 case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH: 918 aPaM = CursorEndOfParagraph( aPaM ); 919 if( aPaM == aOldPaM ) 920 { 921 aPaM = CursorRight( aPaM ); 922 aPaM = CursorEndOfParagraph( aPaM ); 923 } 924 bKeyModifySelection = true; 925 break; 926 case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT: 927 aPaM = CursorStartOfDoc(); 928 bKeyModifySelection = true; 929 break; 930 case css::awt::Key::SELECT_TO_END_OF_DOCUMENT: 931 aPaM = CursorEndOfDoc(); 932 bKeyModifySelection = true; 933 break; 934 } 935 936 if ( aOldPaM != aPaM ) 937 { 938 CursorMoved( aOldPaM.GetNode() ); 939 } 940 941 // May cause, a CreateAnchor or deselection all 942 aSelEngine.SetCurView( pEditView ); 943 aSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() ); 944 EditPaM aOldEnd( pEditView->pImpEditView->GetEditSelection().Max() ); 945 946 { 947 EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection()); 948 aNewSelection.Max() = aPaM; 949 pEditView->pImpEditView->SetEditSelection(aNewSelection); 950 // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Max()) = aPaM; 951 } 952 953 if ( bKeyModifySelection ) 954 { 955 // Then the selection is expanded ... or the whole selection is painted in case of tiled rendering. 956 EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? pEditView->pImpEditView->GetEditSelection().Min() : aOldEnd, aPaM ); 957 pEditView->pImpEditView->DrawSelectionXOR( aTmpNewSel ); 958 } 959 else 960 { 961 EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection()); 962 aNewSelection.Min() = aPaM; 963 pEditView->pImpEditView->SetEditSelection(aNewSelection); 964 // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Min()) = aPaM; 965 } 966 967 return pEditView->pImpEditView->GetEditSelection(); 968 } 969 970 EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * pEditView, const EditPaM& rPaM, bool bStart ) 971 { 972 EditPaM aPaM( rPaM ); 973 974 sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() ); 975 ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 976 if (!pParaPortion) 977 return aPaM; 978 979 sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false ); 980 const EditLine& rLine = pParaPortion->GetLines()[nLine]; 981 bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); 982 983 pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; 984 985 if ( !bEmptyLine ) 986 { 987 OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()); 988 989 UErrorCode nError = U_ZERO_ERROR; 990 UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError ); 991 992 const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/; 993 ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError ); 994 995 sal_Int32 nVisPos = bStart ? 0 : aLine.getLength()-1; 996 const sal_Int32 nLogPos = ubidi_getLogicalIndex( pBidi, nVisPos, &nError ); 997 998 ubidi_close( pBidi ); 999 1000 aPaM.SetIndex( nLogPos + rLine.GetStart() ); 1001 1002 sal_Int32 nTmp; 1003 sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTmp, true ); 1004 const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; 1005 bool bPortionRTL = rTextPortion.IsRightToLeft(); 1006 1007 if ( bStart ) 1008 { 1009 pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 0 : 1 ); 1010 // Maybe we must be *behind* the character 1011 if ( bPortionRTL && pEditView->IsInsertMode() ) 1012 aPaM.SetIndex( aPaM.GetIndex()+1 ); 1013 } 1014 else 1015 { 1016 pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 1 : 0 ); 1017 if ( !bPortionRTL && pEditView->IsInsertMode() ) 1018 aPaM.SetIndex( aPaM.GetIndex()+1 ); 1019 } 1020 } 1021 1022 return aPaM; 1023 } 1024 1025 EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bVisualToLeft ) 1026 { 1027 EditPaM aPaM( rPaM ); 1028 1029 sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() ); 1030 ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 1031 if (!pParaPortion) 1032 return aPaM; 1033 1034 sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false ); 1035 const EditLine& rLine = pParaPortion->GetLines()[nLine]; 1036 bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); 1037 1038 pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; 1039 1040 bool bParaRTL = IsRightToLeft( nPara ); 1041 1042 bool bDone = false; 1043 1044 if ( bEmptyLine ) 1045 { 1046 if ( bVisualToLeft ) 1047 { 1048 aPaM = CursorUp( aPaM, pEditView ); 1049 if ( aPaM != rPaM ) 1050 aPaM = CursorVisualStartEnd( pEditView, aPaM, false ); 1051 } 1052 else 1053 { 1054 aPaM = CursorDown( aPaM, pEditView ); 1055 if ( aPaM != rPaM ) 1056 aPaM = CursorVisualStartEnd( pEditView, aPaM, true ); 1057 } 1058 1059 bDone = true; 1060 } 1061 1062 bool bLogicalBackward = bParaRTL ? !bVisualToLeft : bVisualToLeft; 1063 1064 if ( !bDone && pEditView->IsInsertMode() ) 1065 { 1066 // Check if we are within a portion and don't have overwrite mode, then it's easy... 1067 sal_Int32 nPortionStart; 1068 sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart ); 1069 const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; 1070 1071 bool bPortionBoundary = ( aPaM.GetIndex() == nPortionStart ) || ( aPaM.GetIndex() == (nPortionStart+rTextPortion.GetLen()) ); 1072 sal_uInt16 nRTLLevel = rTextPortion.GetRightToLeftLevel(); 1073 1074 // Portion boundary doesn't matter if both have same RTL level 1075 sal_Int32 nRTLLevelNextPortion = -1; 1076 if ( bPortionBoundary && aPaM.GetIndex() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) ) 1077 { 1078 sal_Int32 nTmp; 1079 sal_Int32 nNextTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex()+1, nTmp, !bLogicalBackward ); 1080 const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nNextTextPortion]; 1081 nRTLLevelNextPortion = rNextTextPortion.GetRightToLeftLevel(); 1082 } 1083 1084 if ( !bPortionBoundary || ( nRTLLevel == nRTLLevelNextPortion ) ) 1085 { 1086 if (bVisualToLeft != bool(nRTLLevel % 2)) 1087 { 1088 aPaM = CursorLeft( aPaM, nCharacterIteratorMode ); 1089 pEditView->pImpEditView->SetCursorBidiLevel( 1 ); 1090 } 1091 else 1092 { 1093 aPaM = CursorRight( aPaM, nCharacterIteratorMode ); 1094 pEditView->pImpEditView->SetCursorBidiLevel( 0 ); 1095 } 1096 bDone = true; 1097 } 1098 } 1099 1100 if ( !bDone ) 1101 { 1102 bool bGotoStartOfNextLine = false; 1103 bool bGotoEndOfPrevLine = false; 1104 1105 OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()); 1106 const sal_Int32 nPosInLine = aPaM.GetIndex() - rLine.GetStart(); 1107 1108 UErrorCode nError = U_ZERO_ERROR; 1109 UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError ); 1110 1111 const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/; 1112 ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError ); 1113 1114 if ( !pEditView->IsInsertMode() ) 1115 { 1116 bool bEndOfLine = nPosInLine == aLine.getLength(); 1117 sal_Int32 nVisPos = ubidi_getVisualIndex( pBidi, !bEndOfLine ? nPosInLine : nPosInLine-1, &nError ); 1118 if ( bVisualToLeft ) 1119 { 1120 bGotoEndOfPrevLine = nVisPos == 0; 1121 if ( !bEndOfLine ) 1122 nVisPos--; 1123 } 1124 else 1125 { 1126 bGotoStartOfNextLine = nVisPos == (aLine.getLength() - 1); 1127 if ( !bEndOfLine ) 1128 nVisPos++; 1129 } 1130 1131 if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine ) 1132 { 1133 aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) ); 1134 pEditView->pImpEditView->SetCursorBidiLevel( 0 ); 1135 } 1136 } 1137 else 1138 { 1139 bool bWasBehind = false; 1140 bool bBeforePortion = !nPosInLine || pEditView->pImpEditView->GetCursorBidiLevel() == 1; 1141 if ( nPosInLine && ( !bBeforePortion ) ) // before the next portion 1142 bWasBehind = true; // step one back, otherwise visual will be unusable when rtl portion follows. 1143 1144 sal_Int32 nPortionStart; 1145 sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, bBeforePortion ); 1146 const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; 1147 bool bRTLPortion = rTextPortion.IsRightToLeft(); 1148 1149 // -1: We are 'behind' the character 1150 tools::Long nVisPos = static_cast<tools::Long>(ubidi_getVisualIndex( pBidi, bWasBehind ? nPosInLine-1 : nPosInLine, &nError )); 1151 if ( bVisualToLeft ) 1152 { 1153 if ( !bWasBehind || bRTLPortion ) 1154 nVisPos--; 1155 } 1156 else 1157 { 1158 if ( bWasBehind || bRTLPortion || bBeforePortion ) 1159 nVisPos++; 1160 } 1161 1162 bGotoEndOfPrevLine = nVisPos < 0; 1163 bGotoStartOfNextLine = nVisPos >= aLine.getLength(); 1164 1165 if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine ) 1166 { 1167 aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) ); 1168 1169 // RTL portion, stay visually on the left side. 1170 sal_Int32 _nPortionStart; 1171 // sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, !bRTLPortion ); 1172 sal_Int32 _nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), _nPortionStart, true ); 1173 const TextPortion& _rTextPortion = pParaPortion->GetTextPortions()[_nTextPortion]; 1174 if ( bVisualToLeft && !bRTLPortion && _rTextPortion.IsRightToLeft() ) 1175 aPaM.SetIndex( aPaM.GetIndex()+1 ); 1176 else if ( !bVisualToLeft && bRTLPortion && ( bWasBehind || !_rTextPortion.IsRightToLeft() ) ) 1177 aPaM.SetIndex( aPaM.GetIndex()+1 ); 1178 1179 pEditView->pImpEditView->SetCursorBidiLevel( _nPortionStart ); 1180 } 1181 } 1182 1183 ubidi_close( pBidi ); 1184 1185 if ( bGotoEndOfPrevLine ) 1186 { 1187 aPaM = CursorUp( aPaM, pEditView ); 1188 if ( aPaM != rPaM ) 1189 aPaM = CursorVisualStartEnd( pEditView, aPaM, false ); 1190 } 1191 else if ( bGotoStartOfNextLine ) 1192 { 1193 aPaM = CursorDown( aPaM, pEditView ); 1194 if ( aPaM != rPaM ) 1195 aPaM = CursorVisualStartEnd( pEditView, aPaM, true ); 1196 } 1197 } 1198 return aPaM; 1199 } 1200 1201 1202 EditPaM ImpEditEngine::CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) 1203 { 1204 EditPaM aCurPaM( rPaM ); 1205 EditPaM aNewPaM( aCurPaM ); 1206 1207 if ( aCurPaM.GetIndex() ) 1208 { 1209 sal_Int32 nCount = 1; 1210 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1211 aNewPaM.SetIndex( 1212 _xBI->previousCharacters( 1213 aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount)); 1214 } 1215 else 1216 { 1217 ContentNode* pNode = aCurPaM.GetNode(); 1218 pNode = GetPrevVisNode( pNode ); 1219 if ( pNode ) 1220 { 1221 aNewPaM.SetNode( pNode ); 1222 aNewPaM.SetIndex( pNode->Len() ); 1223 } 1224 } 1225 1226 return aNewPaM; 1227 } 1228 1229 EditPaM ImpEditEngine::CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) 1230 { 1231 EditPaM aCurPaM( rPaM ); 1232 EditPaM aNewPaM( aCurPaM ); 1233 1234 if ( aCurPaM.GetIndex() < aCurPaM.GetNode()->Len() ) 1235 { 1236 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1237 sal_Int32 nCount = 1; 1238 aNewPaM.SetIndex( 1239 _xBI->nextCharacters( 1240 aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount)); 1241 } 1242 else 1243 { 1244 ContentNode* pNode = aCurPaM.GetNode(); 1245 pNode = GetNextVisNode( pNode ); 1246 if ( pNode ) 1247 { 1248 aNewPaM.SetNode( pNode ); 1249 aNewPaM.SetIndex( 0 ); 1250 } 1251 } 1252 1253 return aNewPaM; 1254 } 1255 1256 EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView const * pView ) 1257 { 1258 assert(pView && "No View - No Cursor Movement!"); 1259 1260 const ParaPortion& rPPortion = FindParaPortion( rPaM.GetNode() ); 1261 sal_Int32 nLine = rPPortion.GetLineNumber( rPaM.GetIndex() ); 1262 const EditLine& rLine = rPPortion.GetLines()[nLine]; 1263 1264 tools::Long nX; 1265 if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) 1266 { 1267 nX = GetXPos( &rPPortion, &rLine, rPaM.GetIndex() ); 1268 pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; 1269 } 1270 else 1271 nX = pView->pImpEditView->nTravelXPos; 1272 1273 EditPaM aNewPaM( rPaM ); 1274 if ( nLine ) // same paragraph 1275 { 1276 const EditLine& rPrevLine = rPPortion.GetLines()[nLine-1]; 1277 aNewPaM.SetIndex( GetChar( &rPPortion, &rPrevLine, nX ) ); 1278 // If a previous automatically wrapped line, and one has to be exactly 1279 // at the end of this line, the cursor lands on the current line at the 1280 // beginning. See Problem: Last character of an automatically wrapped 1281 // Row = cursor 1282 if ( aNewPaM.GetIndex() && ( aNewPaM.GetIndex() == rLine.GetStart() ) ) 1283 aNewPaM = CursorLeft( aNewPaM ); 1284 } 1285 else // previous paragraph 1286 { 1287 const ParaPortion* pPrevPortion = GetPrevVisPortion( &rPPortion ); 1288 if ( pPrevPortion ) 1289 { 1290 const EditLine& rLine2 = pPrevPortion->GetLines()[pPrevPortion->GetLines().Count()-1]; 1291 aNewPaM.SetNode( pPrevPortion->GetNode() ); 1292 aNewPaM.SetIndex( GetChar( pPrevPortion, &rLine2, nX+nOnePixelInRef ) ); 1293 } 1294 } 1295 1296 return aNewPaM; 1297 } 1298 1299 EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView const * pView ) 1300 { 1301 OSL_ENSURE( pView, "No View - No Cursor Movement!" ); 1302 1303 const ParaPortion& rPPortion = FindParaPortion( rPaM.GetNode() ); 1304 sal_Int32 nLine = rPPortion.GetLineNumber( rPaM.GetIndex() ); 1305 1306 tools::Long nX; 1307 if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) 1308 { 1309 const EditLine& rLine = rPPortion.GetLines()[nLine]; 1310 nX = GetXPos( &rPPortion, &rLine, rPaM.GetIndex() ); 1311 pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; 1312 } 1313 else 1314 nX = pView->pImpEditView->nTravelXPos; 1315 1316 EditPaM aNewPaM( rPaM ); 1317 if ( nLine < rPPortion.GetLines().Count()-1 ) 1318 { 1319 const EditLine& rNextLine = rPPortion.GetLines()[nLine+1]; 1320 aNewPaM.SetIndex( GetChar( &rPPortion, &rNextLine, nX ) ); 1321 // Special treatment, see CursorUp ... 1322 if ( ( aNewPaM.GetIndex() == rNextLine.GetEnd() ) && ( aNewPaM.GetIndex() > rNextLine.GetStart() ) && ( aNewPaM.GetIndex() < rPPortion.GetNode()->Len() ) ) 1323 aNewPaM = CursorLeft( aNewPaM ); 1324 } 1325 else // next paragraph 1326 { 1327 const ParaPortion* pNextPortion = GetNextVisPortion( &rPPortion ); 1328 if ( pNextPortion ) 1329 { 1330 const EditLine& rLine = pNextPortion->GetLines()[0]; 1331 aNewPaM.SetNode( pNextPortion->GetNode() ); 1332 // Never at the very end when several lines, because then a line 1333 // below the cursor appears. 1334 aNewPaM.SetIndex( GetChar( pNextPortion, &rLine, nX+nOnePixelInRef ) ); 1335 if ( ( aNewPaM.GetIndex() == rLine.GetEnd() ) && ( aNewPaM.GetIndex() > rLine.GetStart() ) && ( pNextPortion->GetLines().Count() > 1 ) ) 1336 aNewPaM = CursorLeft( aNewPaM ); 1337 } 1338 } 1339 1340 return aNewPaM; 1341 } 1342 1343 EditPaM ImpEditEngine::CursorStartOfLine( const EditPaM& rPaM ) 1344 { 1345 const ParaPortion& rCurPortion = FindParaPortion( rPaM.GetNode() ); 1346 sal_Int32 nLine = rCurPortion.GetLineNumber( rPaM.GetIndex() ); 1347 const EditLine& rLine = rCurPortion.GetLines()[nLine]; 1348 1349 EditPaM aNewPaM( rPaM ); 1350 aNewPaM.SetIndex( rLine.GetStart() ); 1351 return aNewPaM; 1352 } 1353 1354 EditPaM ImpEditEngine::CursorEndOfLine( const EditPaM& rPaM ) 1355 { 1356 const ParaPortion& rCurPortion = FindParaPortion( rPaM.GetNode() ); 1357 sal_Int32 nLine = rCurPortion.GetLineNumber( rPaM.GetIndex() ); 1358 const EditLine& rLine = rCurPortion.GetLines()[nLine]; 1359 1360 EditPaM aNewPaM( rPaM ); 1361 aNewPaM.SetIndex( rLine.GetEnd() ); 1362 if ( rLine.GetEnd() > rLine.GetStart() ) 1363 { 1364 if ( aNewPaM.GetNode()->IsFeature( aNewPaM.GetIndex() - 1 ) ) 1365 { 1366 // When a soft break, be in front of it! 1367 const EditCharAttrib* pNextFeature = aNewPaM.GetNode()->GetCharAttribs().FindFeature( aNewPaM.GetIndex()-1 ); 1368 if ( pNextFeature && ( pNextFeature->GetItem()->Which() == EE_FEATURE_LINEBR ) ) 1369 aNewPaM = CursorLeft( aNewPaM ); 1370 } 1371 else if ( ( aNewPaM.GetNode()->GetChar( aNewPaM.GetIndex() - 1 ) == ' ' ) && ( aNewPaM.GetIndex() != aNewPaM.GetNode()->Len() ) ) 1372 { 1373 // For a Blank in an auto wrapped line, it makes sense, to stand 1374 // in front of it, since the user wants to be after the word. 1375 // If this is changed, special treatment for Pos1 to End! 1376 aNewPaM = CursorLeft( aNewPaM ); 1377 } 1378 } 1379 return aNewPaM; 1380 } 1381 1382 EditPaM ImpEditEngine::CursorStartOfParagraph( const EditPaM& rPaM ) 1383 { 1384 EditPaM aPaM(rPaM); 1385 aPaM.SetIndex(0); 1386 return aPaM; 1387 } 1388 1389 EditPaM ImpEditEngine::CursorEndOfParagraph( const EditPaM& rPaM ) 1390 { 1391 EditPaM aPaM(rPaM); 1392 aPaM.SetIndex(rPaM.GetNode()->Len()); 1393 return aPaM; 1394 } 1395 1396 EditPaM ImpEditEngine::CursorStartOfDoc() 1397 { 1398 EditPaM aPaM( aEditDoc.GetObject( 0 ), 0 ); 1399 return aPaM; 1400 } 1401 1402 EditPaM ImpEditEngine::CursorEndOfDoc() 1403 { 1404 ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 ); 1405 ParaPortion* pLastPortion = GetParaPortions().SafeGetObject( aEditDoc.Count()-1 ); 1406 OSL_ENSURE( pLastNode && pLastPortion, "CursorEndOfDoc: Node or Portion not found" ); 1407 if (!(pLastNode && pLastPortion)) 1408 return EditPaM(); 1409 1410 if ( !pLastPortion->IsVisible() ) 1411 { 1412 pLastNode = GetPrevVisNode( pLastPortion->GetNode() ); 1413 OSL_ENSURE( pLastNode, "No visible paragraph?" ); 1414 if ( !pLastNode ) 1415 pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 ); 1416 } 1417 1418 EditPaM aPaM( pLastNode, pLastNode->Len() ); 1419 return aPaM; 1420 } 1421 1422 EditPaM ImpEditEngine::PageUp( const EditPaM& rPaM, EditView const * pView ) 1423 { 1424 tools::Rectangle aRect = PaMtoEditCursor( rPaM ); 1425 Point aTopLeft = aRect.TopLeft(); 1426 aTopLeft.AdjustY( -(pView->GetVisArea().GetHeight() *9/10) ); 1427 aTopLeft.AdjustX(nOnePixelInRef ); 1428 if ( aTopLeft.Y() < 0 ) 1429 { 1430 aTopLeft.setY( 0 ); 1431 } 1432 return GetPaM( aTopLeft ); 1433 } 1434 1435 EditPaM ImpEditEngine::PageDown( const EditPaM& rPaM, EditView const * pView ) 1436 { 1437 tools::Rectangle aRect = PaMtoEditCursor( rPaM ); 1438 Point aBottomRight = aRect.BottomRight(); 1439 aBottomRight.AdjustY(pView->GetVisArea().GetHeight() *9/10 ); 1440 aBottomRight.AdjustX(nOnePixelInRef ); 1441 tools::Long nHeight = GetTextHeight(); 1442 if ( aBottomRight.Y() > nHeight ) 1443 { 1444 aBottomRight.setY( nHeight-2 ); 1445 } 1446 return GetPaM( aBottomRight ); 1447 } 1448 1449 EditPaM ImpEditEngine::WordLeft( const EditPaM& rPaM ) 1450 { 1451 const sal_Int32 nCurrentPos = rPaM.GetIndex(); 1452 EditPaM aNewPaM( rPaM ); 1453 if ( nCurrentPos == 0 ) 1454 { 1455 // Previous paragraph... 1456 sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() ); 1457 ContentNode* pPrevNode = aEditDoc.GetObject( --nCurPara ); 1458 if ( pPrevNode ) 1459 { 1460 aNewPaM.SetNode( pPrevNode ); 1461 aNewPaM.SetIndex( pPrevNode->Len() ); 1462 } 1463 } 1464 else 1465 { 1466 // we need to increase the position by 1 when retrieving the locale 1467 // since the attribute for the char left to the cursor position is returned 1468 EditPaM aTmpPaM( aNewPaM ); 1469 if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() ) 1470 aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); 1471 lang::Locale aLocale( GetLocale( aTmpPaM ) ); 1472 1473 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1474 i18n::Boundary aBoundary = 1475 _xBI->getWordBoundary(aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); 1476 if ( aBoundary.startPos >= nCurrentPos ) 1477 aBoundary = _xBI->previousWord( 1478 aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES); 1479 aNewPaM.SetIndex( ( aBoundary.startPos != -1 ) ? aBoundary.startPos : 0 ); 1480 } 1481 1482 return aNewPaM; 1483 } 1484 1485 EditPaM ImpEditEngine::WordRight( const EditPaM& rPaM, sal_Int16 nWordType ) 1486 { 1487 const sal_Int32 nMax = rPaM.GetNode()->Len(); 1488 EditPaM aNewPaM( rPaM ); 1489 if ( aNewPaM.GetIndex() < nMax ) 1490 { 1491 // we need to increase the position by 1 when retrieving the locale 1492 // since the attribute for the char left to the cursor position is returned 1493 EditPaM aTmpPaM( aNewPaM ); 1494 aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); 1495 lang::Locale aLocale( GetLocale( aTmpPaM ) ); 1496 1497 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1498 i18n::Boundary aBoundary = _xBI->nextWord( 1499 aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), aLocale, nWordType); 1500 aNewPaM.SetIndex( aBoundary.startPos ); 1501 } 1502 // not 'else', maybe the index reached nMax now... 1503 if ( aNewPaM.GetIndex() >= nMax ) 1504 { 1505 // Next paragraph ... 1506 sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() ); 1507 ContentNode* pNextNode = aEditDoc.GetObject( ++nCurPara ); 1508 if ( pNextNode ) 1509 { 1510 aNewPaM.SetNode( pNextNode ); 1511 aNewPaM.SetIndex( 0 ); 1512 } 1513 } 1514 return aNewPaM; 1515 } 1516 1517 EditPaM ImpEditEngine::StartOfWord( const EditPaM& rPaM ) 1518 { 1519 EditPaM aNewPaM( rPaM ); 1520 1521 // we need to increase the position by 1 when retrieving the locale 1522 // since the attribute for the char left to the cursor position is returned 1523 EditPaM aTmpPaM( aNewPaM ); 1524 if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() ) 1525 aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); 1526 lang::Locale aLocale( GetLocale( aTmpPaM ) ); 1527 1528 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1529 i18n::Boundary aBoundary = _xBI->getWordBoundary( 1530 rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); 1531 1532 aNewPaM.SetIndex( aBoundary.startPos ); 1533 return aNewPaM; 1534 } 1535 1536 EditPaM ImpEditEngine::EndOfWord( const EditPaM& rPaM ) 1537 { 1538 EditPaM aNewPaM( rPaM ); 1539 1540 // we need to increase the position by 1 when retrieving the locale 1541 // since the attribute for the char left to the cursor position is returned 1542 EditPaM aTmpPaM( aNewPaM ); 1543 if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() ) 1544 aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); 1545 lang::Locale aLocale( GetLocale( aTmpPaM ) ); 1546 1547 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1548 i18n::Boundary aBoundary = _xBI->getWordBoundary( 1549 rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); 1550 1551 aNewPaM.SetIndex( aBoundary.endPos ); 1552 return aNewPaM; 1553 } 1554 1555 EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 nWordType, bool bAcceptStartOfWord ) 1556 { 1557 EditSelection aNewSel( rCurSel ); 1558 EditPaM aPaM( rCurSel.Max() ); 1559 1560 // we need to increase the position by 1 when retrieving the locale 1561 // since the attribute for the char left to the cursor position is returned 1562 EditPaM aTmpPaM( aPaM ); 1563 if ( aTmpPaM.GetIndex() < aPaM.GetNode()->Len() ) 1564 aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); 1565 lang::Locale aLocale( GetLocale( aTmpPaM ) ); 1566 1567 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1568 sal_Int16 nType = _xBI->getWordType( 1569 aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale); 1570 1571 if ( nType == i18n::WordType::ANY_WORD ) 1572 { 1573 i18n::Boundary aBoundary = _xBI->getWordBoundary( 1574 aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale, nWordType, true); 1575 1576 // don't select when cursor at end of word 1577 if ( ( aBoundary.endPos > aPaM.GetIndex() ) && 1578 ( ( aBoundary.startPos < aPaM.GetIndex() ) || ( bAcceptStartOfWord && ( aBoundary.startPos == aPaM.GetIndex() ) ) ) ) 1579 { 1580 aNewSel.Min().SetIndex( aBoundary.startPos ); 1581 aNewSel.Max().SetIndex( aBoundary.endPos ); 1582 } 1583 } 1584 1585 return aNewSel; 1586 } 1587 1588 EditSelection ImpEditEngine::SelectSentence( const EditSelection& rCurSel ) 1589 const 1590 { 1591 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1592 const EditPaM& rPaM = rCurSel.Min(); 1593 const ContentNode* pNode = rPaM.GetNode(); 1594 // #i50710# line breaks are marked with 0x01 - the break iterator prefers 0x0a for that 1595 const OUString sParagraph = pNode->GetString().replaceAll("\x01", "\x0a"); 1596 //return Null if search starts at the beginning of the string 1597 sal_Int32 nStart = rPaM.GetIndex() ? _xBI->beginOfSentence( sParagraph, rPaM.GetIndex(), GetLocale( rPaM ) ) : 0; 1598 1599 sal_Int32 nEnd = _xBI->endOfSentence( 1600 pNode->GetString(), rPaM.GetIndex(), GetLocale(rPaM)); 1601 1602 EditSelection aNewSel( rCurSel ); 1603 OSL_ENSURE(pNode->Len() ? (nStart < pNode->Len()) : (nStart == 0), "sentence start index out of range"); 1604 OSL_ENSURE(nEnd <= pNode->Len(), "sentence end index out of range"); 1605 aNewSel.Min().SetIndex( nStart ); 1606 aNewSel.Max().SetIndex( nEnd ); 1607 return aNewSel; 1608 } 1609 1610 bool ImpEditEngine::IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const 1611 { 1612 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1613 if (!pCTLOptions) 1614 pCTLOptions.reset( new SvtCTLOptions ); 1615 1616 // get the index that really is first 1617 const sal_Int32 nFirstPos = std::min(rCurSel.Min().GetIndex(), rCurSel.Max().GetIndex()); 1618 1619 bool bIsSequenceChecking = 1620 pCTLOptions->IsCTLFontEnabled() && 1621 pCTLOptions->IsCTLSequenceChecking() && 1622 nFirstPos != 0 && /* first char needs not to be checked */ 1623 _xBI.is() && i18n::ScriptType::COMPLEX == _xBI->getScriptType( OUString( nChar ), 0 ); 1624 1625 return bIsSequenceChecking; 1626 } 1627 1628 static bool lcl_HasStrongLTR ( std::u16string_view rTxt, sal_Int32 nStart, sal_Int32 nEnd ) 1629 { 1630 for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx ) 1631 { 1632 const UCharDirection nCharDir = u_charDirection ( rTxt[ nCharIdx ] ); 1633 if ( nCharDir == U_LEFT_TO_RIGHT || 1634 nCharDir == U_LEFT_TO_RIGHT_EMBEDDING || 1635 nCharDir == U_LEFT_TO_RIGHT_OVERRIDE ) 1636 return true; 1637 } 1638 return false; 1639 } 1640 1641 1642 void ImpEditEngine::InitScriptTypes( sal_Int32 nPara ) 1643 { 1644 ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 1645 if (!pParaPortion) 1646 return; 1647 1648 ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; 1649 rTypes.clear(); 1650 1651 ContentNode* pNode = pParaPortion->GetNode(); 1652 if ( !pNode->Len() ) 1653 return; 1654 1655 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 1656 1657 OUString aText = pNode->GetString(); 1658 1659 // To handle fields put the character from the field in the string, 1660 // because endOfScript( ... ) will skip the CH_FEATURE, because this is WEAK 1661 const EditCharAttrib* pField = pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, 0 ); 1662 while ( pField ) 1663 { 1664 const OUString aFldText = static_cast<const EditCharAttribField*>(pField)->GetFieldValue(); 1665 if ( !aFldText.isEmpty() ) 1666 { 1667 aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(0,1) ); 1668 short nFldScriptType = _xBI->getScriptType( aFldText, 0 ); 1669 1670 for ( sal_Int32 nCharInField = 1; nCharInField < aFldText.getLength(); nCharInField++ ) 1671 { 1672 short nTmpType = _xBI->getScriptType( aFldText, nCharInField ); 1673 1674 // First char from field wins... 1675 if ( nFldScriptType == i18n::ScriptType::WEAK ) 1676 { 1677 nFldScriptType = nTmpType; 1678 aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) ); 1679 } 1680 1681 // ... but if the first one is LATIN, and there are CJK or CTL chars too, 1682 // we prefer that ScriptType because we need another font. 1683 if ( ( nTmpType == i18n::ScriptType::ASIAN ) || ( nTmpType == i18n::ScriptType::COMPLEX ) ) 1684 { 1685 aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) ); 1686 break; 1687 } 1688 } 1689 } 1690 // #112831# Last Field might go from 0xffff to 0x0000 1691 pField = pField->GetEnd() ? pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, pField->GetEnd() ) : nullptr; 1692 } 1693 1694 sal_Int32 nTextLen = aText.getLength(); 1695 1696 sal_Int32 nPos = 0; 1697 short nScriptType = _xBI->getScriptType( aText, nPos ); 1698 rTypes.emplace_back( nScriptType, nPos, nTextLen ); 1699 nPos = _xBI->endOfScript( aText, nPos, nScriptType ); 1700 while ( ( nPos != -1 ) && ( nPos < nTextLen ) ) 1701 { 1702 rTypes.back().nEndPos = nPos; 1703 1704 nScriptType = _xBI->getScriptType( aText, nPos ); 1705 tools::Long nEndPos = _xBI->endOfScript( aText, nPos, nScriptType ); 1706 1707 if ( ( nScriptType == i18n::ScriptType::WEAK ) || ( nScriptType == rTypes.back().nScriptType ) ) 1708 { 1709 // Expand last ScriptTypePosInfo, don't create weak or unnecessary portions 1710 rTypes.back().nEndPos = nEndPos; 1711 } 1712 else 1713 { 1714 if ( _xBI->getScriptType( aText, nPos - 1 ) == i18n::ScriptType::WEAK ) 1715 { 1716 switch ( u_charType(aText.iterateCodePoints(&nPos, 0) ) ) { 1717 case U_NON_SPACING_MARK: 1718 case U_ENCLOSING_MARK: 1719 case U_COMBINING_SPACING_MARK: 1720 --nPos; 1721 rTypes.back().nEndPos--; 1722 break; 1723 } 1724 } 1725 rTypes.emplace_back( nScriptType, nPos, nTextLen ); 1726 } 1727 1728 nPos = nEndPos; 1729 } 1730 1731 if ( rTypes[0].nScriptType == i18n::ScriptType::WEAK ) 1732 rTypes[0].nScriptType = ( rTypes.size() > 1 ) ? rTypes[1].nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() ); 1733 1734 // create writing direction information: 1735 if ( pParaPortion->aWritingDirectionInfos.empty() ) 1736 InitWritingDirections( nPara ); 1737 1738 // i89825: Use CTL font for numbers embedded into an RTL run: 1739 WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; 1740 for (const WritingDirectionInfo & rDirInfo : rDirInfos) 1741 { 1742 const sal_Int32 nStart = rDirInfo.nStartPos; 1743 const sal_Int32 nEnd = rDirInfo.nEndPos; 1744 const sal_uInt8 nCurrDirType = rDirInfo.nType; 1745 1746 if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run 1747 ( nCurrDirType > UBIDI_LTR && !lcl_HasStrongLTR( aText, nStart, nEnd ) ) ) // non-strong text in embedded LTR run 1748 { 1749 size_t nIdx = 0; 1750 1751 // Skip entries in ScriptArray which are not inside the RTL run: 1752 while ( nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart ) 1753 ++nIdx; 1754 1755 // Remove any entries *inside* the current run: 1756 while (nIdx < rTypes.size() && rTypes[nIdx].nEndPos <= nEnd) 1757 { 1758 // coverity[use_iterator] - we're protected from a bad iterator by the above condition 1759 rTypes.erase(rTypes.begin() + nIdx); 1760 } 1761 1762 // special case: 1763 if(nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart && rTypes[nIdx].nEndPos > nEnd) 1764 { 1765 rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( rTypes[nIdx].nScriptType, nEnd, rTypes[nIdx].nEndPos ) ); 1766 rTypes[nIdx].nEndPos = nStart; 1767 } 1768 1769 if( nIdx ) 1770 rTypes[nIdx - 1].nEndPos = nStart; 1771 1772 rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( i18n::ScriptType::COMPLEX, nStart, nEnd) ); 1773 ++nIdx; 1774 1775 if( nIdx < rTypes.size() ) 1776 rTypes[nIdx].nStartPos = nEnd; 1777 } 1778 } 1779 } 1780 1781 namespace { 1782 1783 struct FindByPos 1784 { 1785 explicit FindByPos(sal_Int32 nPos) 1786 : mnPos(nPos) 1787 { 1788 } 1789 1790 bool operator()(const ScriptTypePosInfos::value_type& rValue) 1791 { 1792 return rValue.nStartPos <= mnPos && rValue.nEndPos >= mnPos; 1793 } 1794 1795 private: 1796 sal_Int32 mnPos; 1797 }; 1798 1799 } 1800 1801 sal_uInt16 ImpEditEngine::GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos ) const 1802 { 1803 sal_uInt16 nScriptType = 0; 1804 1805 if ( pEndPos ) 1806 *pEndPos = rPaM.GetNode()->Len(); 1807 1808 if ( rPaM.GetNode()->Len() ) 1809 { 1810 sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() ); 1811 const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 1812 if (pParaPortion) 1813 { 1814 if ( pParaPortion->aScriptInfos.empty() ) 1815 const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); 1816 1817 const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; 1818 1819 const sal_Int32 nPos = rPaM.GetIndex(); 1820 ScriptTypePosInfos::const_iterator itr = std::find_if(rTypes.begin(), rTypes.end(), FindByPos(nPos)); 1821 if(itr != rTypes.end()) 1822 { 1823 nScriptType = itr->nScriptType; 1824 if( pEndPos ) 1825 *pEndPos = itr->nEndPos; 1826 } 1827 } 1828 } 1829 return nScriptType ? nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() ); 1830 } 1831 1832 SvtScriptType ImpEditEngine::GetItemScriptType( const EditSelection& rSel ) const 1833 { 1834 EditSelection aSel( rSel ); 1835 aSel.Adjust( aEditDoc ); 1836 1837 SvtScriptType nScriptType = SvtScriptType::NONE; 1838 1839 sal_Int32 nStartPara = GetEditDoc().GetPos( aSel.Min().GetNode() ); 1840 sal_Int32 nEndPara = GetEditDoc().GetPos( aSel.Max().GetNode() ); 1841 1842 for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) 1843 { 1844 const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 1845 if (!pParaPortion) 1846 continue; 1847 1848 if ( pParaPortion->aScriptInfos.empty() ) 1849 const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); 1850 1851 const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; 1852 1853 // find all the scripts of this range 1854 sal_Int32 nS = ( nPara == nStartPara ) ? aSel.Min().GetIndex() : 0; 1855 sal_Int32 nE = ( nPara == nEndPara ) ? aSel.Max().GetIndex() : pParaPortion->GetNode()->Len(); 1856 1857 //no selection, just bare cursor 1858 if (nStartPara == nEndPara && nS == nE) 1859 { 1860 //If we are not at the start of the paragraph we want the properties of the 1861 //preceding character. Otherwise get the properties of the next (or what the 1862 //next would have if it existed) 1863 if (nS != 0) 1864 --nS; 1865 else 1866 ++nE; 1867 } 1868 1869 for (const ScriptTypePosInfo & rType : rTypes) 1870 { 1871 bool bStartInRange = rType.nStartPos <= nS && nS < rType.nEndPos; 1872 bool bEndInRange = rType.nStartPos < nE && nE <= rType.nEndPos; 1873 1874 if (bStartInRange || bEndInRange) 1875 { 1876 if ( rType.nScriptType != i18n::ScriptType::WEAK ) 1877 nScriptType |= SvtLanguageOptions::FromI18NToSvtScriptType( rType.nScriptType ); 1878 } 1879 } 1880 } 1881 return bool(nScriptType) ? nScriptType : SvtLanguageOptions::GetScriptTypeOfLanguage( GetDefaultLanguage() ); 1882 } 1883 1884 bool ImpEditEngine::IsScriptChange( const EditPaM& rPaM ) const 1885 { 1886 bool bScriptChange = false; 1887 1888 if ( rPaM.GetNode()->Len() ) 1889 { 1890 sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() ); 1891 const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 1892 if (pParaPortion) 1893 { 1894 if ( pParaPortion->aScriptInfos.empty() ) 1895 const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); 1896 1897 const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; 1898 const sal_Int32 nPos = rPaM.GetIndex(); 1899 for (const ScriptTypePosInfo & rType : rTypes) 1900 { 1901 if ( rType.nStartPos == nPos ) 1902 { 1903 bScriptChange = true; 1904 break; 1905 } 1906 } 1907 } 1908 } 1909 return bScriptChange; 1910 } 1911 1912 bool ImpEditEngine::HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const 1913 { 1914 bool bTypeFound = false; 1915 1916 const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 1917 if (pParaPortion) 1918 { 1919 if ( pParaPortion->aScriptInfos.empty() ) 1920 const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); 1921 1922 const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; 1923 for ( size_t n = rTypes.size(); n && !bTypeFound; ) 1924 { 1925 if ( rTypes[--n].nScriptType == nType ) 1926 bTypeFound = true; 1927 } 1928 } 1929 return bTypeFound; 1930 } 1931 1932 void ImpEditEngine::InitWritingDirections( sal_Int32 nPara ) 1933 { 1934 ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 1935 if (!pParaPortion) 1936 return; 1937 1938 WritingDirectionInfos& rInfos = pParaPortion->aWritingDirectionInfos; 1939 rInfos.clear(); 1940 1941 if (pParaPortion->GetNode()->Len()) 1942 { 1943 const OUString aText = pParaPortion->GetNode()->GetString(); 1944 1945 // Bidi functions from icu 2.0 1946 1947 UErrorCode nError = U_ZERO_ERROR; 1948 UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError ); 1949 nError = U_ZERO_ERROR; 1950 1951 const UBiDiLevel nBidiLevel = IsRightToLeft(nPara) ? 1 /*RTL*/ : 0 /*LTR*/; 1952 ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError ); 1953 nError = U_ZERO_ERROR; 1954 1955 int32_t nCount = ubidi_countRuns( pBidi, &nError ); 1956 1957 /* ubidi_countRuns can return -1 in case of error */ 1958 if (nCount > 0) 1959 { 1960 int32_t nStart = 0; 1961 int32_t nEnd; 1962 UBiDiLevel nCurrDir; 1963 1964 for (int32_t nIdx = 0; nIdx < nCount; ++nIdx) 1965 { 1966 ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir ); 1967 rInfos.emplace_back( nCurrDir, nStart, nEnd ); 1968 nStart = nEnd; 1969 } 1970 } 1971 1972 ubidi_close( pBidi ); 1973 } 1974 1975 // No infos mean ubidi error, default to LTR 1976 if ( rInfos.empty() ) 1977 rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->Len() ); 1978 1979 } 1980 1981 bool ImpEditEngine::IsRightToLeft( sal_Int32 nPara ) const 1982 { 1983 bool bR2L = false; 1984 const SvxFrameDirectionItem* pFrameDirItem = nullptr; 1985 1986 if ( !IsEffectivelyVertical() ) 1987 { 1988 bR2L = GetDefaultHorizontalTextDirection() == EEHorizontalTextDirection::R2L; 1989 pFrameDirItem = &GetParaAttrib( nPara, EE_PARA_WRITINGDIR ); 1990 if ( pFrameDirItem->GetValue() == SvxFrameDirection::Environment ) 1991 { 1992 // #103045# if DefaultHorizontalTextDirection is set, use that value, otherwise pool default. 1993 if ( GetDefaultHorizontalTextDirection() != EEHorizontalTextDirection::Default ) 1994 { 1995 pFrameDirItem = nullptr; // bR2L already set to default horizontal text direction 1996 } 1997 else 1998 { 1999 // Use pool default 2000 pFrameDirItem = &GetEmptyItemSet().Get(EE_PARA_WRITINGDIR); 2001 } 2002 } 2003 } 2004 2005 if ( pFrameDirItem ) 2006 bR2L = pFrameDirItem->GetValue() == SvxFrameDirection::Horizontal_RL_TB; 2007 2008 return bR2L; 2009 } 2010 2011 bool ImpEditEngine::HasDifferentRTLLevels( const ContentNode* pNode ) 2012 { 2013 bool bHasDifferentRTLLevels = false; 2014 2015 sal_Int32 nPara = GetEditDoc().GetPos( pNode ); 2016 ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 2017 if (pParaPortion) 2018 { 2019 sal_uInt16 nRTLLevel = IsRightToLeft( nPara ) ? 1 : 0; 2020 for ( sal_Int32 n = 0; n < pParaPortion->GetTextPortions().Count(); n++ ) 2021 { 2022 const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n]; 2023 if ( rTextPortion.GetRightToLeftLevel() != nRTLLevel ) 2024 { 2025 bHasDifferentRTLLevels = true; 2026 break; 2027 } 2028 } 2029 } 2030 return bHasDifferentRTLLevels; 2031 } 2032 2033 2034 sal_uInt8 ImpEditEngine::GetRightToLeft( sal_Int32 nPara, sal_Int32 nPos, sal_Int32* pStart, sal_Int32* pEnd ) 2035 { 2036 sal_uInt8 nRightToLeft = 0; 2037 2038 ContentNode* pNode = aEditDoc.GetObject( nPara ); 2039 if ( pNode && pNode->Len() ) 2040 { 2041 ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); 2042 if (pParaPortion) 2043 { 2044 if ( pParaPortion->aWritingDirectionInfos.empty() ) 2045 InitWritingDirections( nPara ); 2046 2047 WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; 2048 for (const WritingDirectionInfo & rDirInfo : rDirInfos) 2049 { 2050 if ( ( rDirInfo.nStartPos <= nPos ) && ( rDirInfo.nEndPos >= nPos ) ) 2051 { 2052 nRightToLeft = rDirInfo.nType; 2053 if ( pStart ) 2054 *pStart = rDirInfo.nStartPos; 2055 if ( pEnd ) 2056 *pEnd = rDirInfo.nEndPos; 2057 break; 2058 } 2059 } 2060 } 2061 } 2062 return nRightToLeft; 2063 } 2064 2065 SvxAdjust ImpEditEngine::GetJustification( sal_Int32 nPara ) const 2066 { 2067 SvxAdjust eJustification = SvxAdjust::Left; 2068 2069 if ( !aStatus.IsOutliner() ) 2070 { 2071 eJustification = GetParaAttrib( nPara, EE_PARA_JUST ).GetAdjust(); 2072 2073 if ( IsRightToLeft( nPara ) ) 2074 { 2075 if ( eJustification == SvxAdjust::Left ) 2076 eJustification = SvxAdjust::Right; 2077 else if ( eJustification == SvxAdjust::Right ) 2078 eJustification = SvxAdjust::Left; 2079 } 2080 } 2081 return eJustification; 2082 } 2083 2084 SvxCellJustifyMethod ImpEditEngine::GetJustifyMethod( sal_Int32 nPara ) const 2085 { 2086 const SvxJustifyMethodItem& rItem = GetParaAttrib(nPara, EE_PARA_JUST_METHOD); 2087 return static_cast<SvxCellJustifyMethod>(rItem.GetEnumValue()); 2088 } 2089 2090 SvxCellVerJustify ImpEditEngine::GetVerJustification( sal_Int32 nPara ) const 2091 { 2092 const SvxVerJustifyItem& rItem = GetParaAttrib(nPara, EE_PARA_VER_JUST); 2093 return static_cast<SvxCellVerJustify>(rItem.GetEnumValue()); 2094 } 2095 2096 // Text changes 2097 void ImpEditEngine::ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars ) 2098 { 2099 if ( IsUndoEnabled() && !IsInUndo() ) 2100 { 2101 const OUString aStr( rPaM.GetNode()->Copy( rPaM.GetIndex(), nChars ) ); 2102 2103 // Check whether attributes are deleted or changed: 2104 const sal_Int32 nStart = rPaM.GetIndex(); 2105 const sal_Int32 nEnd = nStart + nChars; 2106 const CharAttribList::AttribsType& rAttribs = rPaM.GetNode()->GetCharAttribs().GetAttribs(); 2107 for (const auto & rAttrib : rAttribs) 2108 { 2109 const EditCharAttrib& rAttr = *rAttrib; 2110 if (rAttr.GetEnd() >= nStart && rAttr.GetStart() < nEnd) 2111 { 2112 EditSelection aSel( rPaM ); 2113 aSel.Max().SetIndex( aSel.Max().GetIndex() + nChars ); 2114 InsertUndo( CreateAttribUndo( aSel, GetEmptyItemSet() ) ); 2115 break; // for 2116 } 2117 } 2118 InsertUndo(std::make_unique<EditUndoRemoveChars>(pEditEngine, CreateEPaM(rPaM), aStr)); 2119 } 2120 2121 aEditDoc.RemoveChars( rPaM, nChars ); 2122 } 2123 2124 EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 nNewPos ) 2125 { 2126 aOldPositions.Justify(); 2127 bool bValidAction = ( static_cast<tools::Long>(nNewPos) < aOldPositions.Min() ) || ( static_cast<tools::Long>(nNewPos) > aOldPositions.Max() ); 2128 OSL_ENSURE( bValidAction, "Move in itself?" ); 2129 OSL_ENSURE( aOldPositions.Max() <= static_cast<tools::Long>(GetParaPortions().Count()), "totally over it: MoveParagraphs" ); 2130 2131 EditSelection aSelection; 2132 2133 if ( !bValidAction ) 2134 { 2135 aSelection = aEditDoc.GetStartPaM(); 2136 return aSelection; 2137 } 2138 2139 sal_Int32 nParaCount = GetParaPortions().Count(); 2140 2141 if ( nNewPos >= nParaCount ) 2142 nNewPos = nParaCount; 2143 2144 // Height may change when moving first or last Paragraph 2145 ParaPortion* pRecalc1 = nullptr; 2146 ParaPortion* pRecalc2 = nullptr; 2147 ParaPortion* pRecalc3 = nullptr; 2148 ParaPortion* pRecalc4 = nullptr; 2149 2150 if ( nNewPos == 0 ) // Move to Start 2151 { 2152 pRecalc1 = &GetParaPortions()[0]; 2153 pRecalc2 = &GetParaPortions()[aOldPositions.Min()]; 2154 2155 } 2156 else if ( nNewPos == nParaCount ) 2157 { 2158 pRecalc1 = &GetParaPortions()[nParaCount-1]; 2159 pRecalc2 = &GetParaPortions()[aOldPositions.Max()]; 2160 } 2161 2162 if ( aOldPositions.Min() == 0 ) // Move from Start 2163 { 2164 pRecalc3 = &GetParaPortions()[0]; 2165 pRecalc4 = &GetParaPortions()[aOldPositions.Max()+1]; 2166 } 2167 else if ( aOldPositions.Max() == (nParaCount-1) ) 2168 { 2169 pRecalc3 = &GetParaPortions()[aOldPositions.Max()]; 2170 pRecalc4 = &GetParaPortions()[aOldPositions.Min()-1]; 2171 } 2172 2173 MoveParagraphsInfo aMoveParagraphsInfo( aOldPositions.Min(), aOldPositions.Max(), nNewPos ); 2174 aBeginMovingParagraphsHdl.Call( aMoveParagraphsInfo ); 2175 2176 if ( IsUndoEnabled() && !IsInUndo()) 2177 InsertUndo(std::make_unique<EditUndoMoveParagraphs>(pEditEngine, aOldPositions, nNewPos)); 2178 2179 // do not lose sight of the Position ! 2180 ParaPortion* pDestPortion = GetParaPortions().SafeGetObject( nNewPos ); 2181 2182 ParaPortionList aTmpPortionList; 2183 for (tools::Long i = aOldPositions.Min(); i <= aOldPositions.Max(); i++ ) 2184 { 2185 // always aOldPositions.Min(), since Remove(). 2186 ParaPortion aTmpPortion = GetParaPortions().Remove(aOldPositions.Min()); 2187 aEditDoc.Release( aOldPositions.Min() ); 2188 aTmpPortionList.Append(std::move(aTmpPortion)); 2189 } 2190 2191 sal_Int32 nRealNewPos = pDestPortion ? GetParaPortions().GetPos( pDestPortion ) : GetParaPortions().Count(); 2192 OSL_ENSURE( nRealNewPos != EE_PARA_NOT_FOUND, "ImpMoveParagraphs: Invalid Position!" ); 2193 2194 sal_Int32 i = 0; 2195 while( aTmpPortionList.Count() > 0 ) 2196 { 2197 ParaPortion aTmpPortion = aTmpPortionList.Remove(0); 2198 if ( i == 0 ) 2199 aSelection.Min().SetNode( aTmpPortion.GetNode() ); 2200 2201 aSelection.Max().SetNode( aTmpPortion.GetNode() ); 2202 aSelection.Max().SetIndex( aTmpPortion.GetNode()->Len() ); 2203 2204 ContentNode* pN = aTmpPortion.GetNode(); 2205 aEditDoc.Insert(nRealNewPos+i, pN); 2206 2207 GetParaPortions().Insert(nRealNewPos+i, std::move(aTmpPortion)); 2208 ++i; 2209 } 2210 2211 aEndMovingParagraphsHdl.Call( aMoveParagraphsInfo ); 2212 2213 if ( GetNotifyHdl().IsSet() ) 2214 { 2215 EENotify aNotify( EE_NOTIFY_PARAGRAPHSMOVED ); 2216 aNotify.nParagraph = nNewPos; 2217 aNotify.nParam1 = aOldPositions.Min(); 2218 aNotify.nParam2 = aOldPositions.Max(); 2219 GetNotifyHdl().Call( aNotify ); 2220 } 2221 2222 aEditDoc.SetModified( true ); 2223 2224 if ( pRecalc1 ) 2225 CalcHeight( pRecalc1 ); 2226 if ( pRecalc2 ) 2227 CalcHeight( pRecalc2 ); 2228 if ( pRecalc3 ) 2229 CalcHeight( pRecalc3 ); 2230 if ( pRecalc4 ) 2231 CalcHeight( pRecalc4 ); 2232 2233 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG 2234 ParaPortionList::DbgCheck(GetParaPortions(), aEditDoc); 2235 #endif 2236 return aSelection; 2237 } 2238 2239 2240 EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward ) 2241 { 2242 OSL_ENSURE( pLeft != pRight, "Join together the same paragraph ?" ); 2243 OSL_ENSURE( aEditDoc.GetPos( pLeft ) != EE_PARA_NOT_FOUND, "Inserted node not found (1)" ); 2244 OSL_ENSURE( aEditDoc.GetPos( pRight ) != EE_PARA_NOT_FOUND, "Inserted node not found (2)" ); 2245 2246 // #i120020# it is possible that left and right are *not* in the desired order (left/right) 2247 // so correct it. This correction is needed, else an invalid SfxLinkUndoAction will be 2248 // created from ConnectParagraphs below. Assert this situation, it should be corrected by the 2249 // caller. 2250 if(aEditDoc.GetPos( pLeft ) > aEditDoc.GetPos( pRight )) 2251 { 2252 OSL_ENSURE(false, "ImpConnectParagraphs with wrong order of pLeft/pRight nodes (!)"); 2253 std::swap(pLeft, pRight); 2254 } 2255 2256 sal_Int32 nParagraphTobeDeleted = aEditDoc.GetPos( pRight ); 2257 aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pRight, nParagraphTobeDeleted )); 2258 2259 GetEditEnginePtr()->ParagraphConnected( aEditDoc.GetPos( pLeft ), aEditDoc.GetPos( pRight ) ); 2260 2261 if ( IsUndoEnabled() && !IsInUndo() ) 2262 { 2263 InsertUndo( std::make_unique<EditUndoConnectParas>(pEditEngine, 2264 aEditDoc.GetPos( pLeft ), pLeft->Len(), 2265 pLeft->GetContentAttribs().GetItems(), pRight->GetContentAttribs().GetItems(), 2266 pLeft->GetStyleSheet(), pRight->GetStyleSheet(), bBackward ) ); 2267 } 2268 2269 if ( bBackward ) 2270 { 2271 pLeft->SetStyleSheet( pRight->GetStyleSheet() ); 2272 // it feels wrong to set pLeft's attribs if pRight is empty, tdf#128046 2273 if ( pRight->Len() ) 2274 pLeft->GetContentAttribs().GetItems().Set( pRight->GetContentAttribs().GetItems() ); 2275 pLeft->GetCharAttribs().GetDefFont() = pRight->GetCharAttribs().GetDefFont(); 2276 } 2277 2278 ParaAttribsChanged( pLeft, true ); 2279 2280 // First search for Portions since pRight is gone after ConnectParagraphs. 2281 ParaPortion& rLeftPortion = FindParaPortion( pLeft ); 2282 2283 if ( GetStatus().DoOnlineSpelling() ) 2284 { 2285 sal_Int32 nEnd = pLeft->Len(); 2286 sal_Int32 nInv = nEnd ? nEnd-1 : nEnd; 2287 pLeft->GetWrongList()->ClearWrongs( nInv, static_cast<size_t>(-1), pLeft ); // Possibly remove one 2288 pLeft->GetWrongList()->SetInvalidRange(nInv, nEnd+1); 2289 // Take over misspelled words 2290 WrongList* pRWrongs = pRight->GetWrongList(); 2291 for (auto & elem : *pRWrongs) 2292 { 2293 if (elem.mnStart != 0) // Not a subsequent 2294 { 2295 elem.mnStart = elem.mnStart + nEnd; 2296 elem.mnEnd = elem.mnEnd + nEnd; 2297 pLeft->GetWrongList()->push_back(elem); 2298 } 2299 } 2300 } 2301 2302 if ( IsCallParaInsertedOrDeleted() ) 2303 GetEditEnginePtr()->ParagraphDeleted( nParagraphTobeDeleted ); 2304 2305 EditPaM aPaM = aEditDoc.ConnectParagraphs( pLeft, pRight ); 2306 GetParaPortions().Remove( nParagraphTobeDeleted ); 2307 2308 rLeftPortion.MarkSelectionInvalid( aPaM.GetIndex() ); 2309 2310 // the right node is deleted by EditDoc:ConnectParagraphs(). 2311 if ( GetTextRanger() ) 2312 { 2313 // By joining together the two, the left is although reformatted, 2314 // however if its height does not change then the formatting receives 2315 // the change of the total text height too late... 2316 for ( sal_Int32 n = nParagraphTobeDeleted; n < GetParaPortions().Count(); n++ ) 2317 { 2318 ParaPortion& rPP = GetParaPortions()[n]; 2319 rPP.MarkSelectionInvalid( 0 ); 2320 rPP.GetLines().Reset(); 2321 } 2322 } 2323 2324 TextModified(); 2325 2326 return aPaM; 2327 } 2328 2329 EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 nMode, DeleteMode nDelMode ) 2330 { 2331 OSL_ENSURE( !rSel.DbgIsBuggy( aEditDoc ), "Index out of range in DeleteLeftOrRight" ); 2332 2333 if ( rSel.HasRange() ) // only then Delete Selection 2334 return ImpDeleteSelection( rSel ); 2335 2336 EditPaM aCurPos( rSel.Max() ); 2337 EditPaM aDelStart( aCurPos ); 2338 EditPaM aDelEnd( aCurPos ); 2339 if ( nMode == DEL_LEFT ) 2340 { 2341 if ( nDelMode == DeleteMode::Simple ) 2342 { 2343 sal_uInt16 nCharMode = i18n::CharacterIteratorMode::SKIPCHARACTER; 2344 // Check if we are deleting a CJK ideograph variance sequence (IVS). 2345 sal_Int32 nIndex = aCurPos.GetIndex(); 2346 if (nIndex > 0) 2347 { 2348 const OUString& rString = aCurPos.GetNode()->GetString(); 2349 sal_Int32 nCode = rString.iterateCodePoints(&nIndex, -1); 2350 if (unicode::isIVSSelector(nCode) && nIndex > 0 && 2351 unicode::isCJKIVSCharacter(rString.iterateCodePoints(&nIndex, -1))) 2352 { 2353 nCharMode = i18n::CharacterIteratorMode::SKIPCELL; 2354 } 2355 } 2356 aDelStart = CursorLeft(aCurPos, nCharMode); 2357 } 2358 else if ( nDelMode == DeleteMode::RestOfWord ) 2359 { 2360 aDelStart = StartOfWord( aCurPos ); 2361 if ( aDelStart.GetIndex() == aCurPos.GetIndex() ) 2362 aDelStart = WordLeft( aCurPos ); 2363 } 2364 else // DELMODE_RESTOFCONTENT 2365 { 2366 aDelStart.SetIndex( 0 ); 2367 if ( aDelStart == aCurPos ) 2368 { 2369 // Complete paragraph previous 2370 ContentNode* pPrev = GetPrevVisNode( aCurPos.GetNode() ); 2371 if ( pPrev ) 2372 aDelStart = EditPaM( pPrev, 0 ); 2373 } 2374 } 2375 } 2376 else 2377 { 2378 if ( nDelMode == DeleteMode::Simple ) 2379 { 2380 aDelEnd = CursorRight( aCurPos ); 2381 } 2382 else if ( nDelMode == DeleteMode::RestOfWord ) 2383 { 2384 aDelEnd = EndOfWord( aCurPos ); 2385 if (aDelEnd.GetIndex() == aCurPos.GetIndex()) 2386 { 2387 const sal_Int32 nLen(aCurPos.GetNode()->Len()); 2388 // end of para? 2389 if (aDelEnd.GetIndex() == nLen) 2390 { 2391 ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() ); 2392 if ( pNext ) 2393 aDelEnd = EditPaM( pNext, 0 ); 2394 } 2395 else // there's still something to delete on the right 2396 { 2397 aDelEnd = EndOfWord( WordRight( aCurPos ) ); 2398 } 2399 } 2400 } 2401 else // DELMODE_RESTOFCONTENT 2402 { 2403 aDelEnd.SetIndex( aCurPos.GetNode()->Len() ); 2404 if ( aDelEnd == aCurPos ) 2405 { 2406 // Complete paragraph next 2407 ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() ); 2408 if ( pNext ) 2409 aDelEnd = EditPaM( pNext, pNext->Len() ); 2410 } 2411 } 2412 } 2413 2414 // ConnectParagraphs not enough for different Nodes when 2415 // DeleteMode::RestOfContent. 2416 if ( ( nDelMode == DeleteMode::RestOfContent ) || ( aDelStart.GetNode() == aDelEnd.GetNode() ) ) 2417 return ImpDeleteSelection( EditSelection( aDelStart, aDelEnd ) ); 2418 2419 return ImpConnectParagraphs(aDelStart.GetNode(), aDelEnd.GetNode()); 2420 } 2421 2422 EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) 2423 { 2424 if ( !rCurSel.HasRange() ) 2425 return rCurSel.Min(); 2426 2427 EditSelection aCurSel(rCurSel); 2428 aCurSel.Adjust( aEditDoc ); 2429 EditPaM aStartPaM(aCurSel.Min()); 2430 EditPaM aEndPaM(aCurSel.Max()); 2431 2432 CursorMoved( aStartPaM.GetNode() ); // only so that newly set Attributes disappear... 2433 CursorMoved( aEndPaM.GetNode() ); // only so that newly set Attributes disappear... 2434 2435 OSL_ENSURE( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" ); 2436 OSL_ENSURE( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" ); 2437 2438 sal_Int32 nStartNode = aEditDoc.GetPos( aStartPaM.GetNode() ); 2439 sal_Int32 nEndNode = aEditDoc.GetPos( aEndPaM.GetNode() ); 2440 2441 OSL_ENSURE( nEndNode != EE_PARA_NOT_FOUND, "Start > End ?!" ); 2442 OSL_ENSURE( nStartNode <= nEndNode, "Start > End ?!" ); 2443 2444 // Remove all nodes in between... 2445 for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ ) 2446 { 2447 // Always nStartNode+1, due to Remove()! 2448 ImpRemoveParagraph( nStartNode+1 ); 2449 } 2450 2451 if ( aStartPaM.GetNode() != aEndPaM.GetNode() ) 2452 { 2453 // The Rest of the StartNodes... 2454 ImpRemoveChars( aStartPaM, aStartPaM.GetNode()->Len() - aStartPaM.GetIndex() ); 2455 ParaPortion& rPortion = FindParaPortion( aStartPaM.GetNode() ); 2456 rPortion.MarkSelectionInvalid( aStartPaM.GetIndex() ); 2457 2458 // The beginning of the EndNodes... 2459 const sal_Int32 nChars = aEndPaM.GetIndex(); 2460 aEndPaM.SetIndex( 0 ); 2461 ImpRemoveChars( aEndPaM, nChars ); 2462 ParaPortion& rPortion2 = FindParaPortion( aEndPaM.GetNode() ); 2463 rPortion2.MarkSelectionInvalid( 0 ); 2464 // Join together... 2465 aStartPaM = ImpConnectParagraphs( aStartPaM.GetNode(), aEndPaM.GetNode() ); 2466 } 2467 else 2468 { 2469 ImpRemoveChars( aStartPaM, aEndPaM.GetIndex() - aStartPaM.GetIndex() ); 2470 ParaPortion& rPortion = FindParaPortion( aStartPaM.GetNode() ); 2471 rPortion.MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() ); 2472 } 2473 2474 UpdateSelections(); 2475 TextModified(); 2476 return aStartPaM; 2477 } 2478 2479 void ImpEditEngine::ImpRemoveParagraph( sal_Int32 nPara ) 2480 { 2481 ContentNode* pNode = aEditDoc.GetObject( nPara ); 2482 ContentNode* pNextNode = aEditDoc.GetObject( nPara+1 ); 2483 2484 OSL_ENSURE( pNode, "Blind Node in ImpRemoveParagraph" ); 2485 2486 aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pNode, nPara )); 2487 2488 // The node is managed by the undo and possibly destroyed! 2489 aEditDoc.Release( nPara ); 2490 GetParaPortions().Remove( nPara ); 2491 2492 if ( IsCallParaInsertedOrDeleted() ) 2493 { 2494 GetEditEnginePtr()->ParagraphDeleted( nPara ); 2495 } 2496 2497 // Extra-Space may be determined again in the following. For 2498 // ParaAttribsChanged the paragraph is unfortunately formatted again, 2499 // however this method should not be time critical! 2500 if ( pNextNode ) 2501 ParaAttribsChanged( pNextNode ); 2502 2503 if ( IsUndoEnabled() && !IsInUndo() ) 2504 InsertUndo(std::make_unique<EditUndoDelContent>(pEditEngine, pNode, nPara)); 2505 else 2506 { 2507 aEditDoc.RemoveItemsFromPool(*pNode); 2508 if ( pNode->GetStyleSheet() ) 2509 EndListening( *pNode->GetStyleSheet() ); 2510 delete pNode; 2511 } 2512 } 2513 2514 EditPaM ImpEditEngine::AutoCorrect( const EditSelection& rCurSel, sal_Unicode c, 2515 bool bOverwrite, vcl::Window const * pFrameWin ) 2516 { 2517 // i.e. Calc has special needs regarding a leading single quotation mark 2518 // when starting cell input. 2519 if (c == '\'' && !IsReplaceLeadingSingleQuotationMark() && 2520 rCurSel.Min() == rCurSel.Max() && rCurSel.Max().GetIndex() == 0) 2521 { 2522 return InsertTextUserInput( rCurSel, c, bOverwrite ); 2523 } 2524 2525 EditSelection aSel( rCurSel ); 2526 SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect(); 2527 if ( pAutoCorrect ) 2528 { 2529 if ( aSel.HasRange() ) 2530 aSel = ImpDeleteSelection( rCurSel ); 2531 2532 // #i78661 allow application to turn off capitalization of 2533 // start sentence explicitly. 2534 // (This is done by setting IsFirstWordCapitalization to sal_False.) 2535 bool bOldCapitalStartSentence = pAutoCorrect->IsAutoCorrFlag( ACFlags::CapitalStartSentence ); 2536 if (!IsFirstWordCapitalization()) 2537 { 2538 ESelection aESel( CreateESel(aSel) ); 2539 EditSelection aFirstWordSel; 2540 EditSelection aSecondWordSel; 2541 if (aESel.nEndPara == 0) // is this the first para? 2542 { 2543 // select first word... 2544 // start by checking if para starts with word. 2545 aFirstWordSel = SelectWord( CreateSel(ESelection()) ); 2546 if (aFirstWordSel.Min().GetIndex() == 0 && aFirstWordSel.Max().GetIndex() == 0) 2547 { 2548 // para does not start with word -> select next/first word 2549 EditPaM aRightWord( WordRight( aFirstWordSel.Max() ) ); 2550 aFirstWordSel = SelectWord( EditSelection( aRightWord ) ); 2551 } 2552 2553 // select second word 2554 // (sometimes aSel might not point to the end of the first word 2555 // but to some following char like '.'. ':', ... 2556 // In those cases we need aSecondWordSel to see if aSel 2557 // will actually effect the first word.) 2558 EditPaM aRight2Word( WordRight( aFirstWordSel.Max() ) ); 2559 aSecondWordSel = SelectWord( EditSelection( aRight2Word ) ); 2560 } 2561 bool bIsFirstWordInFirstPara = aESel.nEndPara == 0 && 2562 aFirstWordSel.Max().GetIndex() <= aSel.Max().GetIndex() && 2563 aSel.Max().GetIndex() <= aSecondWordSel.Min().GetIndex(); 2564 2565 if (bIsFirstWordInFirstPara) 2566 pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, IsFirstWordCapitalization() ); 2567 } 2568 2569 ContentNode* pNode = aSel.Max().GetNode(); 2570 const sal_Int32 nIndex = aSel.Max().GetIndex(); 2571 EdtAutoCorrDoc aAuto(pEditEngine, pNode, nIndex, c); 2572 // FIXME: this _must_ be called with reference to the actual node text! 2573 OUString const& rNodeString(pNode->GetString()); 2574 pAutoCorrect->DoAutoCorrect( 2575 aAuto, rNodeString, nIndex, c, !bOverwrite, mbNbspRunNext, pFrameWin ); 2576 aSel.Max().SetIndex( aAuto.GetCursor() ); 2577 2578 // #i78661 since the SvxAutoCorrect object used here is 2579 // shared we need to reset the value to its original state. 2580 pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, bOldCapitalStartSentence ); 2581 } 2582 return aSel.Max(); 2583 } 2584 2585 2586 EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel, 2587 sal_Unicode c, bool bOverwrite ) 2588 { 2589 OSL_ENSURE( c != '\t', "Tab for InsertText ?" ); 2590 OSL_ENSURE( c != '\n', "Word wrapping for InsertText ?"); 2591 2592 EditPaM aPaM( rCurSel.Min() ); 2593 2594 bool bDoOverwrite = bOverwrite && 2595 ( aPaM.GetIndex() < aPaM.GetNode()->Len() ); 2596 2597 bool bUndoAction = ( rCurSel.HasRange() || bDoOverwrite ); 2598 2599 if ( bUndoAction ) 2600 UndoActionStart( EDITUNDO_INSERT ); 2601 2602 if ( rCurSel.HasRange() ) 2603 { 2604 aPaM = ImpDeleteSelection( rCurSel ); 2605 } 2606 else if ( bDoOverwrite ) 2607 { 2608 // If selected, then do not also overwrite a character! 2609 EditSelection aTmpSel( aPaM ); 2610 aTmpSel.Max().SetIndex( aTmpSel.Max().GetIndex()+1 ); 2611 OSL_ENSURE( !aTmpSel.DbgIsBuggy( aEditDoc ), "Overwrite: Wrong selection! "); 2612 ImpDeleteSelection( aTmpSel ); 2613 } 2614 2615 if ( aPaM.GetNode()->Len() < MAXCHARSINPARA ) 2616 { 2617 if (IsInputSequenceCheckingRequired( c, rCurSel )) 2618 { 2619 uno::Reference < i18n::XExtendedInputSequenceChecker > _xISC( ImplGetInputSequenceChecker() ); 2620 if (!pCTLOptions) 2621 pCTLOptions.reset( new SvtCTLOptions ); 2622 2623 if (_xISC) 2624 { 2625 const sal_Int32 nTmpPos = aPaM.GetIndex(); 2626 sal_Int16 nCheckMode = pCTLOptions->IsCTLSequenceCheckingRestricted() ? 2627 i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC; 2628 2629 // the text that needs to be checked is only the one 2630 // before the current cursor position 2631 const OUString aOldText( aPaM.GetNode()->Copy(0, nTmpPos) ); 2632 OUString aNewText( aOldText ); 2633 if (pCTLOptions->IsCTLSequenceCheckingTypeAndReplace()) 2634 { 2635 _xISC->correctInputSequence(aNewText, nTmpPos - 1, c, nCheckMode); 2636 2637 // find position of first character that has changed 2638 sal_Int32 nOldLen = aOldText.getLength(); 2639 sal_Int32 nNewLen = aNewText.getLength(); 2640 const sal_Unicode *pOldTxt = aOldText.getStr(); 2641 const sal_Unicode *pNewTxt = aNewText.getStr(); 2642 sal_Int32 nChgPos = 0; 2643 while ( nChgPos < nOldLen && nChgPos < nNewLen && 2644 pOldTxt[nChgPos] == pNewTxt[nChgPos] ) 2645 ++nChgPos; 2646 2647 const OUString aChgText( aNewText.copy( nChgPos ) ); 2648 2649 // select text from first pos to be changed to current pos 2650 EditSelection aSel( EditPaM( aPaM.GetNode(), nChgPos ), aPaM ); 2651 2652 if (!aChgText.isEmpty()) 2653 return InsertText( aSel, aChgText ); // implicitly handles undo 2654 else 2655 return aPaM; 2656 } 2657 else 2658 { 2659 // should the character be ignored (i.e. not get inserted) ? 2660 if (!_xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode )) 2661 return aPaM; // nothing to be done -> no need for undo 2662 } 2663 } 2664 2665 // at this point now we will insert the character 'normally' some lines below... 2666 } 2667 2668 if ( IsUndoEnabled() && !IsInUndo() ) 2669 { 2670 std::unique_ptr<EditUndoInsertChars> pNewUndo(new EditUndoInsertChars(pEditEngine, CreateEPaM(aPaM), OUString(c))); 2671 bool bTryMerge = !bDoOverwrite && ( c != ' ' ); 2672 InsertUndo( std::move(pNewUndo), bTryMerge ); 2673 } 2674 2675 aEditDoc.InsertText( aPaM, OUString(c) ); 2676 ParaPortion& rPortion = FindParaPortion( aPaM.GetNode() ); 2677 rPortion.MarkInvalid( aPaM.GetIndex(), 1 ); 2678 aPaM.SetIndex( aPaM.GetIndex()+1 ); // does not do EditDoc-Method anymore 2679 } 2680 2681 TextModified(); 2682 2683 if ( bUndoAction ) 2684 UndoActionEnd(); 2685 2686 return aPaM; 2687 } 2688 2689 EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUString& rStr) 2690 { 2691 UndoActionStart( EDITUNDO_INSERT ); 2692 2693 EditPaM aPaM; 2694 if ( aCurSel.HasRange() ) 2695 aPaM = ImpDeleteSelection( aCurSel ); 2696 else 2697 aPaM = aCurSel.Max(); 2698 2699 EditPaM aCurPaM( aPaM ); // for the Invalidate 2700 2701 // get word boundaries in order to clear possible WrongList entries 2702 // and invalidate all the necessary text (everything after and including the 2703 // start of the word) 2704 // #i107201# do the expensive SelectWord call only if online spelling is active 2705 EditSelection aCurWord; 2706 if ( GetStatus().DoOnlineSpelling() ) 2707 aCurWord = SelectWord( aCurPaM, i18n::WordType::DICTIONARY_WORD ); 2708 2709 OUString aText(convertLineEnd(rStr, LINEEND_LF)); 2710 if (utl::ConfigManager::IsFuzzing()) //tab expansion performance in editeng is appalling 2711 aText = aText.replaceAll("\t","-"); 2712 SfxVoidItem aTabItem( EE_FEATURE_TAB ); 2713 2714 // Converts to linesep = \n 2715 // Token LINE_SEP query, 2716 // since the MAC-Compiler makes something else from \n ! 2717 2718 sal_Int32 nStart = 0; 2719 while ( nStart < aText.getLength() ) 2720 { 2721 sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart ); 2722 if ( nEnd == -1 ) 2723 nEnd = aText.getLength(); // not dereference! 2724 2725 // Start == End => empty line 2726 if ( nEnd > nStart ) 2727 { 2728 OUString aLine = aText.copy( nStart, nEnd-nStart ); 2729 sal_Int32 nExistingChars = aPaM.GetNode()->Len(); 2730 sal_Int32 nChars = nExistingChars + aLine.getLength(); 2731 if (nChars > MAXCHARSINPARA) 2732 { 2733 sal_Int32 nMaxNewChars = std::max<sal_Int32>(0, MAXCHARSINPARA - nExistingChars); 2734 // Wherever we break, it may be wrong. However, try to find the 2735 // previous non-alnum/non-letter character. Note this is only 2736 // in the to be appended data, otherwise already existing 2737 // characters would have to be moved and PaM to be updated. 2738 // Restrict to 2*42, if not found by then assume other data or 2739 // language-script uses only letters or idiographs. 2740 sal_Int32 nPos = nMaxNewChars; 2741 while (nPos-- > 0 && (nMaxNewChars - nPos) <= 84) 2742 { 2743 switch (unicode::getUnicodeType(aLine[nPos])) 2744 { 2745 case css::i18n::UnicodeType::UPPERCASE_LETTER: 2746 case css::i18n::UnicodeType::LOWERCASE_LETTER: 2747 case css::i18n::UnicodeType::TITLECASE_LETTER: 2748 case css::i18n::UnicodeType::MODIFIER_LETTER: 2749 case css::i18n::UnicodeType::OTHER_LETTER: 2750 case css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER: 2751 case css::i18n::UnicodeType::LETTER_NUMBER: 2752 case css::i18n::UnicodeType::OTHER_NUMBER: 2753 case css::i18n::UnicodeType::CURRENCY_SYMBOL: 2754 break; 2755 default: 2756 { 2757 const sal_Unicode c = aLine[nPos]; 2758 // Ignore NO-BREAK spaces, NBSP, NNBSP, ZWNBSP. 2759 if (c == 0x00A0 || c == 0x202F || c == 0xFEFF) 2760 break; 2761 if (c == '-' && nPos + 1 < nMaxNewChars) 2762 { 2763 // Keep HYPHEN-MINUS with a number to the right. 2764 const sal_Int16 t = unicode::getUnicodeType(aLine[nPos+1]); 2765 if ( t == css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER || 2766 t == css::i18n::UnicodeType::LETTER_NUMBER || 2767 t == css::i18n::UnicodeType::OTHER_NUMBER) 2768 nMaxNewChars = nPos; // line break before 2769 else 2770 nMaxNewChars = nPos + 1; // line break after 2771 } 2772 else 2773 { 2774 nMaxNewChars = nPos + 1; // line break after 2775 } 2776 nPos = 0; // will break loop 2777 } 2778 } 2779 } 2780 // Remaining characters end up in the next paragraph. Note that 2781 // new nStart will be nEnd+1 below so decrement by one more. 2782 nEnd -= (aLine.getLength() - nMaxNewChars + 1); 2783 aLine = aLine.copy( 0, nMaxNewChars ); // Delete the Rest... 2784 } 2785 if ( IsUndoEnabled() && !IsInUndo() ) 2786 InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), aLine)); 2787 // Tabs ? 2788 if ( aLine.indexOf( '\t' ) == -1 ) 2789 aPaM = aEditDoc.InsertText( aPaM, aLine ); 2790 else 2791 { 2792 sal_Int32 nStart2 = 0; 2793 while ( nStart2 < aLine.getLength() ) 2794 { 2795 sal_Int32 nEnd2 = aLine.indexOf( "\t", nStart2 ); 2796 if ( nEnd2 == -1 ) 2797 nEnd2 = aLine.getLength(); // not dereference! 2798 2799 if ( nEnd2 > nStart2 ) 2800 aPaM = aEditDoc.InsertText( aPaM, aLine.copy( nStart2, nEnd2-nStart2 ) ); 2801 if ( nEnd2 < aLine.getLength() ) 2802 { 2803 aPaM = aEditDoc.InsertFeature( aPaM, aTabItem ); 2804 } 2805 nStart2 = nEnd2+1; 2806 } 2807 } 2808 ParaPortion& rPortion = FindParaPortion( aPaM.GetNode() ); 2809 2810 if ( GetStatus().DoOnlineSpelling() ) 2811 { 2812 // now remove the Wrongs (red spell check marks) from both words... 2813 WrongList *pWrongs = aCurPaM.GetNode()->GetWrongList(); 2814 if (pWrongs && !pWrongs->empty()) 2815 pWrongs->ClearWrongs( aCurWord.Min().GetIndex(), aPaM.GetIndex(), aPaM.GetNode() ); 2816 // ... and mark both words as 'to be checked again' 2817 rPortion.MarkInvalid( aCurWord.Min().GetIndex(), aLine.getLength() ); 2818 } 2819 else 2820 rPortion.MarkInvalid( aCurPaM.GetIndex(), aLine.getLength() ); 2821 } 2822 if ( nEnd < aText.getLength() ) 2823 aPaM = ImpInsertParaBreak( aPaM ); 2824 2825 nStart = nEnd+1; 2826 } 2827 2828 UndoActionEnd(); 2829 2830 TextModified(); 2831 return aPaM; 2832 } 2833 2834 EditPaM ImpEditEngine::ImpFastInsertText( EditPaM aPaM, const OUString& rStr ) 2835 { 2836 OSL_ENSURE( rStr.indexOf( 0x0A ) == -1, "FastInsertText: Newline not allowed! "); 2837 OSL_ENSURE( rStr.indexOf( 0x0D ) == -1, "FastInsertText: Newline not allowed! "); 2838 OSL_ENSURE( rStr.indexOf( '\t' ) == -1, "FastInsertText: Newline not allowed! "); 2839 2840 if ( ( aPaM.GetNode()->Len() + rStr.getLength() ) < MAXCHARSINPARA ) 2841 { 2842 if ( IsUndoEnabled() && !IsInUndo() ) 2843 InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), rStr)); 2844 2845 aPaM = aEditDoc.InsertText( aPaM, rStr ); 2846 TextModified(); 2847 } 2848 else 2849 { 2850 aPaM = ImpInsertText( aPaM, rStr ); 2851 } 2852 2853 return aPaM; 2854 } 2855 2856 EditPaM ImpEditEngine::ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem) 2857 { 2858 EditPaM aPaM; 2859 if ( rCurSel.HasRange() ) 2860 aPaM = ImpDeleteSelection( rCurSel ); 2861 else 2862 aPaM = rCurSel.Max(); 2863 2864 if ( aPaM.GetIndex() >= SAL_MAX_INT32-1 ) 2865 return aPaM; 2866 2867 if ( IsUndoEnabled() && !IsInUndo() ) 2868 InsertUndo(std::make_unique<EditUndoInsertFeature>(pEditEngine, CreateEPaM(aPaM), rItem)); 2869 aPaM = aEditDoc.InsertFeature( aPaM, rItem ); 2870 UpdateFields(); 2871 2872 ParaPortion& rPortion = FindParaPortion( aPaM.GetNode() ); 2873 rPortion.MarkInvalid( aPaM.GetIndex()-1, 1 ); 2874 2875 TextModified(); 2876 2877 return aPaM; 2878 } 2879 2880 EditPaM ImpEditEngine::ImpInsertParaBreak( const EditSelection& rCurSel ) 2881 { 2882 EditPaM aPaM; 2883 if ( rCurSel.HasRange() ) 2884 aPaM = ImpDeleteSelection( rCurSel ); 2885 else 2886 aPaM = rCurSel.Max(); 2887 2888 return ImpInsertParaBreak( aPaM ); 2889 } 2890 2891 EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs ) 2892 { 2893 if ( aEditDoc.Count() >= EE_PARA_MAX_COUNT ) 2894 { 2895 SAL_WARN( "editeng", "ImpEditEngine::ImpInsertParaBreak - can't process more than " 2896 << EE_PARA_MAX_COUNT << " paragraphs!"); 2897 return rPaM; 2898 } 2899 2900 if ( IsUndoEnabled() && !IsInUndo() ) 2901 InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, aEditDoc.GetPos(rPaM.GetNode()), rPaM.GetIndex())); 2902 2903 EditPaM aPaM( aEditDoc.InsertParaBreak( rPaM, bKeepEndingAttribs ) ); 2904 2905 if ( GetStatus().DoOnlineSpelling() ) 2906 { 2907 sal_Int32 nEnd = rPaM.GetNode()->Len(); 2908 aPaM.GetNode()->CreateWrongList(); 2909 WrongList* pLWrongs = rPaM.GetNode()->GetWrongList(); 2910 WrongList* pRWrongs = aPaM.GetNode()->GetWrongList(); 2911 // take over misspelled words: 2912 for (auto & elem : *pLWrongs) 2913 { 2914 // Correct only if really a word gets overlapped in the process of 2915 // Spell checking 2916 if (elem.mnStart > o3tl::make_unsigned(nEnd)) 2917 { 2918 pRWrongs->push_back(elem); 2919 editeng::MisspellRange& rRWrong = pRWrongs->back(); 2920 rRWrong.mnStart = rRWrong.mnStart - nEnd; 2921 rRWrong.mnEnd = rRWrong.mnEnd - nEnd; 2922 } 2923 else if (elem.mnStart < o3tl::make_unsigned(nEnd) && elem.mnEnd > o3tl::make_unsigned(nEnd)) 2924 elem.mnEnd = nEnd; 2925 } 2926 sal_Int32 nInv = nEnd ? nEnd-1 : nEnd; 2927 if ( nEnd ) 2928 pLWrongs->SetInvalidRange(nInv, nEnd); 2929 else 2930 pLWrongs->SetValid(); 2931 pRWrongs->SetValid(); 2932 pRWrongs->SetInvalidRange(0, 1); // Only test the first word 2933 } 2934 2935 ParaPortion& rPortion = FindParaPortion( rPaM.GetNode() ); 2936 rPortion.MarkInvalid( rPaM.GetIndex(), 0 ); 2937 2938 // Optimization: Do not place unnecessarily many getPos to Listen! 2939 // Here, as in undo, but also in all other methods. 2940 sal_Int32 nPos = GetParaPortions().GetPos( &rPortion ); 2941 ParaPortion& rNewPortion = GetParaPortions().Insert(nPos+1, ParaPortion(aPaM.GetNode())); 2942 ParaAttribsChanged( rNewPortion.GetNode() ); 2943 if ( IsCallParaInsertedOrDeleted() ) 2944 GetEditEnginePtr()->ParagraphInserted( nPos+1 ); 2945 2946 CursorMoved( rPaM.GetNode() ); // if empty Attributes have emerged. 2947 TextModified(); 2948 return aPaM; 2949 } 2950 2951 EditPaM ImpEditEngine::ImpFastInsertParagraph( sal_Int32 nPara ) 2952 { 2953 if ( IsUndoEnabled() && !IsInUndo() ) 2954 { 2955 if ( nPara ) 2956 { 2957 OSL_ENSURE( aEditDoc.GetObject( nPara-1 ), "FastInsertParagraph: Prev does not exist" ); 2958 InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, nPara-1, aEditDoc.GetObject( nPara-1 )->Len())); 2959 } 2960 else 2961 InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, 0, 0)); 2962 } 2963 2964 ContentNode* pNode = new ContentNode( aEditDoc.GetItemPool() ); 2965 // If flat mode, then later no Font is set: 2966 pNode->GetCharAttribs().GetDefFont() = aEditDoc.GetDefFont(); 2967 2968 if ( GetStatus().DoOnlineSpelling() ) 2969 pNode->CreateWrongList(); 2970 2971 aEditDoc.Insert(nPara, pNode); 2972 2973 GetParaPortions().Insert(nPara, ParaPortion( pNode )); 2974 if ( IsCallParaInsertedOrDeleted() ) 2975 GetEditEnginePtr()->ParagraphInserted( nPara ); 2976 2977 return EditPaM( pNode, 0 ); 2978 } 2979 2980 EditPaM ImpEditEngine::InsertParaBreak(const EditSelection& rCurSel) 2981 { 2982 EditPaM aPaM(ImpInsertParaBreak(rCurSel)); 2983 if ( aStatus.DoAutoIndenting() ) 2984 { 2985 sal_Int32 nPara = aEditDoc.GetPos( aPaM.GetNode() ); 2986 OSL_ENSURE( nPara > 0, "AutoIndenting: Error!" ); 2987 const OUString aPrevParaText( GetEditDoc().GetParaAsString( nPara-1 ) ); 2988 sal_Int32 n = 0; 2989 while ( ( n < aPrevParaText.getLength() ) && 2990 ( ( aPrevParaText[n] == ' ' ) || ( aPrevParaText[n] == '\t' ) ) ) 2991 { 2992 if ( aPrevParaText[n] == '\t' ) 2993 aPaM = ImpInsertFeature( aPaM, SfxVoidItem( EE_FEATURE_TAB ) ); 2994 else 2995 aPaM = ImpInsertText( aPaM, OUString(aPrevParaText[n]) ); 2996 n++; 2997 } 2998 2999 } 3000 return aPaM; 3001 } 3002 3003 EditPaM ImpEditEngine::InsertTab(const EditSelection& rCurSel) 3004 { 3005 EditPaM aPaM( ImpInsertFeature(rCurSel, SfxVoidItem(EE_FEATURE_TAB ))); 3006 return aPaM; 3007 } 3008 3009 EditPaM ImpEditEngine::InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld) 3010 { 3011 return ImpInsertFeature(rCurSel, rFld); 3012 } 3013 3014 bool ImpEditEngine::UpdateFields() 3015 { 3016 bool bChanges = false; 3017 sal_Int32 nParas = GetEditDoc().Count(); 3018 for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) 3019 { 3020 bool bChangesInPara = false; 3021 ContentNode* pNode = GetEditDoc().GetObject( nPara ); 3022 OSL_ENSURE( pNode, "NULL-Pointer in Doc" ); 3023 CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); 3024 for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs) 3025 { 3026 EditCharAttrib& rAttr = *rAttrib; 3027 if (rAttr.Which() == EE_FEATURE_FIELD) 3028 { 3029 EditCharAttribField& rField = static_cast<EditCharAttribField&>(rAttr); 3030 EditCharAttribField aCurrent(rField); 3031 rField.Reset(); 3032 3033 if (!aStatus.MarkNonUrlFields() && !aStatus.MarkUrlFields()) 3034 ; // nothing marked 3035 else if (aStatus.MarkNonUrlFields() && aStatus.MarkUrlFields()) 3036 rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor; 3037 else 3038 { 3039 bool bURL = false; 3040 if (const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(rField.GetItem())) 3041 { 3042 if (const SvxFieldData* pFieldData = pFieldItem->GetField()) 3043 bURL = (dynamic_cast<const SvxURLField* >(pFieldData) != nullptr); 3044 } 3045 if ((bURL && aStatus.MarkUrlFields()) || (!bURL && aStatus.MarkNonUrlFields())) 3046 rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor; 3047 } 3048 3049 const OUString aFldValue = 3050 GetEditEnginePtr()->CalcFieldValue( 3051 static_cast<const SvxFieldItem&>(*rField.GetItem()), 3052 nPara, rField.GetStart(), rField.GetTextColor(), rField.GetFieldColor()); 3053 3054 rField.SetFieldValue(aFldValue); 3055 if (rField != aCurrent) 3056 { 3057 bChanges = true; 3058 bChangesInPara = true; 3059 } 3060 } 3061 } 3062 if ( bChangesInPara ) 3063 { 3064 // If possible be more precise when invalidate. 3065 ParaPortion& rPortion = GetParaPortions()[nPara]; 3066 rPortion.MarkSelectionInvalid( 0 ); 3067 } 3068 } 3069 return bChanges; 3070 } 3071 3072 EditPaM ImpEditEngine::InsertLineBreak(const EditSelection& aCurSel) 3073 { 3074 EditPaM aPaM( ImpInsertFeature( aCurSel, SfxVoidItem( EE_FEATURE_LINEBR ) ) ); 3075 return aPaM; 3076 } 3077 3078 3079 // Helper functions 3080 3081 tools::Rectangle ImpEditEngine::GetEditCursor(const ParaPortion* pPortion, const EditLine* pLine, 3082 sal_Int32 nIndex, GetCursorFlags nFlags) 3083 { 3084 assert(pPortion && pLine); 3085 // nIndex might be not in the line 3086 // Search within the line... 3087 tools::Long nX; 3088 3089 if ((nIndex == pLine->GetStart()) && (nFlags & GetCursorFlags::StartOfLine)) 3090 { 3091 Range aXRange = GetLineXPosStartEnd(pPortion, pLine); 3092 nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Min() 3093 : aXRange.Max(); 3094 } 3095 else if ((nIndex == pLine->GetEnd()) && (nFlags & GetCursorFlags::EndOfLine)) 3096 { 3097 Range aXRange = GetLineXPosStartEnd(pPortion, pLine); 3098 nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Max() 3099 : aXRange.Min(); 3100 } 3101 else 3102 { 3103 nX = GetXPos(pPortion, pLine, nIndex, bool(nFlags & GetCursorFlags::PreferPortionStart)); 3104 } 3105 3106 tools::Rectangle aEditCursor; 3107 aEditCursor.SetLeft(nX); 3108 aEditCursor.SetRight(nX); 3109 3110 aEditCursor.SetBottom(pLine->GetHeight() - 1); 3111 if (nFlags & GetCursorFlags::TextOnly) 3112 aEditCursor.SetTop(aEditCursor.Bottom() - pLine->GetTxtHeight() + 1); 3113 else 3114 aEditCursor.SetTop(aEditCursor.Bottom() 3115 - std::min(pLine->GetTxtHeight(), pLine->GetHeight()) + 1); 3116 return aEditCursor; 3117 } 3118 3119 tools::Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags ) 3120 { 3121 assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: PaMtoEditCursor" ); 3122 3123 tools::Rectangle aEditCursor; 3124 const sal_Int32 nIndex = aPaM.GetIndex(); 3125 const ParaPortion* pPortion = nullptr; 3126 const EditLine* pLastLine = nullptr; 3127 tools::Rectangle aLineArea; 3128 3129 auto FindPortionLineAndArea 3130 = [&, bEOL(bool(nFlags & GetCursorFlags::EndOfLine))](const LineAreaInfo& rInfo) { 3131 if (!rInfo.pLine) // start of ParaPortion 3132 { 3133 ContentNode* pNode = rInfo.rPortion.GetNode(); 3134 OSL_ENSURE(pNode, "Invalid Node in Portion!"); 3135 if (pNode != aPaM.GetNode()) 3136 return CallbackResult::SkipThisPortion; 3137 pPortion = &rInfo.rPortion; 3138 } 3139 else // guaranteed that this is the correct ParaPortion 3140 { 3141 pLastLine = rInfo.pLine; 3142 aLineArea = rInfo.aArea; 3143 if ((rInfo.pLine->GetStart() == nIndex) || (rInfo.pLine->IsIn(nIndex, bEOL))) 3144 return CallbackResult::Stop; 3145 } 3146 return CallbackResult::Continue; 3147 }; 3148 IterateLineAreas(FindPortionLineAndArea, IterFlag::none); 3149 3150 if (pLastLine) 3151 { 3152 aEditCursor = GetEditCursor(pPortion, pLastLine, nIndex, nFlags); 3153 aEditCursor.Move(getTopLeftDocOffset(aLineArea)); 3154 } 3155 else 3156 OSL_FAIL("Line not found!"); 3157 3158 return aEditCursor; 3159 } 3160 3161 void ImpEditEngine::IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions) 3162 { 3163 const Point aOrigin(0, 0); 3164 Point aLineStart(aOrigin); 3165 const tools::Long nVertLineSpacing = CalcVertLineSpacing(aLineStart); 3166 const tools::Long nColumnWidth = GetColumnWidth(aPaperSize); 3167 sal_Int16 nColumn = 0; 3168 for (sal_Int32 n = 0, nPortions = GetParaPortions().Count(); n < nPortions; ++n) 3169 { 3170 ParaPortion& rPortion = GetParaPortions()[n]; 3171 bool bSkipThis = true; 3172 if (rPortion.IsVisible()) 3173 { 3174 // when typing idle formatting, asynchronous Paint. Invisible Portions may be invalid. 3175 if (rPortion.IsInvalid()) 3176 return; 3177 3178 LineAreaInfo aInfo{ 3179 rPortion, // rPortion 3180 nullptr, // pLine 3181 0, // nHeightNeededToNotWrap 3182 { aLineStart, Size{ nColumnWidth, rPortion.GetFirstLineOffset() } }, // aArea 3183 n, // nPortion 3184 0, // nLine 3185 nColumn // nColumn 3186 }; 3187 auto eResult = f(aInfo); 3188 if (eResult == CallbackResult::Stop) 3189 return; 3190 bSkipThis = eResult == CallbackResult::SkipThisPortion; 3191 3192 sal_uInt16 nSBL = 0; 3193 if (!aStatus.IsOutliner()) 3194 { 3195 const SvxLineSpacingItem& rLSItem 3196 = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); 3197 nSBL = (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix) 3198 ? GetYValue(rLSItem.GetInterLineSpace()) 3199 : 0; 3200 } 3201 3202 adjustYDirectionAware(aLineStart, rPortion.GetFirstLineOffset()); 3203 for (sal_Int32 nLine = 0, nLines = rPortion.GetLines().Count(); nLine < nLines; nLine++) 3204 { 3205 EditLine& rLine = rPortion.GetLines()[nLine]; 3206 tools::Long nLineHeight = rLine.GetHeight(); 3207 if (nLine != nLines - 1) 3208 nLineHeight += nVertLineSpacing; 3209 MoveToNextLine(aLineStart, nLineHeight, nColumn, aOrigin, 3210 &aInfo.nHeightNeededToNotWrap); 3211 const bool bInclILS = eOptions & IterFlag::inclILS; 3212 if (bInclILS && (nLine != nLines - 1) && !aStatus.IsOutliner()) 3213 { 3214 adjustYDirectionAware(aLineStart, nSBL); 3215 nLineHeight += nSBL; 3216 } 3217 3218 if (!bSkipThis) 3219 { 3220 Point aOtherCorner(aLineStart); 3221 adjustXDirectionAware(aOtherCorner, nColumnWidth); 3222 adjustYDirectionAware(aOtherCorner, -nLineHeight); 3223 3224 // Calls to f() for each line 3225 aInfo.nColumn = nColumn; 3226 aInfo.pLine = &rLine; 3227 aInfo.nLine = nLine; 3228 aInfo.aArea = tools::Rectangle::Justify(aLineStart, aOtherCorner); 3229 eResult = f(aInfo); 3230 if (eResult == CallbackResult::Stop) 3231 return; 3232 bSkipThis = eResult == CallbackResult::SkipThisPortion; 3233 } 3234 3235 if (!bInclILS && (nLine != nLines - 1) && !aStatus.IsOutliner()) 3236 adjustYDirectionAware(aLineStart, nSBL); 3237 } 3238 if (!aStatus.IsOutliner()) 3239 { 3240 const SvxULSpaceItem& rULItem 3241 = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); 3242 tools::Long nUL = GetYValue(rULItem.GetLower()); 3243 adjustYDirectionAware(aLineStart, nUL); 3244 } 3245 } 3246 // Invisible ParaPortion has no height (see ParaPortion::GetHeight), don't handle it 3247 } 3248 } 3249 3250 std::tuple<const ParaPortion*, const EditLine*, tools::Long> 3251 ImpEditEngine::GetPortionAndLine(Point aDocPos) 3252 { 3253 // First find the column from the point 3254 sal_Int32 nClickColumn = 0; 3255 for (tools::Long nColumnStart = 0, nColumnWidth = GetColumnWidth(aPaperSize);; 3256 nColumnStart += mnColumnSpacing + nColumnWidth, ++nClickColumn) 3257 { 3258 if (aDocPos.X() <= nColumnStart + nColumnWidth + mnColumnSpacing / 2) 3259 break; 3260 if (nClickColumn >= mnColumns - 1) 3261 break; 3262 } 3263 3264 const ParaPortion* pLastPortion = nullptr; 3265 const EditLine* pLastLine = nullptr; 3266 tools::Long nLineStartX = 0; 3267 Point aPos; 3268 adjustYDirectionAware(aPos, aDocPos.Y()); 3269 3270 auto FindLastMatchingPortionAndLine = [&](const LineAreaInfo& rInfo) { 3271 if (rInfo.pLine) // Only handle lines, not ParaPortion starts 3272 { 3273 if (rInfo.nColumn > nClickColumn) 3274 return CallbackResult::Stop; 3275 pLastPortion = &rInfo.rPortion; // Candidate paragraph 3276 pLastLine = rInfo.pLine; // Last visible line not later than click position 3277 nLineStartX = getTopLeftDocOffset(rInfo.aArea).Width(); 3278 if (rInfo.nColumn == nClickColumn && getYOverflowDirectionAware(aPos, rInfo.aArea) == 0) 3279 return CallbackResult::Stop; // Found it 3280 } 3281 return CallbackResult::Continue; 3282 }; 3283 IterateLineAreas(FindLastMatchingPortionAndLine, IterFlag::inclILS); 3284 3285 return { pLastPortion, pLastLine, nLineStartX }; 3286 } 3287 3288 EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart ) 3289 { 3290 assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: GetPaM" ); 3291 3292 if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(aDocPos); pPortion) 3293 { 3294 sal_Int32 nCurIndex 3295 = GetChar(pPortion, pLine, aDocPos.X() - nLineStartX, bSmart); 3296 EditPaM aPaM(pPortion->GetNode(), nCurIndex); 3297 3298 if (nCurIndex && (nCurIndex == pLine->GetEnd()) 3299 && (pLine != &pPortion->GetLines()[pPortion->GetLines().Count() - 1])) 3300 { 3301 aPaM = CursorLeft(aPaM); 3302 } 3303 3304 return aPaM; 3305 } 3306 return {}; 3307 } 3308 3309 bool ImpEditEngine::IsTextPos(const Point& rDocPos, sal_uInt16 nBorder) 3310 { 3311 if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(rDocPos); pPortion) 3312 { 3313 Range aLineXPosStartEnd = GetLineXPosStartEnd(pPortion, pLine); 3314 if ((rDocPos.X() >= nLineStartX + aLineXPosStartEnd.Min() - nBorder) 3315 && (rDocPos.X() <= nLineStartX + aLineXPosStartEnd.Max() + nBorder)) 3316 return true; 3317 } 3318 return false; 3319 } 3320 3321 sal_uInt32 ImpEditEngine::GetTextHeight() const 3322 { 3323 assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" ); 3324 OSL_ENSURE( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); 3325 return nCurTextHeight; 3326 } 3327 3328 sal_uInt32 ImpEditEngine::CalcTextWidth( bool bIgnoreExtraSpace ) 3329 { 3330 // If still not formatted and not in the process. 3331 // Will be brought in the formatting for AutoPageSize. 3332 if ( !IsFormatted() && !IsFormatting() ) 3333 FormatDoc(); 3334 3335 sal_uInt32 nMaxWidth = 0; 3336 3337 // Over all the paragraphs ... 3338 3339 sal_Int32 nParas = GetParaPortions().Count(); 3340 for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) 3341 { 3342 nMaxWidth = std::max(nMaxWidth, CalcParaWidth(nPara, bIgnoreExtraSpace)); 3343 } 3344 3345 return nMaxWidth; 3346 } 3347 3348 sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace ) 3349 { 3350 // If still not formatted and not in the process. 3351 // Will be brought in the formatting for AutoPageSize. 3352 if ( !IsFormatted() && !IsFormatting() ) 3353 FormatDoc(); 3354 3355 tools::Long nMaxWidth = 0; 3356 3357 // Over all the paragraphs ... 3358 3359 ParaPortion& rPortion = GetParaPortions()[nPara]; 3360 if ( rPortion.IsVisible() ) 3361 { 3362 const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( rPortion.GetNode() ); 3363 sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( rPortion.GetNode() ); 3364 3365 3366 // On the lines of the paragraph ... 3367 3368 sal_Int32 nLines = rPortion.GetLines().Count(); 3369 for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ ) 3370 { 3371 EditLine& rLine = rPortion.GetLines()[nLine]; 3372 // nCurWidth = pLine->GetStartPosX(); 3373 // For Center- or Right- alignment it depends on the paper 3374 // width, here not preferred. I general, it is best not leave it 3375 // to StartPosX, also the right indents have to be taken into 3376 // account! 3377 tools::Long nCurWidth = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth ); 3378 if ( nLine == 0 ) 3379 { 3380 tools::Long nFI = GetXValue( rLRItem.GetTextFirstLineOffset() ); 3381 nCurWidth -= nFI; 3382 if ( rPortion.GetBulletX() > nCurWidth ) 3383 { 3384 nCurWidth += nFI; // LI? 3385 if ( rPortion.GetBulletX() > nCurWidth ) 3386 nCurWidth = rPortion.GetBulletX(); 3387 } 3388 } 3389 nCurWidth += GetXValue( rLRItem.GetRight() ); 3390 nCurWidth += CalcLineWidth( &rPortion, &rLine, bIgnoreExtraSpace ); 3391 if ( nCurWidth > nMaxWidth ) 3392 { 3393 nMaxWidth = nCurWidth; 3394 } 3395 } 3396 } 3397 3398 nMaxWidth++; // widen it, because in CreateLines for >= is wrapped. 3399 return static_cast<sal_uInt32>(nMaxWidth); 3400 } 3401 3402 sal_uInt32 ImpEditEngine::CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace ) 3403 { 3404 sal_Int32 nPara = GetEditDoc().GetPos( pPortion->GetNode() ); 3405 3406 // #114278# Saving both layout mode and language (since I'm 3407 // potentially changing both) 3408 GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE ); 3409 3410 ImplInitLayoutMode(*GetRefDevice(), nPara, -1); 3411 3412 SvxAdjust eJustification = GetJustification( nPara ); 3413 3414 // Calculation of the width without the Indents ... 3415 sal_uInt32 nWidth = 0; 3416 sal_Int32 nPos = pLine->GetStart(); 3417 for ( sal_Int32 nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ ) 3418 { 3419 const TextPortion& rTextPortion = pPortion->GetTextPortions()[nTP]; 3420 switch ( rTextPortion.GetKind() ) 3421 { 3422 case PortionKind::FIELD: 3423 case PortionKind::HYPHENATOR: 3424 case PortionKind::TAB: 3425 { 3426 nWidth += rTextPortion.GetSize().Width(); 3427 } 3428 break; 3429 case PortionKind::TEXT: 3430 { 3431 if ( ( eJustification != SvxAdjust::Block ) || ( !bIgnoreExtraSpace ) ) 3432 { 3433 nWidth += rTextPortion.GetSize().Width(); 3434 } 3435 else 3436 { 3437 SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() ); 3438 SeekCursor( pPortion->GetNode(), nPos+1, aTmpFont ); 3439 aTmpFont.SetPhysFont(*GetRefDevice()); 3440 ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); 3441 nWidth += aTmpFont.QuickGetTextSize( GetRefDevice(), 3442 pPortion->GetNode()->GetString(), nPos, rTextPortion.GetLen(), nullptr, &mGlyphsCache ).Width(); 3443 } 3444 } 3445 break; 3446 case PortionKind::LINEBREAK: break; 3447 } 3448 nPos = nPos + rTextPortion.GetLen(); 3449 } 3450 3451 GetRefDevice()->Pop(); 3452 3453 return nWidth; 3454 } 3455 3456 sal_uInt32 ImpEditEngine::GetTextHeightNTP() const 3457 { 3458 assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" ); 3459 DBG_ASSERT( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); 3460 return nCurTextHeightNTP; 3461 } 3462 3463 tools::Long ImpEditEngine::Calc1ColumnTextHeight(tools::Long* pHeightNTP) 3464 { 3465 tools::Long nHeight = 0; 3466 if (pHeightNTP) 3467 *pHeightNTP = 0; 3468 // Pretend that we have ~infinite height to get total height 3469 comphelper::ValueRestorationGuard aGuard(nCurTextHeight, 3470 std::numeric_limits<tools::Long>::max()); 3471 3472 auto FindLastLineBottom = [&](const LineAreaInfo& rInfo) { 3473 if (rInfo.pLine) 3474 { 3475 // bottom coordinate does not belong to area, so no need to do +1 3476 nHeight = getBottomDocOffset(rInfo.aArea); 3477 if (pHeightNTP && !rInfo.rPortion.IsEmpty()) 3478 *pHeightNTP = nHeight; 3479 } 3480 return CallbackResult::Continue; 3481 }; 3482 IterateLineAreas(FindLastLineBottom, IterFlag::none); 3483 return nHeight; 3484 } 3485 3486 tools::Long ImpEditEngine::CalcTextHeight(tools::Long* pHeightNTP) 3487 { 3488 assert( IsUpdateLayout() && "Should not be used when Update=FALSE: CalcTextHeight" ); 3489 3490 if (mnColumns <= 1) 3491 return Calc1ColumnTextHeight(pHeightNTP); // All text fits into a single column - done! 3492 3493 // The final column height can be smaller than total height divided by number of columns (taking 3494 // into account first line offset and interline spacing, that aren't considered in positioning 3495 // after the wrap). The wrap should only happen after the minimal height is exceeded. 3496 tools::Long nTentativeColHeight = mnMinColumnWrapHeight; 3497 tools::Long nWantedIncrease = 0; 3498 tools::Long nCurrentTextHeight; 3499 3500 // This does the necessary column balancing for the case when the text does not fit min height. 3501 // When the height of column (taken from nCurTextHeight) is too small, the last column will 3502 // overflow, so the resulting height of the text will exceed the set column height. Increasing 3503 // the column height step by step by the minimal value that allows one of columns to accommodate 3504 // one line more, we finally get to the point where all the text fits. At each iteration, the 3505 // height is only increased, so it's impossible to have infinite layout loops. The found value 3506 // is the global minimum. 3507 // 3508 // E.g., given the following four line heights: 3509 // Line 1: 10; 3510 // Line 2: 12; 3511 // Line 3: 10; 3512 // Line 4: 10; 3513 // number of columns 3, and the minimal paper height of 5, the iterations would be: 3514 // * Tentative column height is set to 5 3515 // <ITERATION 1> 3516 // * Line 1 is attempted to go to column 0. Overflow is 5 => moved to column 1. 3517 // * Line 2 is attempted to go to column 1 after Line 1; overflow is 17 => moved to column 2. 3518 // * Line 3 is attempted to go to column 2 after Line 2; overflow is 17, stays in max column 2. 3519 // * Line 4 goes to column 2 after Line 3. 3520 // * Final iteration columns are: {empty}, {Line 1}, {Line 2, Line 3, Line 4} 3521 // * Total text height is max({0, 10, 32}) == 32 > Tentative column height 5 => NEXT ITERATION 3522 // * Minimal height increase that allows at least one column to accommodate one more line is 3523 // min({5, 17, 17}) = 5. 3524 // * Tentative column height is set to 5 + 5 = 10. 3525 // <ITERATION 2> 3526 // * Line 1 goes to column 0, no overflow. 3527 // * Line 2 is attempted to go to column 0 after Line 1; overflow is 12 => moved to column 1. 3528 // * Line 3 is attempted to go to column 1 after Line 2; overflow is 12 => moved to column 2. 3529 // * Line 4 is attempted to go to column 2 after Line 3; overflow is 10, stays in max column 2. 3530 // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} 3531 // * Total text height is max({10, 12, 20}) == 20 > Tentative column height 10 => NEXT ITERATION 3532 // * Minimal height increase that allows at least one column to accommodate one more line is 3533 // min({12, 12, 10}) = 10. 3534 // * Tentative column height is set to 10 + 10 == 20. 3535 // <ITERATION 3> 3536 // * Line 1 goes to column 0, no overflow. 3537 // * Line 2 is attempted to go to column 0 after Line 1; overflow is 2 => moved to column 1. 3538 // * Line 3 is attempted to go to column 1 after Line 2; overflow is 2 => moved to column 2. 3539 // * Line 4 is attempted to go to column 2 after Line 3; no overflow. 3540 // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} 3541 // * Total text height is max({10, 12, 20}) == 20 == Tentative column height 20 => END. 3542 do 3543 { 3544 nTentativeColHeight += nWantedIncrease; 3545 nWantedIncrease = std::numeric_limits<tools::Long>::max(); 3546 nCurrentTextHeight = 0; 3547 if (pHeightNTP) 3548 *pHeightNTP = 0; 3549 auto GetHeightAndWantedIncrease = [&, minHeight = tools::Long(0), lastCol = sal_Int16(0)]( 3550 const LineAreaInfo& rInfo) mutable { 3551 if (rInfo.pLine) 3552 { 3553 if (lastCol != rInfo.nColumn) 3554 { 3555 minHeight = std::max(nCurrentTextHeight, 3556 minHeight); // total height can't be less than previous columns 3557 nWantedIncrease = std::min(rInfo.nHeightNeededToNotWrap, nWantedIncrease); 3558 lastCol = rInfo.nColumn; 3559 } 3560 // bottom coordinate does not belong to area, so no need to do +1 3561 nCurrentTextHeight = std::max(getBottomDocOffset(rInfo.aArea), minHeight); 3562 if (pHeightNTP) 3563 { 3564 if (rInfo.rPortion.IsEmpty()) 3565 *pHeightNTP = std::max(*pHeightNTP, minHeight); 3566 else 3567 *pHeightNTP = nCurrentTextHeight; 3568 } 3569 } 3570 return CallbackResult::Continue; 3571 }; 3572 comphelper::ValueRestorationGuard aGuard(nCurTextHeight, nTentativeColHeight); 3573 IterateLineAreas(GetHeightAndWantedIncrease, IterFlag::none); 3574 } while (nCurrentTextHeight > nTentativeColHeight && nWantedIncrease > 0 3575 && nWantedIncrease != std::numeric_limits<tools::Long>::max()); 3576 return nCurrentTextHeight; 3577 } 3578 3579 sal_Int32 ImpEditEngine::GetLineCount( sal_Int32 nParagraph ) const 3580 { 3581 OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); 3582 const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); 3583 OSL_ENSURE( pPPortion, "Paragraph not found: GetLineCount" ); 3584 if ( pPPortion ) 3585 return pPPortion->GetLines().Count(); 3586 3587 return -1; 3588 } 3589 3590 sal_Int32 ImpEditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const 3591 { 3592 OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineLen: Out of range" ); 3593 const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); 3594 OSL_ENSURE( pPPortion, "Paragraph not found: GetLineLen" ); 3595 if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) 3596 { 3597 const EditLine& rLine = pPPortion->GetLines()[nLine]; 3598 return rLine.GetLen(); 3599 } 3600 3601 return -1; 3602 } 3603 3604 void ImpEditEngine::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const 3605 { 3606 OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); 3607 const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); 3608 OSL_ENSURE( pPPortion, "Paragraph not found: GetLineBoundaries" ); 3609 rStart = rEnd = -1; // default values in case of error 3610 if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) 3611 { 3612 const EditLine& rLine = pPPortion->GetLines()[nLine]; 3613 rStart = rLine.GetStart(); 3614 rEnd = rLine.GetEnd(); 3615 } 3616 } 3617 3618 sal_Int32 ImpEditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const 3619 { 3620 sal_Int32 nLineNo = -1; 3621 const ContentNode* pNode = GetEditDoc().GetObject( nPara ); 3622 OSL_ENSURE( pNode, "GetLineNumberAtIndex: invalid paragraph index" ); 3623 if (pNode) 3624 { 3625 // we explicitly allow for the index to point at the character right behind the text 3626 const bool bValidIndex = /*0 <= nIndex &&*/ nIndex <= pNode->Len(); 3627 OSL_ENSURE( bValidIndex, "GetLineNumberAtIndex: invalid index" ); 3628 const sal_Int32 nLineCount = GetLineCount( nPara ); 3629 if (nIndex == pNode->Len()) 3630 nLineNo = nLineCount > 0 ? nLineCount - 1 : 0; 3631 else if (bValidIndex) // nIndex < pNode->Len() 3632 { 3633 sal_Int32 nStart = -1, nEnd = -1; 3634 for (sal_Int32 i = 0; i < nLineCount && nLineNo == -1; ++i) 3635 { 3636 GetLineBoundaries( nStart, nEnd, nPara, i ); 3637 if (nStart >= 0 && nStart <= nIndex && nEnd >= 0 && nIndex < nEnd) 3638 nLineNo = i; 3639 } 3640 } 3641 } 3642 return nLineNo; 3643 } 3644 3645 sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine ) 3646 { 3647 OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); 3648 ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); 3649 OSL_ENSURE( pPPortion, "Paragraph not found: GetLineHeight" ); 3650 if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) 3651 { 3652 const EditLine& rLine = pPPortion->GetLines()[nLine]; 3653 return rLine.GetHeight(); 3654 } 3655 3656 return 0xFFFF; 3657 } 3658 3659 sal_uInt32 ImpEditEngine::GetParaHeight( sal_Int32 nParagraph ) 3660 { 3661 sal_uInt32 nHeight = 0; 3662 3663 ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); 3664 OSL_ENSURE( pPPortion, "Paragraph not found: GetParaHeight" ); 3665 3666 if ( pPPortion ) 3667 nHeight = pPPortion->GetHeight(); 3668 3669 return nHeight; 3670 } 3671 3672 void ImpEditEngine::UpdateSelections() 3673 { 3674 // Check whether one of the selections is at a deleted node... 3675 // If the node is valid, the index has yet to be examined! 3676 for (EditView* pView : aEditViews) 3677 { 3678 EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); 3679 bool bChanged = false; 3680 for (const std::unique_ptr<DeletedNodeInfo> & aDeletedNode : aDeletedNodes) 3681 { 3682 const DeletedNodeInfo& rInf = *aDeletedNode; 3683 if ( ( aCurSel.Min().GetNode() == rInf.GetNode() ) || 3684 ( aCurSel.Max().GetNode() == rInf.GetNode() ) ) 3685 { 3686 // Use ParaPortions, as now also hidden paragraphs have to be 3687 // taken into account! 3688 sal_Int32 nPara = rInf.GetPosition(); 3689 if (!GetParaPortions().SafeGetObject(nPara)) // Last paragraph 3690 { 3691 nPara = GetParaPortions().Count()-1; 3692 } 3693 // Do not end up from a hidden paragraph: 3694 sal_Int32 nCurPara = nPara; 3695 sal_Int32 nLastPara = GetParaPortions().Count()-1; 3696 while ( nPara <= nLastPara && !GetParaPortions()[nPara].IsVisible() ) 3697 nPara++; 3698 if ( nPara > nLastPara ) // then also backwards ... 3699 { 3700 nPara = nCurPara; 3701 while ( nPara && !GetParaPortions()[nPara].IsVisible() ) 3702 nPara--; 3703 } 3704 OSL_ENSURE( GetParaPortions()[nPara].IsVisible(), "No visible paragraph found: UpdateSelections" ); 3705 3706 ParaPortion& rParaPortion = GetParaPortions()[nPara]; 3707 EditSelection aTmpSelection( EditPaM( rParaPortion.GetNode(), 0 ) ); 3708 pView->pImpEditView->SetEditSelection( aTmpSelection ); 3709 bChanged=true; 3710 break; // for loop 3711 } 3712 } 3713 if ( !bChanged ) 3714 { 3715 // Check Index if node shrunk. 3716 if ( aCurSel.Min().GetIndex() > aCurSel.Min().GetNode()->Len() ) 3717 { 3718 aCurSel.Min().SetIndex( aCurSel.Min().GetNode()->Len() ); 3719 pView->pImpEditView->SetEditSelection( aCurSel ); 3720 } 3721 if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() ) 3722 { 3723 aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() ); 3724 pView->pImpEditView->SetEditSelection( aCurSel ); 3725 } 3726 } 3727 } 3728 aDeletedNodes.clear(); 3729 } 3730 3731 EditSelection ImpEditEngine::ConvertSelection( 3732 sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos ) 3733 { 3734 EditSelection aNewSelection; 3735 3736 // Start... 3737 ContentNode* pNode = aEditDoc.GetObject( nStartPara ); 3738 sal_Int32 nIndex = nStartPos; 3739 if ( !pNode ) 3740 { 3741 pNode = aEditDoc[ aEditDoc.Count()-1 ]; 3742 nIndex = pNode->Len(); 3743 } 3744 else if ( nIndex > pNode->Len() ) 3745 nIndex = pNode->Len(); 3746 3747 aNewSelection.Min().SetNode( pNode ); 3748 aNewSelection.Min().SetIndex( nIndex ); 3749 3750 // End... 3751 pNode = aEditDoc.GetObject( nEndPara ); 3752 nIndex = nEndPos; 3753 if ( !pNode ) 3754 { 3755 pNode = aEditDoc[ aEditDoc.Count()-1 ]; 3756 nIndex = pNode->Len(); 3757 } 3758 else if ( nIndex > pNode->Len() ) 3759 nIndex = pNode->Len(); 3760 3761 aNewSelection.Max().SetNode( pNode ); 3762 aNewSelection.Max().SetIndex( nIndex ); 3763 3764 return aNewSelection; 3765 } 3766 3767 void ImpEditEngine::SetActiveView( EditView* pView ) 3768 { 3769 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 3770 // Actually, now bHasVisSel and HideSelection would be necessary !!! 3771 3772 if ( pView == pActiveView ) 3773 return; 3774 3775 if ( pActiveView && pActiveView->HasSelection() ) 3776 pActiveView->pImpEditView->DrawSelectionXOR(); 3777 3778 pActiveView = pView; 3779 3780 if ( pActiveView && pActiveView->HasSelection() ) 3781 pActiveView->pImpEditView->DrawSelectionXOR(); 3782 3783 // NN: Quick fix for #78668#: 3784 // When editing of a cell in Calc is ended, the edit engine is not deleted, 3785 // only the edit views are removed. If mpIMEInfos is still set in that case, 3786 // mpIMEInfos->aPos points to an invalid selection. 3787 // -> reset mpIMEInfos now 3788 // (probably something like this is necessary whenever the content is modified 3789 // from the outside) 3790 3791 if ( !pView && mpIMEInfos ) 3792 { 3793 mpIMEInfos.reset(); 3794 } 3795 } 3796 3797 uno::Reference< datatransfer::XTransferable > ImpEditEngine::CreateTransferable( const EditSelection& rSelection ) 3798 { 3799 EditSelection aSelection( rSelection ); 3800 aSelection.Adjust( GetEditDoc() ); 3801 3802 rtl::Reference<EditDataObject> pDataObj = new EditDataObject; 3803 3804 pDataObj->GetString() = convertLineEnd(GetSelected(aSelection), GetSystemLineEnd()); // System specific 3805 3806 WriteRTF( pDataObj->GetRTFStream(), aSelection ); 3807 pDataObj->GetRTFStream().Seek( 0 ); 3808 3809 WriteXML( pDataObj->GetODFStream(), aSelection ); 3810 pDataObj->GetODFStream().Seek( 0 ); 3811 3812 //Dumping the ODFStream to a XML file for testing purpose 3813 /* 3814 std::filebuf afilebuf; 3815 afilebuf.open ("gsoc17_clipboard_test.xml",std::ios::out); 3816 std::ostream os(&afilebuf); 3817 os.write((const char*)(pDataObj->GetODFStream().GetData()), pDataObj->GetODFStream().remainingSize()); 3818 afilebuf.close(); 3819 */ 3820 //dumping ends 3821 3822 if ( ( aSelection.Min().GetNode() == aSelection.Max().GetNode() ) 3823 && ( aSelection.Max().GetIndex() == (aSelection.Min().GetIndex()+1) ) ) 3824 { 3825 const EditCharAttrib* pAttr = aSelection.Min().GetNode()->GetCharAttribs(). 3826 FindFeature( aSelection.Min().GetIndex() ); 3827 if ( pAttr && 3828 ( pAttr->GetStart() == aSelection.Min().GetIndex() ) && 3829 ( pAttr->Which() == EE_FEATURE_FIELD ) ) 3830 { 3831 const SvxFieldItem* pField = static_cast<const SvxFieldItem*>(pAttr->GetItem()); 3832 const SvxFieldData* pFld = pField->GetField(); 3833 if ( auto pUrlField = dynamic_cast<const SvxURLField* >(pFld) ) 3834 { 3835 // Office-Bookmark 3836 pDataObj->GetURL() = pUrlField->GetURL(); 3837 } 3838 } 3839 } 3840 3841 return pDataObj; 3842 } 3843 3844 EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial ) 3845 { 3846 EditSelection aNewSelection( rPaM ); 3847 3848 if ( !rxDataObj.is() ) 3849 return aNewSelection; 3850 3851 datatransfer::DataFlavor aFlavor; 3852 bool bDone = false; 3853 3854 if ( bUseSpecial ) 3855 { 3856 // XML 3857 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aFlavor ); 3858 if ( rxDataObj->isDataFlavorSupported( aFlavor ) ) 3859 { 3860 try 3861 { 3862 uno::Any aData = rxDataObj->getTransferData( aFlavor ); 3863 uno::Sequence< sal_Int8 > aSeq; 3864 aData >>= aSeq; 3865 { 3866 SvMemoryStream aODFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ ); 3867 aNewSelection = Read( aODFStream, rBaseURL, EETextFormat::Xml, rPaM ); 3868 } 3869 bDone = true; 3870 } 3871 catch( const css::uno::Exception&) 3872 { 3873 TOOLS_WARN_EXCEPTION( "editeng", "Unable to paste EDITENGINE_ODF_TEXT_FLAT" ); 3874 } 3875 } 3876 3877 if ( !bDone ) 3878 { 3879 // RTF 3880 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aFlavor ); 3881 // RICHTEXT 3882 datatransfer::DataFlavor aFlavorRichtext; 3883 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aFlavorRichtext ); 3884 bool bRtfSupported = rxDataObj->isDataFlavorSupported( aFlavor ); 3885 bool bRichtextSupported = rxDataObj->isDataFlavorSupported( aFlavorRichtext ); 3886 if ( bRtfSupported || bRichtextSupported ) 3887 { 3888 if(bRichtextSupported) 3889 { 3890 aFlavor = aFlavorRichtext; 3891 } 3892 try 3893 { 3894 uno::Any aData = rxDataObj->getTransferData( aFlavor ); 3895 uno::Sequence< sal_Int8 > aSeq; 3896 aData >>= aSeq; 3897 { 3898 SvMemoryStream aRTFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ ); 3899 aNewSelection = Read( aRTFStream, rBaseURL, EETextFormat::Rtf, rPaM ); 3900 } 3901 bDone = true; 3902 } 3903 catch( const css::uno::Exception& ) 3904 { 3905 } 3906 } 3907 } 3908 } 3909 if ( !bDone ) 3910 { 3911 SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); 3912 if ( rxDataObj->isDataFlavorSupported( aFlavor ) ) 3913 { 3914 try 3915 { 3916 uno::Any aData = rxDataObj->getTransferData( aFlavor ); 3917 OUString aText; 3918 aData >>= aText; 3919 aNewSelection = ImpInsertText( rPaM, aText ); 3920 } 3921 catch( ... ) 3922 { 3923 ; // #i9286# can happen, even if isDataFlavorSupported returns true... 3924 } 3925 } 3926 } 3927 3928 return aNewSelection; 3929 } 3930 3931 sal_Int32 ImpEditEngine::GetChar( 3932 const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nXPos, bool bSmart) 3933 { 3934 OSL_ENSURE( pLine, "No line received: GetChar" ); 3935 3936 sal_Int32 nChar = -1; 3937 sal_Int32 nCurIndex = pLine->GetStart(); 3938 3939 3940 // Search best matching portion with GetPortionXOffset() 3941 for ( sal_Int32 i = pLine->GetStartPortion(); i <= pLine->GetEndPortion(); i++ ) 3942 { 3943 const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; 3944 tools::Long nXLeft = GetPortionXOffset( pParaPortion, pLine, i ); 3945 tools::Long nXRight = nXLeft + rPortion.GetSize().Width(); 3946 if ( ( nXLeft <= nXPos ) && ( nXRight >= nXPos ) ) 3947 { 3948 nChar = nCurIndex; 3949 3950 // Search within Portion... 3951 3952 // Don't search within special portions... 3953 if ( rPortion.GetKind() != PortionKind::TEXT ) 3954 { 3955 // ...but check on which side 3956 if ( bSmart ) 3957 { 3958 tools::Long nLeftDiff = nXPos-nXLeft; 3959 tools::Long nRightDiff = nXRight-nXPos; 3960 if ( nRightDiff < nLeftDiff ) 3961 nChar++; 3962 } 3963 } 3964 else 3965 { 3966 sal_Int32 nMax = rPortion.GetLen(); 3967 sal_Int32 nOffset = -1; 3968 sal_Int32 nTmpCurIndex = nChar - pLine->GetStart(); 3969 3970 tools::Long nXInPortion = nXPos - nXLeft; 3971 if ( rPortion.IsRightToLeft() ) 3972 nXInPortion = nXRight - nXPos; 3973 3974 // Search in Array... 3975 for ( sal_Int32 x = 0; x < nMax; x++ ) 3976 { 3977 tools::Long nTmpPosMax = pLine->GetCharPosArray()[nTmpCurIndex+x]; 3978 if ( nTmpPosMax > nXInPortion ) 3979 { 3980 // Check whether this or the previous... 3981 tools::Long nTmpPosMin = x ? pLine->GetCharPosArray()[nTmpCurIndex+x-1] : 0; 3982 tools::Long nDiffLeft = nXInPortion - nTmpPosMin; 3983 tools::Long nDiffRight = nTmpPosMax - nXInPortion; 3984 OSL_ENSURE( nDiffLeft >= 0, "DiffLeft negative" ); 3985 OSL_ENSURE( nDiffRight >= 0, "DiffRight negative" ); 3986 nOffset = ( bSmart && ( nDiffRight < nDiffLeft ) ) ? x+1 : x; 3987 // I18N: If there are character position with the length of 0, 3988 // they belong to the same character, we can not use this position as an index. 3989 // Skip all 0-positions, cheaper than using XBreakIterator: 3990 if ( nOffset < nMax ) 3991 { 3992 const tools::Long nX = pLine->GetCharPosArray()[nOffset]; 3993 while ( ( (nOffset+1) < nMax ) && ( pLine->GetCharPosArray()[nOffset+1] == nX ) ) 3994 nOffset++; 3995 } 3996 break; 3997 } 3998 } 3999 4000 // There should not be any inaccuracies when using the 4001 // CharPosArray! Maybe for kerning? 4002 // 0xFFF happens for example for Outline-Font when at the very end. 4003 if ( nOffset < 0 ) 4004 nOffset = nMax; 4005 4006 OSL_ENSURE( nOffset <= nMax, "nOffset > nMax" ); 4007 4008 nChar = nChar + nOffset; 4009 4010 // Check if index is within a cell: 4011 if ( nChar && ( nChar < pParaPortion->GetNode()->Len() ) ) 4012 { 4013 EditPaM aPaM( pParaPortion->GetNode(), nChar+1 ); 4014 sal_uInt16 nScriptType = GetI18NScriptType( aPaM ); 4015 if ( nScriptType == i18n::ScriptType::COMPLEX ) 4016 { 4017 uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 4018 sal_Int32 nCount = 1; 4019 lang::Locale aLocale = GetLocale( aPaM ); 4020 sal_Int32 nRight = _xBI->nextCharacters( 4021 pParaPortion->GetNode()->GetString(), nChar, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); 4022 sal_Int32 nLeft = _xBI->previousCharacters( 4023 pParaPortion->GetNode()->GetString(), nRight, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); 4024 if ( ( nLeft != nChar ) && ( nRight != nChar ) ) 4025 { 4026 nChar = ( std::abs( nRight - nChar ) < std::abs( nLeft - nChar ) ) ? nRight : nLeft; 4027 } 4028 } 4029 else 4030 { 4031 OUString aStr(pParaPortion->GetNode()->GetString()); 4032 // tdf#102625: don't select middle of a pair of surrogates with mouse cursor 4033 if (rtl::isSurrogate(aStr[nChar])) 4034 --nChar; 4035 } 4036 } 4037 } 4038 } 4039 4040 nCurIndex = nCurIndex + rPortion.GetLen(); 4041 } 4042 4043 if ( nChar == -1 ) 4044 { 4045 nChar = ( nXPos <= pLine->GetStartPosX() ) ? pLine->GetStart() : pLine->GetEnd(); 4046 } 4047 4048 return nChar; 4049 } 4050 4051 Range ImpEditEngine::GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const 4052 { 4053 Range aLineXPosStartEnd; 4054 4055 sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); 4056 if ( !IsRightToLeft( nPara ) ) 4057 { 4058 aLineXPosStartEnd.Min() = pLine->GetStartPosX(); 4059 aLineXPosStartEnd.Max() = pLine->GetStartPosX() + pLine->GetTextWidth(); 4060 } 4061 else 4062 { 4063 aLineXPosStartEnd.Min() = GetPaperSize().Width() - ( pLine->GetStartPosX() + pLine->GetTextWidth() ); 4064 aLineXPosStartEnd.Max() = GetPaperSize().Width() - pLine->GetStartPosX(); 4065 } 4066 4067 4068 return aLineXPosStartEnd; 4069 } 4070 4071 tools::Long ImpEditEngine::GetPortionXOffset( 4072 const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const 4073 { 4074 tools::Long nX = pLine->GetStartPosX(); 4075 4076 for ( sal_Int32 i = pLine->GetStartPortion(); i < nTextPortion; i++ ) 4077 { 4078 const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; 4079 switch ( rPortion.GetKind() ) 4080 { 4081 case PortionKind::FIELD: 4082 case PortionKind::TEXT: 4083 case PortionKind::HYPHENATOR: 4084 case PortionKind::TAB: 4085 { 4086 nX += rPortion.GetSize().Width(); 4087 } 4088 break; 4089 case PortionKind::LINEBREAK: break; 4090 } 4091 } 4092 4093 sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); 4094 bool bR2LPara = IsRightToLeft( nPara ); 4095 4096 const TextPortion& rDestPortion = pParaPortion->GetTextPortions()[nTextPortion]; 4097 if ( rDestPortion.GetKind() != PortionKind::TAB ) 4098 { 4099 if ( !bR2LPara && rDestPortion.GetRightToLeftLevel() ) 4100 { 4101 // Portions behind must be added, visual before this portion 4102 sal_Int32 nTmpPortion = nTextPortion+1; 4103 while ( nTmpPortion <= pLine->GetEndPortion() ) 4104 { 4105 const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; 4106 if ( rNextTextPortion.GetRightToLeftLevel() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) 4107 nX += rNextTextPortion.GetSize().Width(); 4108 else 4109 break; 4110 nTmpPortion++; 4111 } 4112 // Portions before must be removed, visual behind this portion 4113 nTmpPortion = nTextPortion; 4114 while ( nTmpPortion > pLine->GetStartPortion() ) 4115 { 4116 --nTmpPortion; 4117 const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; 4118 if ( rPrevTextPortion.GetRightToLeftLevel() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) 4119 nX -= rPrevTextPortion.GetSize().Width(); 4120 else 4121 break; 4122 } 4123 } 4124 else if ( bR2LPara && !rDestPortion.IsRightToLeft() ) 4125 { 4126 // Portions behind must be removed, visual behind this portion 4127 sal_Int32 nTmpPortion = nTextPortion+1; 4128 while ( nTmpPortion <= pLine->GetEndPortion() ) 4129 { 4130 const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; 4131 if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) 4132 nX += rNextTextPortion.GetSize().Width(); 4133 else 4134 break; 4135 nTmpPortion++; 4136 } 4137 // Portions before must be added, visual before this portion 4138 nTmpPortion = nTextPortion; 4139 while ( nTmpPortion > pLine->GetStartPortion() ) 4140 { 4141 --nTmpPortion; 4142 const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; 4143 if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) 4144 nX -= rPrevTextPortion.GetSize().Width(); 4145 else 4146 break; 4147 } 4148 } 4149 } 4150 if ( bR2LPara ) 4151 { 4152 // Switch X positions... 4153 OSL_ENSURE( GetTextRanger() || GetPaperSize().Width(), "GetPortionXOffset - paper size?!" ); 4154 OSL_ENSURE( GetTextRanger() || (nX <= GetPaperSize().Width()), "GetPortionXOffset - position out of paper size!" ); 4155 nX = GetPaperSize().Width() - nX; 4156 nX -= rDestPortion.GetSize().Width(); 4157 } 4158 4159 return nX; 4160 } 4161 4162 tools::Long ImpEditEngine::GetXPos( 4163 const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart) const 4164 { 4165 OSL_ENSURE( pLine, "No line received: GetXPos" ); 4166 OSL_ENSURE( ( nIndex >= pLine->GetStart() ) && ( nIndex <= pLine->GetEnd() ) , "GetXPos has to be called properly!" ); 4167 4168 bool bDoPreferPortionStart = bPreferPortionStart; 4169 // Assure that the portion belongs to this line: 4170 if ( nIndex == pLine->GetStart() ) 4171 bDoPreferPortionStart = true; 4172 else if ( nIndex == pLine->GetEnd() ) 4173 bDoPreferPortionStart = false; 4174 4175 sal_Int32 nTextPortionStart = 0; 4176 sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart ); 4177 4178 OSL_ENSURE( ( nTextPortion >= pLine->GetStartPortion() ) && ( nTextPortion <= pLine->GetEndPortion() ), "GetXPos: Portion not in current line! " ); 4179 4180 const TextPortion& rPortion = pParaPortion->GetTextPortions()[nTextPortion]; 4181 4182 tools::Long nX = GetPortionXOffset( pParaPortion, pLine, nTextPortion ); 4183 4184 // calc text width, portion size may include CJK/CTL spacing... 4185 // But the array might not be init yet, if using text ranger this method is called within CreateLines()... 4186 tools::Long nPortionTextWidth = rPortion.GetSize().Width(); 4187 if ( ( rPortion.GetKind() == PortionKind::TEXT ) && rPortion.GetLen() && !GetTextRanger() ) 4188 nPortionTextWidth = pLine->GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - pLine->GetStart()]; 4189 4190 if ( nTextPortionStart != nIndex ) 4191 { 4192 // Search within portion... 4193 if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) ) 4194 { 4195 // End of Portion 4196 if ( rPortion.GetKind() == PortionKind::TAB ) 4197 { 4198 if ( nTextPortion+1 < pParaPortion->GetTextPortions().Count() ) 4199 { 4200 const TextPortion& rNextPortion = pParaPortion->GetTextPortions()[nTextPortion+1]; 4201 if ( rNextPortion.GetKind() != PortionKind::TAB ) 4202 { 4203 if ( !bPreferPortionStart ) 4204 nX = GetXPos( pParaPortion, pLine, nIndex, true ); 4205 else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) 4206 nX += nPortionTextWidth; 4207 } 4208 } 4209 else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) 4210 { 4211 nX += nPortionTextWidth; 4212 } 4213 } 4214 else if ( !rPortion.IsRightToLeft() ) 4215 { 4216 nX += nPortionTextWidth; 4217 } 4218 } 4219 else if ( rPortion.GetKind() == PortionKind::TEXT ) 4220 { 4221 OSL_ENSURE( nIndex != pLine->GetStart(), "Strange behavior in new GetXPos()" ); 4222 OSL_ENSURE( pLine && !pLine->GetCharPosArray().empty(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" ); 4223 4224 if( !pLine->GetCharPosArray().empty() ) 4225 { 4226 sal_Int32 nPos = nIndex - 1 - pLine->GetStart(); 4227 if (nPos < 0 || nPos >= static_cast<sal_Int32>(pLine->GetCharPosArray().size())) 4228 { 4229 nPos = pLine->GetCharPosArray().size()-1; 4230 OSL_FAIL("svx::ImpEditEngine::GetXPos(), index out of range!"); 4231 } 4232 4233 // old code restored see #i112788 (which leaves #i74188 unfixed again) 4234 tools::Long nPosInPortion = pLine->GetCharPosArray()[nPos]; 4235 4236 if ( !rPortion.IsRightToLeft() ) 4237 { 4238 nX += nPosInPortion; 4239 } 4240 else 4241 { 4242 nX += nPortionTextWidth - nPosInPortion; 4243 } 4244 4245 if ( rPortion.GetExtraInfos() && rPortion.GetExtraInfos()->bCompressed ) 4246 { 4247 nX += rPortion.GetExtraInfos()->nPortionOffsetX; 4248 if ( rPortion.GetExtraInfos()->nAsianCompressionTypes & AsianCompressionFlags::PunctuationRight ) 4249 { 4250 AsianCompressionFlags nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex ) ); 4251 if ( nType == AsianCompressionFlags::PunctuationRight && !pLine->GetCharPosArray().empty() ) 4252 { 4253 sal_Int32 n = nIndex - nTextPortionStart; 4254 const sal_Int32* pDXArray = pLine->GetCharPosArray().data()+( nTextPortionStart-pLine->GetStart() ); 4255 sal_Int32 nCharWidth = ( ( (n+1) < rPortion.GetLen() ) ? pDXArray[n] : rPortion.GetSize().Width() ) 4256 - ( n ? pDXArray[n-1] : 0 ); 4257 if ( (n+1) < rPortion.GetLen() ) 4258 { 4259 // smaller, when char behind is AsianCompressionFlags::PunctuationRight also 4260 nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex+1 ) ); 4261 if ( nType == AsianCompressionFlags::PunctuationRight ) 4262 { 4263 sal_Int32 nNextCharWidth = ( ( (n+2) < rPortion.GetLen() ) ? pDXArray[n+1] : rPortion.GetSize().Width() ) 4264 - pDXArray[n]; 4265 sal_Int32 nCompressed = nNextCharWidth/2; 4266 nCompressed *= rPortion.GetExtraInfos()->nMaxCompression100thPercent; 4267 nCompressed /= 10000; 4268 nCharWidth += nCompressed; 4269 } 4270 } 4271 else 4272 { 4273 nCharWidth *= 2; // last char pos to portion end is only compressed size 4274 } 4275 nX += nCharWidth/2; // 50% compression 4276 } 4277 } 4278 } 4279 } 4280 } 4281 } 4282 else // if ( nIndex == pLine->GetStart() ) 4283 { 4284 if ( rPortion.IsRightToLeft() ) 4285 { 4286 nX += nPortionTextWidth; 4287 } 4288 } 4289 4290 return nX; 4291 } 4292 4293 void ImpEditEngine::CalcHeight( ParaPortion* pPortion ) 4294 { 4295 pPortion->nHeight = 0; 4296 pPortion->nFirstLineOffset = 0; 4297 4298 if ( !pPortion->IsVisible() ) 4299 return; 4300 4301 OSL_ENSURE( pPortion->GetLines().Count(), "Paragraph with no lines in ParaPortion::CalcHeight" ); 4302 for (sal_Int32 nLine = 0; nLine < pPortion->GetLines().Count(); ++nLine) 4303 pPortion->nHeight += pPortion->GetLines()[nLine].GetHeight(); 4304 4305 if ( aStatus.IsOutliner() ) 4306 return; 4307 4308 const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); 4309 const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); 4310 sal_Int32 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; 4311 4312 if ( nSBL ) 4313 { 4314 if ( pPortion->GetLines().Count() > 1 ) 4315 pPortion->nHeight += ( pPortion->GetLines().Count() - 1 ) * nSBL; 4316 if ( aStatus.ULSpaceSummation() ) 4317 pPortion->nHeight += nSBL; 4318 } 4319 4320 sal_Int32 nPortion = GetParaPortions().GetPos( pPortion ); 4321 if ( nPortion ) 4322 { 4323 sal_uInt16 nUpper = GetYValue( rULItem.GetUpper() ); 4324 pPortion->nHeight += nUpper; 4325 pPortion->nFirstLineOffset = nUpper; 4326 } 4327 4328 if ( nPortion != (GetParaPortions().Count()-1) ) 4329 { 4330 pPortion->nHeight += GetYValue( rULItem.GetLower() ); // not in the last 4331 } 4332 4333 4334 if ( !nPortion || aStatus.ULSpaceSummation() ) 4335 return; 4336 4337 ParaPortion* pPrev = GetParaPortions().SafeGetObject( nPortion-1 ); 4338 if (!pPrev) 4339 return; 4340 4341 const SvxULSpaceItem& rPrevULItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); 4342 const SvxLineSpacingItem& rPrevLSItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); 4343 4344 // In relation between WinWord6/Writer3: 4345 // With a proportional line spacing the paragraph spacing is 4346 // also manipulated. 4347 // Only Writer3: Do not add up, but minimum distance. 4348 4349 // check if distance by LineSpacing > Upper: 4350 sal_uInt16 nExtraSpace = GetYValue( lcl_CalcExtraSpace( rLSItem ) ); 4351 if ( nExtraSpace > pPortion->nFirstLineOffset ) 4352 { 4353 // Paragraph becomes 'bigger': 4354 pPortion->nHeight += ( nExtraSpace - pPortion->nFirstLineOffset ); 4355 pPortion->nFirstLineOffset = nExtraSpace; 4356 } 4357 4358 // Determine nFirstLineOffset now f(pNode) => now f(pNode, pPrev): 4359 sal_uInt16 nPrevLower = GetYValue( rPrevULItem.GetLower() ); 4360 4361 // This PrevLower is still in the height of PrevPortion ... 4362 if ( nPrevLower > pPortion->nFirstLineOffset ) 4363 { 4364 // Paragraph is 'small': 4365 pPortion->nHeight -= pPortion->nFirstLineOffset; 4366 pPortion->nFirstLineOffset = 0; 4367 } 4368 else if ( nPrevLower ) 4369 { 4370 // Paragraph becomes 'somewhat smaller': 4371 pPortion->nHeight -= nPrevLower; 4372 pPortion->nFirstLineOffset = 4373 pPortion->nFirstLineOffset - nPrevLower; 4374 } 4375 // I find it not so good, but Writer3 feature: 4376 // Check if distance by LineSpacing > Lower: this value is not 4377 // stuck in the height of PrevPortion. 4378 if ( pPrev->IsInvalid() ) 4379 return; 4380 4381 nExtraSpace = GetYValue( lcl_CalcExtraSpace( rPrevLSItem ) ); 4382 if ( nExtraSpace > nPrevLower ) 4383 { 4384 sal_uInt16 nMoreLower = nExtraSpace - nPrevLower; 4385 // Paragraph becomes 'bigger', 'grows' downwards: 4386 if ( nMoreLower > pPortion->nFirstLineOffset ) 4387 { 4388 pPortion->nHeight += ( nMoreLower - pPortion->nFirstLineOffset ); 4389 pPortion->nFirstLineOffset = nMoreLower; 4390 } 4391 } 4392 } 4393 4394 void ImpEditEngine::SetValidPaperSize( const Size& rNewSz ) 4395 { 4396 aPaperSize = rNewSz; 4397 4398 tools::Long nMinWidth = aStatus.AutoPageWidth() ? aMinAutoPaperSize.Width() : 0; 4399 tools::Long nMaxWidth = aStatus.AutoPageWidth() ? aMaxAutoPaperSize.Width() : 0x7FFFFFFF; 4400 tools::Long nMinHeight = aStatus.AutoPageHeight() ? aMinAutoPaperSize.Height() : 0; 4401 tools::Long nMaxHeight = aStatus.AutoPageHeight() ? aMaxAutoPaperSize.Height() : 0x7FFFFFFF; 4402 4403 // Minimum/Maximum width: 4404 if ( aPaperSize.Width() < nMinWidth ) 4405 aPaperSize.setWidth( nMinWidth ); 4406 else if ( aPaperSize.Width() > nMaxWidth ) 4407 aPaperSize.setWidth( nMaxWidth ); 4408 4409 // Minimum/Maximum height: 4410 if ( aPaperSize.Height() < nMinHeight ) 4411 aPaperSize.setHeight( nMinHeight ); 4412 else if ( aPaperSize.Height() > nMaxHeight ) 4413 aPaperSize.setHeight( nMaxHeight ); 4414 } 4415 4416 std::shared_ptr<SvxForbiddenCharactersTable> const & ImpEditEngine::GetForbiddenCharsTable() 4417 { 4418 return EditDLL::Get().GetGlobalData()->GetForbiddenCharsTable(); 4419 } 4420 4421 void ImpEditEngine::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars) 4422 { 4423 EditDLL::Get().GetGlobalData()->SetForbiddenCharsTable( xForbiddenChars ); 4424 } 4425 4426 bool ImpEditEngine::IsVisualCursorTravelingEnabled() 4427 { 4428 bool bVisualCursorTravaling = false; 4429 4430 if( !pCTLOptions ) 4431 pCTLOptions.reset( new SvtCTLOptions ); 4432 4433 if ( pCTLOptions->IsCTLFontEnabled() && ( pCTLOptions->GetCTLCursorMovement() == SvtCTLOptions::MOVEMENT_VISUAL ) ) 4434 { 4435 bVisualCursorTravaling = true; 4436 } 4437 4438 return bVisualCursorTravaling; 4439 4440 } 4441 4442 bool ImpEditEngine::DoVisualCursorTraveling() 4443 { 4444 // Don't check if it's necessary, because we also need it when leaving the paragraph 4445 return IsVisualCursorTravelingEnabled(); 4446 } 4447 4448 IMPL_LINK_NOARG(ImpEditEngine, DocModified, LinkParamNone*, void) 4449 { 4450 aModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner 4451 } 4452 4453 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 4454
