diff options
Diffstat (limited to 'editeng/source/editeng/impedit2.cxx')
-rw-r--r-- | editeng/source/editeng/impedit2.cxx | 2065 |
1 files changed, 1146 insertions, 919 deletions
diff --git a/editeng/source/editeng/impedit2.cxx b/editeng/source/editeng/impedit2.cxx index 8ea35eb3e91f..da4d851b3315 100644 --- a/editeng/source/editeng/impedit2.cxx +++ b/editeng/source/editeng/impedit2.cxx @@ -30,6 +30,8 @@ #include "eeobj.hxx" #include <editeng/txtrange.hxx> #include <sfx2/app.hxx> +#include <sfx2/mieclip.hxx> +#include <sfx2/viewsh.hxx> #include <svtools/colorcfg.hxx> #include <svl/ctloptions.hxx> #include <unotools/securityoptions.hxx> @@ -39,6 +41,7 @@ #include <editeng/adjustitem.hxx> #include <editeng/frmdiritem.hxx> #include <editeng/justifyitem.hxx> +#include <editeng/udlnitem.hxx> #include <com/sun/star/i18n/CharacterIteratorMode.hpp> #include <com/sun/star/i18n/WordType.hpp> @@ -48,6 +51,7 @@ #include <com/sun/star/system/SystemShellExecute.hpp> #include <com/sun/star/system/SystemShellExecuteFlags.hpp> #include <com/sun/star/system/XSystemShellExecute.hpp> +#include <com/sun/star/i18n/UnicodeType.hpp> #include <rtl/character.hxx> @@ -57,14 +61,17 @@ #include <sot/exchange.hxx> #include <sot/formats.hxx> #include <svl/asiancfg.hxx> +#include <svl/voiditem.hxx> #include <i18nutil/unicode.hxx> -#include <tools/diagnose_ex.h> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/flagguard.hxx> #include <comphelper/lok.hxx> #include <comphelper/processfactory.hxx> -#include <unotools/configmgr.hxx> +#include <comphelper/configuration.hxx> #include <unicode/ubidi.h> #include <algorithm> +#include <limits> #include <memory> #include <string_view> #include <fstream> @@ -82,70 +89,71 @@ static sal_uInt16 lcl_CalcExtraSpace( const SvxLineSpacingItem& rLSItem ) return nExtra; } +constexpr tools::Long constMaxPaperSize = 0x7FFFFFFF; + ImpEditEngine::ImpEditEngine( EditEngine* pEE, SfxItemPool* pItemPool ) : pSharedVCL(EditDLL::Get().GetSharedVclResources()), - aPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), - aMinAutoPaperSize( 0x0, 0x0 ), - aMaxAutoPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), - aEditDoc( pItemPool ), - pEditEngine(pEE), - pActiveView(nullptr), - pStylePool(nullptr), - pTextObjectPool(nullptr), - pUndoManager(nullptr), - aWordDelimiters(" .,;:-`'?!_=\"{}()[]"), + maPaperSize(constMaxPaperSize, constMaxPaperSize), + maMinAutoPaperSize(0, 0), + maMaxAutoPaperSize(constMaxPaperSize, constMaxPaperSize), + maEditDoc( pItemPool ), + mpEditEngine(pEE), + mpActiveView(nullptr), + mpStylePool(nullptr), + mpTextObjectPool(nullptr), + mpUndoManager(nullptr), + maWordDelimiters(" .,;:-`'?!_=\"{}()[]"), maBackgroundColor(COL_AUTO), - nStretchX(100), - nStretchY(100), - nAsianCompressionMode(CharCompressType::NONE), - eDefaultHorizontalTextDirection(EEHorizontalTextDirection::Default), - nBigTextObjectStart(20), - eDefLanguage(LANGUAGE_DONTKNOW), - nCurTextHeight(0), - nCurTextHeightNTP(0), - bKernAsianPunctuation(false), - bAddExtLeading(false), - bIsFormatting(false), - bFormatted(false), - bInSelection(false), - bIsInUndo(false), - bUpdate(true), - bUndoEnabled(true), - bDowning(false), - bUseAutoColor(true), - bForceAutoColor(false), - bCallParaInsertedOrDeleted(false), - bFirstWordCapitalization(true), + mbRoundToNearestPt(false), + mnAsianCompressionMode(CharCompressType::NONE), + meDefaultHorizontalTextDirection(EEHorizontalTextDirection::Default), + mnBigTextObjectStart(20), + meDefLanguage(LANGUAGE_DONTKNOW), + mnCurTextHeight(0), + mnCurTextHeightNTP(0), + maOnlineSpellTimer("editeng::ImpEditEngine aOnlineSpellTimer"), + maStatusTimer("editeng::ImpEditEngine aStatusTimer"), + mbKernAsianPunctuation(false), + mbAddExtLeading(false), + mbIsFormatting(false), + mbFormatted(false), + mbInSelection(false), + mbIsInUndo(false), + mbUpdateLayout(true), + mbUndoEnabled(true), + mbDowning(false), + mbUseAutoColor(true), + mbForceAutoColor(false), + mbCallParaInsertedOrDeleted(false), + mbFirstWordCapitalization(true), mbLastTryMerge(false), mbReplaceLeadingSingleQuotationMark(true), + mbSkipOutsideFormat(false), + mbFuzzing(comphelper::IsFuzzing()), mbNbspRunNext(false) { - aStatus.GetControlWord() = EEControlBits::USECHARATTRIBS | EEControlBits::DOIDLEFORMAT | + maStatus.GetControlWord() = EEControlBits::USECHARATTRIBS | EEControlBits::DOIDLEFORMAT | EEControlBits::PASTESPECIAL | EEControlBits::UNDOATTRIBS | - EEControlBits::ALLOWBIGOBJS | EEControlBits::RTFSTYLESHEETS | - EEControlBits::FORMAT100; + EEControlBits::ALLOWBIGOBJS | EEControlBits::RTFSTYLESHEETS; - aSelEngine.SetFunctionSet( &aSelFuncSet ); + maSelEngine.SetFunctionSet(&maSelFuncSet); - aStatusTimer.SetTimeout( 200 ); - aStatusTimer.SetInvokeHandler( LINK( this, ImpEditEngine, StatusTimerHdl ) ); - aStatusTimer.SetDebugName( "editeng::ImpEditEngine aStatusTimer" ); + maStatusTimer.SetTimeout(200); + maStatusTimer.SetInvokeHandler(LINK(this, ImpEditEngine, StatusTimerHdl)); - aIdleFormatter.SetPriority( TaskPriority::REPAINT ); - aIdleFormatter.SetInvokeHandler( LINK( this, ImpEditEngine, IdleFormatHdl ) ); - aIdleFormatter.SetDebugName( "editeng::ImpEditEngine aIdleFormatter" ); + maIdleFormatter.SetPriority(TaskPriority::REPAINT); + maIdleFormatter.SetInvokeHandler(LINK(this, ImpEditEngine, IdleFormatHdl)); - aOnlineSpellTimer.SetTimeout( 100 ); - aOnlineSpellTimer.SetInvokeHandler( LINK( this, ImpEditEngine, OnlineSpellHdl ) ); - aOnlineSpellTimer.SetDebugName( "editeng::ImpEditEngine aOnlineSpellTimer" ); + maOnlineSpellTimer.SetTimeout(100); + maOnlineSpellTimer.SetInvokeHandler(LINK( this, ImpEditEngine, OnlineSpellHdl)); // Access data already from here on! SetRefDevice( nullptr ); InitDoc( false ); - bCallParaInsertedOrDeleted = true; + mbCallParaInsertedOrDeleted = true; - aEditDoc.SetModifyHdl( LINK( this, ImpEditEngine, DocModified ) ); + maEditDoc.SetModifyHdl( LINK( this, ImpEditEngine, DocModified ) ); StartListening(*SfxGetpApp()); } @@ -155,43 +163,42 @@ void ImpEditEngine::Dispose() auto pApp = SfxApplication::Get(); if(pApp) EndListening(*pApp); - pVirtDev.disposeAndClear(); + mpVirtDev.disposeAndClear(); mpOwnDev.disposeAndClear(); pSharedVCL.reset(); } ImpEditEngine::~ImpEditEngine() { - aStatusTimer.Stop(); - aOnlineSpellTimer.Stop(); - aIdleFormatter.Stop(); + maStatusTimer.Stop(); + maOnlineSpellTimer.Stop(); + maIdleFormatter.Stop(); // Destroying templates may otherwise cause unnecessary formatting, // when a parent template is destroyed. // And this after the destruction of the data! - bDowning = true; - SetUpdateMode( false ); + mbDowning = true; + SetUpdateLayout( false ); Dispose(); - // it's only legal to delete the pUndoManager if it was created by + // it's only legal to delete the mpUndoManager if it was created by // ImpEditEngine; if it was set by SetUndoManager() it must be cleared // before destroying the ImpEditEngine! - assert(!pUndoManager || typeid(*pUndoManager) == typeid(EditUndoManager)); - delete pUndoManager; - pTextRanger.reset(); + assert(!mpUndoManager || typeid(*mpUndoManager) == typeid(EditUndoManager)); + delete mpUndoManager; + mpTextRanger.reset(); mpIMEInfos.reset(); - pCTLOptions.reset(); - pSpellInfo.reset(); + mpSpellInfo.reset(); } -void ImpEditEngine::SetRefDevice( OutputDevice* pRef ) +void ImpEditEngine::SetRefDevice(OutputDevice* pRef) { if (pRef) - pRefDev = pRef; + mpRefDev = pRef; else - pRefDev = pSharedVCL->GetVirtualDevice(); + mpRefDev = pSharedVCL->GetVirtualDevice(); - nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width()); + mnOnePixelInRef = static_cast<sal_uInt16>(mpRefDev->PixelToLogic( Size( 1, 0 ) ).Width()); if ( IsFormatted() ) { @@ -207,12 +214,12 @@ void ImpEditEngine::SetRefMapMode( const MapMode& rMapMode ) mpOwnDev.disposeAndClear(); mpOwnDev = VclPtr<VirtualDevice>::Create(); - pRefDev = mpOwnDev; - pRefDev->SetMapMode(MapMode(MapUnit::MapTwip)); - SetRefDevice( pRefDev ); + mpRefDev = mpOwnDev; + mpRefDev->SetMapMode(MapMode(MapUnit::MapTwip)); + SetRefDevice(mpRefDev); - pRefDev->SetMapMode( rMapMode ); - nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width()); + mpRefDev->SetMapMode( rMapMode ); + mnOnePixelInRef = static_cast<sal_uInt16>(mpRefDev->PixelToLogic(Size(1, 0)).Width()); if ( IsFormatted() ) { FormatFullDoc(); @@ -222,23 +229,23 @@ void ImpEditEngine::SetRefMapMode( const MapMode& rMapMode ) void ImpEditEngine::InitDoc(bool bKeepParaAttribs) { - sal_Int32 nParas = aEditDoc.Count(); + sal_Int32 nParas = maEditDoc.Count(); for ( sal_Int32 n = bKeepParaAttribs ? 1 : 0; n < nParas; n++ ) { - if ( aEditDoc[n]->GetStyleSheet() ) - EndListening( *aEditDoc[n]->GetStyleSheet() ); + if (maEditDoc.GetObject(n)->GetStyleSheet()) + EndListening( *maEditDoc.GetObject(n)->GetStyleSheet() ); } if ( bKeepParaAttribs ) - aEditDoc.RemoveText(); + maEditDoc.RemoveText(); else - aEditDoc.Clear(); + maEditDoc.Clear(); GetParaPortions().Reset(); - GetParaPortions().Insert(0, std::make_unique<ParaPortion>( aEditDoc[0] )); + GetParaPortions().Insert(0, std::make_unique<ParaPortion>(maEditDoc.GetObject(0))); - bFormatted = false; + mbFormatted = false; if ( IsCallParaInsertedOrDeleted() ) { @@ -247,7 +254,7 @@ void ImpEditEngine::InitDoc(bool bKeepParaAttribs) } if ( GetStatus().DoOnlineSpelling() ) - aEditDoc.GetObject( 0 )->CreateWrongList(); + maEditDoc.GetObject( 0 )->CreateWrongList(); } EditPaM ImpEditEngine::DeleteSelected(const EditSelection& rSel) @@ -262,12 +269,12 @@ OUString ImpEditEngine::GetSelected( const EditSelection& rSel ) const return OUString(); EditSelection aSel( rSel ); - aSel.Adjust( aEditDoc ); + aSel.Adjust( maEditDoc ); ContentNode* pStartNode = aSel.Min().GetNode(); ContentNode* pEndNode = aSel.Max().GetNode(); - sal_Int32 nStartNode = aEditDoc.GetPos( pStartNode ); - sal_Int32 nEndNode = aEditDoc.GetPos( pEndNode ); + sal_Int32 nStartNode = maEditDoc.GetPos( pStartNode ); + sal_Int32 nEndNode = maEditDoc.GetPos( pEndNode ); OSL_ENSURE( nStartNode <= nEndNode, "Selection not sorted ?" ); @@ -277,8 +284,8 @@ OUString ImpEditEngine::GetSelected( const EditSelection& rSel ) const // iterate over the paragraphs ... for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) { - OSL_ENSURE( aEditDoc.GetObject( nNode ), "Node not found: GetSelected" ); - const ContentNode* pNode = aEditDoc.GetObject( nNode ); + const ContentNode* pNode = maEditDoc.GetObject( nNode ); + assert(pNode); const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart! @@ -300,33 +307,33 @@ bool ImpEditEngine::MouseButtonDown( const MouseEvent& rMEvt, EditView* pView ) GetSelEngine().SelMouseButtonDown( rMEvt ); // Special treatment - EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); - if ( !rMEvt.IsShift() ) + EditSelection aCurSel( pView->getImpl().GetEditSelection() ); + if ( rMEvt.IsShift() ) + return true; + + if ( rMEvt.GetClicks() == 2 ) { - if ( rMEvt.GetClicks() == 2 ) - { - // So that the SelectionEngine knows about the anchor. - aSelEngine.CursorPosChanging( true, false ); - - EditSelection aNewSelection( SelectWord( aCurSel ) ); - pView->pImpEditView->DrawSelectionXOR(); - pView->pImpEditView->SetEditSelection( aNewSelection ); - pView->pImpEditView->DrawSelectionXOR(); - pView->ShowCursor(); - } - else if ( rMEvt.GetClicks() == 3 ) - { - // So that the SelectionEngine knows about the anchor. - aSelEngine.CursorPosChanging( true, false ); - - EditSelection aNewSelection( aCurSel ); - aNewSelection.Min().SetIndex( 0 ); - aNewSelection.Max().SetIndex( aCurSel.Min().GetNode()->Len() ); - pView->pImpEditView->DrawSelectionXOR(); - pView->pImpEditView->SetEditSelection( aNewSelection ); - pView->pImpEditView->DrawSelectionXOR(); - pView->ShowCursor(); - } + // So that the SelectionEngine knows about the anchor. + maSelEngine.CursorPosChanging( true, false ); + + EditSelection aNewSelection( SelectWord( aCurSel ) ); + pView->getImpl().DrawSelectionXOR(); + pView->getImpl().SetEditSelection( aNewSelection ); + pView->getImpl().DrawSelectionXOR(); + pView->ShowCursor(); + } + else if ( rMEvt.GetClicks() == 3 ) + { + // So that the SelectionEngine knows about the anchor. + maSelEngine.CursorPosChanging( true, false ); + + EditSelection aNewSelection( aCurSel ); + aNewSelection.Min().SetIndex( 0 ); + aNewSelection.Max().SetIndex( aCurSel.Min().GetNode()->Len() ); + pView->getImpl().DrawSelectionXOR(); + pView->getImpl().SetEditSelection( aNewSelection ); + pView->getImpl().DrawSelectionXOR(); + pView->ShowCursor(); } return true; } @@ -339,61 +346,69 @@ bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) SetActiveView( pView ); if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput ) { - pView->DeleteSelected(); - mpIMEInfos.reset(); - EditPaM aPaM = pView->GetImpEditView()->GetEditSelection().Max(); - OUString aOldTextAfterStartPos = aPaM.GetNode()->Copy( aPaM.GetIndex() ); - sal_Int32 nMax = aOldTextAfterStartPos.indexOf( CH_FEATURE ); - if ( nMax != -1 ) // don't overwrite features! - aOldTextAfterStartPos = aOldTextAfterStartPos.copy( 0, nMax ); - mpIMEInfos.reset( new ImplIMEInfos( aPaM, aOldTextAfterStartPos ) ); - mpIMEInfos->bWasCursorOverwrite = !pView->IsInsertMode(); - UndoActionStart( EDITUNDO_INSERT ); + if (!pView->IsReadOnly()) + { + pView->DeleteSelected(); + mpIMEInfos.reset(); + EditPaM aPaM = pView->getImpl().GetEditSelection().Max(); + OUString aOldTextAfterStartPos = aPaM.GetNode()->Copy( aPaM.GetIndex() ); + sal_Int32 nMax = aOldTextAfterStartPos.indexOf( CH_FEATURE ); + if ( nMax != -1 ) // don't overwrite features! + aOldTextAfterStartPos = aOldTextAfterStartPos.copy( 0, nMax ); + mpIMEInfos.reset( new ImplIMEInfos( aPaM, aOldTextAfterStartPos ) ); + mpIMEInfos->bWasCursorOverwrite = !pView->IsInsertMode(); + UndoActionStart( EDITUNDO_INSERT ); + } } else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput ) { - OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => No start ?" ); - if( mpIMEInfos ) + if (!pView->IsReadOnly()) { - // #102812# convert quotes in IME text - // works on the last input character, this is especially in Korean text often done - // quotes that are inside of the string are not replaced! - // Borrowed from sw: edtwin.cxx - if ( mpIMEInfos->nLen ) + OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => No start ?" ); + if( mpIMEInfos ) { - EditSelection aSel( mpIMEInfos->aPos ); - aSel.Min().SetIndex( aSel.Min().GetIndex() + mpIMEInfos->nLen-1 ); - aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen ); // #102812# convert quotes in IME text // works on the last input character, this is especially in Korean text often done // quotes that are inside of the string are not replaced! - const sal_Unicode nCharCode = aSel.Min().GetNode()->GetChar( aSel.Min().GetIndex() ); - if ( ( GetStatus().DoAutoCorrect() ) && ( ( nCharCode == '\"' ) || ( nCharCode == '\'' ) ) ) + // Borrowed from sw: edtwin.cxx + if ( mpIMEInfos->nLen ) { - aSel = DeleteSelected( aSel ); - aSel = AutoCorrect( aSel, nCharCode, mpIMEInfos->bWasCursorOverwrite ); - pView->pImpEditView->SetEditSelection( aSel ); + EditSelection aSel( mpIMEInfos->aPos ); + aSel.Min().SetIndex( aSel.Min().GetIndex() + mpIMEInfos->nLen-1 ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen ); + // #102812# convert quotes in IME text + // works on the last input character, this is especially in Korean text often done + // quotes that are inside of the string are not replaced! + // See also tdf#155350 + const sal_Unicode nCharCode = aSel.Min().GetNode()->GetChar( aSel.Min().GetIndex() ); + if ( ( GetStatus().DoAutoCorrect() ) && SvxAutoCorrect::IsAutoCorrectChar(nCharCode) ) + { + aSel = DeleteSelected( aSel ); + aSel = AutoCorrect( aSel, nCharCode, mpIMEInfos->bWasCursorOverwrite ); + pView->getImpl().SetEditSelection( aSel ); + } } - } - ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); - pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); + ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); + if (pPortion) + pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); - bool bWasCursorOverwrite = mpIMEInfos->bWasCursorOverwrite; + bool bWasCursorOverwrite = mpIMEInfos->bWasCursorOverwrite; - mpIMEInfos.reset(); + mpIMEInfos.reset(); - FormatAndUpdate( pView ); + FormatAndLayout( pView ); - pView->SetInsertMode( !bWasCursorOverwrite ); + pView->SetInsertMode( !bWasCursorOverwrite ); + } + UndoActionEnd(); } - UndoActionEnd(); } else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput ) { - OSL_ENSURE( mpIMEInfos, "CommandEventId::ExtTextInput => No Start ?" ); - if( mpIMEInfos ) + if( mpIMEInfos && !pView->IsReadOnly()) { + OSL_ENSURE( mpIMEInfos, "CommandEventId::ExtTextInput => No Start ?" ); const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData(); if ( !pData->IsOnlyCursorChanged() ) @@ -444,7 +459,7 @@ bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); - FormatAndUpdate( pView ); + FormatAndLayout( pView ); } EditSelection aNewSel = EditPaM( mpIMEInfos->aPos.GetNode(), mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() ); @@ -464,7 +479,7 @@ bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) { if (mpIMEInfos) { - EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); + EditPaM aPaM( pView->getImpl().GetEditSelection().Max() ); tools::Rectangle aR1 = PaMtoEditCursor( aPaM ); sal_Int32 nInputEnd = mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen; @@ -479,8 +494,8 @@ bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) const EditLine& rLine = pParaPortion->GetLines()[nLine]; if ( nInputEnd > rLine.GetEnd() ) nInputEnd = rLine.GetEnd(); - tools::Rectangle aR2 = PaMtoEditCursor( EditPaM( aPaM.GetNode(), nInputEnd ), GetCursorFlags::EndOfLine ); - tools::Rectangle aRect = pView->GetImpEditView()->GetWindowPos( aR1 ); + tools::Rectangle aR2 = PaMtoEditCursor( EditPaM( aPaM.GetNode(), nInputEnd ), CursorFlags{ .bEndOfLine = true }); + tools::Rectangle aRect = pView->getImpl().GetWindowPos( aR1 ); auto nExtTextInputWidth = aR2.Left() - aR1.Right(); if (EditViewCallbacks* pEditViewCallbacks = pView->getEditViewCallbacks()) pEditViewCallbacks->EditViewCursorRect(aRect, nExtTextInputWidth); @@ -523,7 +538,7 @@ bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) if ( aSelection.nStartPara != aSelection.nEndPara ) { - sal_Int32 aParaLen = pEditEngine->GetTextLen( aSelection.nStartPara ); + sal_Int32 aParaLen = mpEditEngine->GetTextLen( aSelection.nStartPara ); aSelection.nEndPara = aSelection.nStartPara; aSelection.nEndPos = aParaLen; pView->SetSelection( aSelection ); @@ -534,26 +549,49 @@ bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) { if (mpIMEInfos) { - EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); + EditPaM aPaM( pView->getImpl().GetEditSelection().Max() ); if ( !IsFormatted() ) FormatDoc(); - ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) ); + sal_Int32 nPortionPos = GetEditDoc().GetPos(aPaM.GetNode()); + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject(nPortionPos); if (pParaPortion) { - sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true ); - const EditLine& rLine = pParaPortion->GetLines()[nLine]; - std::unique_ptr<tools::Rectangle[]> aRects(new tools::Rectangle[ mpIMEInfos->nLen ]); - for (sal_Int32 i = 0; i < mpIMEInfos->nLen; ++i) - { - sal_Int32 nInputPos = mpIMEInfos->aPos.GetIndex() + i; - if ( nInputPos > rLine.GetEnd() ) - nInputPos = rLine.GetEnd(); - tools::Rectangle aR2 = GetEditCursor( pParaPortion, nInputPos ); - aRects[ i ] = pView->GetImpEditView()->GetWindowPos( aR2 ); - } + const sal_Int32 nMinPos = mpIMEInfos->aPos.GetIndex(); + const sal_Int32 nMaxPos = nMinPos + mpIMEInfos->nLen - 1; + std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen); + + auto CollectCharPositions = [&](const LineAreaInfo& rInfo) { + if (!rInfo.pLine) // Start of ParaPortion + { + if (rInfo.nPortion < nPortionPos) + return CallbackResult::SkipThisPortion; + if (rInfo.nPortion > nPortionPos) + return CallbackResult::Stop; + assert(&rInfo.rPortion == pParaPortion); + } + else // This is the needed ParaPortion + { + if (rInfo.pLine->GetStart() > nMaxPos) + return CallbackResult::Stop; + if (rInfo.pLine->GetEnd() < nMinPos) + return CallbackResult::Continue; + for (sal_Int32 n = nMinPos; n <= nMaxPos; ++n) + { + if (rInfo.pLine->IsIn(n)) + { + tools::Rectangle aR = GetEditCursor(*pParaPortion, *rInfo.pLine, n, CursorFlags()); + aR.Move(getTopLeftDocOffset(rInfo.aArea)); + aRects[n - nMinPos] = pView->getImpl().GetWindowPos(aR); + } + } + } + return CallbackResult::Continue; + }; + IterateLineAreas(CollectCharPositions, IterFlag::none); + if (vcl::Window* pWindow = pView->GetWindow()) - pWindow->SetCompositionCharRect( aRects.get(), mpIMEInfos->nLen ); + pWindow->SetCompositionCharRect(aRects.data(), aRects.size()); } } } @@ -574,37 +612,40 @@ bool ImpEditEngine::MouseButtonUp( const MouseEvent& rMEvt, EditView* pView ) // non-tiled-rendering case, but it has been here since 2000 (and before) // so who knows what corner case it was supposed to solve back then if (!comphelper::LibreOfficeKit::isActive()) - bInSelection = false; + mbInSelection = false; // Special treatments - EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); - if ( !aCurSel.HasRange() ) + EditSelection aCurSel( pView->getImpl().GetEditSelection() ); + if ( aCurSel.HasRange() ) + return true; + + if ( ( rMEvt.GetClicks() != 1 ) || !rMEvt.IsLeft() || rMEvt.IsMod2() ) + return true; + + const OutputDevice& rOutDev = pView->getEditViewCallbacks() ? pView->getEditViewCallbacks()->EditViewOutputDevice() : *pView->GetWindow()->GetOutDev(); + Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel()); + const SvxFieldItem* pFld = pView->GetField(aLogicClick); + if (!pFld) + return true; + + // tdf#121039 When in edit mode, editeng is responsible for opening the URL on mouse click + bool bUrlOpened = GetEditEnginePtr()->FieldClicked( *pFld ); + if (bUrlOpened) + return true; + + if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField())) { - if ( ( rMEvt.GetClicks() == 1 ) && rMEvt.IsLeft() && !rMEvt.IsMod2() ) + bool bCtrlClickHappened = rMEvt.IsMod1(); + bool bCtrlClickSecOption + = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink); + if ((bCtrlClickHappened && bCtrlClickSecOption) + || (!bCtrlClickHappened && !bCtrlClickSecOption)) { - const OutputDevice& rOutDev = pView->getEditViewCallbacks() ? pView->getEditViewCallbacks()->EditViewOutputDevice() : *pView->GetWindow(); - Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel()); - if (const SvxFieldItem* pFld = pView->GetField(aLogicClick)) - { - // tdf#121039 When in edit mode, editeng is responsible for opening the URL on mouse click - if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField())) - { - SvtSecurityOptions aSecOpt; - bool bCtrlClickHappened = rMEvt.IsMod1(); - bool bCtrlClickSecOption - = aSecOpt.IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink); - if ((bCtrlClickHappened && bCtrlClickSecOption) - || (!bCtrlClickHappened && !bCtrlClickSecOption)) - { - css::uno::Reference<css::system::XSystemShellExecute> exec( - css::system::SystemShellExecute::create( - comphelper::getProcessComponentContext())); - exec->execute(pUrlField->GetURL(), OUString(), - css::system::SystemShellExecuteFlags::DEFAULTS); - } - } - GetEditEnginePtr()->FieldClicked( *pFld ); - } + css::uno::Reference<css::system::XSystemShellExecute> exec( + css::system::SystemShellExecute::create( + comphelper::getProcessComponentContext())); + exec->execute(pUrlField->GetURL(), OUString(), + css::system::SystemShellExecuteFlags::DEFAULTS); } } return true; @@ -633,33 +674,39 @@ void ImpEditEngine::Clear() { InitDoc( false ); - EditPaM aPaM = aEditDoc.GetStartPaM(); + EditPaM aPaM = maEditDoc.GetStartPaM(); EditSelection aSel( aPaM ); - nCurTextHeight = 0; - nCurTextHeightNTP = 0; + mnCurTextHeight = 0; + mnCurTextHeightNTP = 0; ResetUndoManager(); - for (size_t nView = aEditViews.size(); nView; ) + for (size_t nView = maEditViews.size(); nView; ) { - EditView* pView = aEditViews[--nView]; - pView->pImpEditView->SetEditSelection( aSel ); + EditView* pView = maEditViews[--nView]; + pView->getImpl().SetEditSelection( aSel ); } + + // Related: tdf#82115 Fix crash when handling input method events. + // The nodes in mpIMEInfos may be deleted in ImpEditEngine::Clear() which + // causes a crash in the CommandEventId::ExtTextInput and + // CommandEventId::EndExtTextInput event handlers. + mpIMEInfos.reset(); } EditPaM ImpEditEngine::RemoveText() { InitDoc( true ); - EditPaM aStartPaM = aEditDoc.GetStartPaM(); + EditPaM aStartPaM = maEditDoc.GetStartPaM(); EditSelection aEmptySel( aStartPaM, aStartPaM ); - for (EditView* pView : aEditViews) + for (EditView* pView : maEditViews) { - pView->pImpEditView->SetEditSelection( aEmptySel ); + pView->getImpl().SetEditSelection( aEmptySel ); } ResetUndoManager(); - return aEditDoc.GetStartPaM(); + return maEditDoc.GetStartPaM(); } @@ -676,22 +723,23 @@ void ImpEditEngine::SetText(const OUString& rText) if (!rText.isEmpty()) aPaM = ImpInsertText( aEmptySel, rText ); - for (EditView* pView : aEditViews) + for (EditView* pView : maEditViews) { - pView->pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) ); + pView->getImpl().SetEditSelection( EditSelection( aPaM, aPaM ) ); // If no text then also no Format&Update // => The text remains. - if (rText.isEmpty() && GetUpdateMode()) + if (rText.isEmpty() && IsUpdateLayout()) { tools::Rectangle aTmpRect( pView->GetOutputArea().TopLeft(), - Size( aPaperSize.Width(), nCurTextHeight ) ); + Size( maPaperSize.Width(), mnCurTextHeight ) ); aTmpRect.Intersection( pView->GetOutputArea() ); pView->InvalidateWindow( aTmpRect ); } } - if (rText.isEmpty()) { // otherwise it must be invalidated later, !bFormatted is enough. - nCurTextHeight = 0; - nCurTextHeightNTP = 0; + if (rText.isEmpty()) // otherwise it must be invalidated later, !bFormatted is enough. + { + mnCurTextHeight = 0; + mnCurTextHeightNTP = 0; } EnableUndo( bUndoCurrentlyEnabled ); OSL_ENSURE( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "Undo after SetText?" ); @@ -702,7 +750,7 @@ const SfxItemSet& ImpEditEngine::GetEmptyItemSet() const { if ( !pEmptyItemSet ) { - pEmptyItemSet = std::make_unique<SfxItemSet>(const_cast<SfxItemPool&>(aEditDoc.GetItemPool()), svl::Items<EE_ITEMS_START, EE_ITEMS_END>{}); + pEmptyItemSet = std::make_unique<SfxItemSetFixed<EE_ITEMS_START, EE_ITEMS_END>>(const_cast<SfxItemPool&>(maEditDoc.GetItemPool())); for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++) { pEmptyItemSet->ClearItem( nWhich ); @@ -714,18 +762,9 @@ const SfxItemSet& ImpEditEngine::GetEmptyItemSet() const // MISC -void ImpEditEngine::CursorMoved( const ContentNode* pPrevNode ) -{ - // Delete empty attributes, but only if paragraph is not empty! - if (pPrevNode->GetCharAttribs().HasEmptyAttribs() && pPrevNode->Len()) - { - const_cast<ContentNode*>(pPrevNode)->GetCharAttribs().DeleteEmptyAttribs(aEditDoc.GetItemPool()); - } -} - void ImpEditEngine::TextModified() { - bFormatted = false; + mbFormatted = false; if ( GetNotifyHdl().IsSet() ) { @@ -739,21 +778,21 @@ void ImpEditEngine::ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreU { assert(pNode && "ParaAttribsChanged: Which one?"); - aEditDoc.SetModified( true ); - bFormatted = false; + maEditDoc.SetModified( true ); + mbFormatted = false; ParaPortion* pPortion = FindParaPortion( pNode ); - OSL_ENSURE( pPortion, "ParaAttribsChanged: Portion?" ); + assert(pPortion); pPortion->MarkSelectionInvalid( 0 ); - sal_Int32 nPara = aEditDoc.GetPos( pNode ); - if ( bIgnoreUndoCheck || pEditEngine->IsInUndo() ) - pEditEngine->ParaAttribsChanged( nPara ); + sal_Int32 nPara = maEditDoc.GetPos( pNode ); + if (bIgnoreUndoCheck || mpEditEngine->IsInUndo()) + mpEditEngine->ParaAttribsChanged( nPara ); ParaPortion* pNextPortion = GetParaPortions().SafeGetObject( nPara+1 ); // => is formatted again anyway, if Invalid. if ( pNextPortion && !pNextPortion->IsInvalid() ) - CalcHeight( pNextPortion ); + CalcHeight(*pNextPortion); } @@ -765,14 +804,14 @@ EditSelection const & ImpEditEngine::MoveCursor( const KeyEvent& rKeyEvent, Edit // Actually, only necessary for up/down, but whatever. CheckIdleFormatter(); - EditPaM aPaM( pEditView->pImpEditView->GetEditSelection().Max() ); + EditPaM aPaM(pEditView->getImpl().GetEditSelection().Max()); EditPaM aOldPaM( aPaM ); TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom; - if (IsVertical() && IsTopToBottom()) + if (IsEffectivelyVertical() && IsTopToBottom()) eTextDirection = TextDirectionality::TopToBottom_RightToLeft; - else if (IsVertical() && !IsTopToBottom()) + else if (IsEffectivelyVertical() && !IsTopToBottom()) eTextDirection = TextDirectionality::BottomToTop_LeftToRight; else if ( IsRightToLeft( GetEditDoc().GetPos( aPaM.GetNode() ) ) ) eTextDirection = TextDirectionality::RightToLeft_TopToBottom; @@ -905,41 +944,41 @@ EditSelection const & ImpEditEngine::MoveCursor( const KeyEvent& rKeyEvent, Edit break; } - if ( aOldPaM != aPaM ) + if ( aOldPaM != aPaM && nullptr != aOldPaM.GetNode() ) { - CursorMoved( aOldPaM.GetNode() ); + aOldPaM.GetNode()->checkAndDeleteEmptyAttribs(); } // May cause, a CreateAnchor or deselection all - aSelEngine.SetCurView( pEditView ); - aSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() ); - EditPaM aOldEnd( pEditView->pImpEditView->GetEditSelection().Max() ); + maSelEngine.SetCurView(pEditView); + maSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() ); + EditPaM aOldEnd(pEditView->getImpl().GetEditSelection().Max()); { - EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection()); + EditSelection aNewSelection(pEditView->getImpl().GetEditSelection()); aNewSelection.Max() = aPaM; - pEditView->pImpEditView->SetEditSelection(aNewSelection); - // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Max()) = aPaM; + pEditView->getImpl().SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(pEditView->getImpl().GetEditSelection().Max()) = aPaM; } if ( bKeyModifySelection ) { // Then the selection is expanded ... or the whole selection is painted in case of tiled rendering. - EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? pEditView->pImpEditView->GetEditSelection().Min() : aOldEnd, aPaM ); - pEditView->pImpEditView->DrawSelectionXOR( aTmpNewSel ); + EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? pEditView->getImpl().GetEditSelection().Min() : aOldEnd, aPaM ); + pEditView->getImpl().DrawSelectionXOR( aTmpNewSel ); } else { - EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection()); + EditSelection aNewSelection(pEditView->getImpl().GetEditSelection()); aNewSelection.Min() = aPaM; - pEditView->pImpEditView->SetEditSelection(aNewSelection); - // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Min()) = aPaM; + pEditView->getImpl().SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(pEditView->getImpl().GetEditSelection().Min()) = aPaM; } - return pEditView->pImpEditView->GetEditSelection(); + return pEditView->getImpl().GetEditSelection(); } -EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * pEditView, const EditPaM& rPaM, bool bStart ) +EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * mpEditView, const EditPaM& rPaM, bool bStart ) { EditPaM aPaM( rPaM ); @@ -952,7 +991,7 @@ EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * pEditView, const E const EditLine& rLine = pParaPortion->GetLines()[nLine]; bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); - pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; + mpEditView->getImpl().maExtraCursorFlags = CursorFlags(); if ( !bEmptyLine ) { @@ -978,15 +1017,15 @@ EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * pEditView, const E if ( bStart ) { - pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 0 : 1 ); + mpEditView->getImpl().SetCursorBidiLevel( bPortionRTL ? 0 : 1 ); // Maybe we must be *behind* the character - if ( bPortionRTL && pEditView->IsInsertMode() ) + if (bPortionRTL && mpEditView->IsInsertMode()) aPaM.SetIndex( aPaM.GetIndex()+1 ); } else { - pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 1 : 0 ); - if ( !bPortionRTL && pEditView->IsInsertMode() ) + mpEditView->getImpl().SetCursorBidiLevel( bPortionRTL ? 1 : 0 ); + if ( !bPortionRTL && mpEditView->IsInsertMode() ) aPaM.SetIndex( aPaM.GetIndex()+1 ); } } @@ -1007,7 +1046,7 @@ EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const const EditLine& rLine = pParaPortion->GetLines()[nLine]; bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); - pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; + pEditView->getImpl().maExtraCursorFlags = CursorFlags(); bool bParaRTL = IsRightToLeft( nPara ); @@ -1058,12 +1097,12 @@ EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const if (bVisualToLeft != bool(nRTLLevel % 2)) { aPaM = CursorLeft( aPaM, nCharacterIteratorMode ); - pEditView->pImpEditView->SetCursorBidiLevel( 1 ); + pEditView->getImpl().SetCursorBidiLevel( 1 ); } else { aPaM = CursorRight( aPaM, nCharacterIteratorMode ); - pEditView->pImpEditView->SetCursorBidiLevel( 0 ); + pEditView->getImpl().SetCursorBidiLevel( 0 ); } bDone = true; } @@ -1103,13 +1142,13 @@ EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine ) { aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) ); - pEditView->pImpEditView->SetCursorBidiLevel( 0 ); + pEditView->getImpl().SetCursorBidiLevel( 0 ); } } else { bool bWasBehind = false; - bool bBeforePortion = !nPosInLine || pEditView->pImpEditView->GetCursorBidiLevel() == 1; + bool bBeforePortion = !nPosInLine || pEditView->getImpl().GetCursorBidiLevel() == 1; if ( nPosInLine && ( !bBeforePortion ) ) // before the next portion bWasBehind = true; // step one back, otherwise visual will be unusable when rtl portion follows. @@ -1148,7 +1187,7 @@ EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const else if ( !bVisualToLeft && bRTLPortion && ( bWasBehind || !_rTextPortion.IsRightToLeft() ) ) aPaM.SetIndex( aPaM.GetIndex()+1 ); - pEditView->pImpEditView->SetCursorBidiLevel( _nPortionStart ); + pEditView->getImpl().SetCursorBidiLevel( _nPortionStart ); } } @@ -1230,24 +1269,24 @@ EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView const * pView ) assert(pView && "No View - No Cursor Movement!"); const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() ); - OSL_ENSURE( pPPortion, "No matching portion found: CursorUp "); + assert(pPPortion); sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() ); const EditLine& rLine = pPPortion->GetLines()[nLine]; tools::Long nX; - if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) + if ( pView->getImpl().mnTravelXPos == TRAVEL_X_DONTKNOW ) { - nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() ); - pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; + nX = GetXPos(*pPPortion, rLine, rPaM.GetIndex()); + pView->getImpl().mnTravelXPos = nX + mnOnePixelInRef; } else - nX = pView->pImpEditView->nTravelXPos; + nX = pView->getImpl().mnTravelXPos; EditPaM aNewPaM( rPaM ); if ( nLine ) // same paragraph { const EditLine& rPrevLine = pPPortion->GetLines()[nLine-1]; - aNewPaM.SetIndex( GetChar( pPPortion, &rPrevLine, nX ) ); + aNewPaM.SetIndex(GetChar(*pPPortion, rPrevLine, nX)); // If a previous automatically wrapped line, and one has to be exactly // at the end of this line, the cursor lands on the current line at the // beginning. See Problem: Last character of an automatically wrapped @@ -1262,7 +1301,7 @@ EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView const * pView ) { const EditLine& rLine2 = pPrevPortion->GetLines()[pPrevPortion->GetLines().Count()-1]; aNewPaM.SetNode( pPrevPortion->GetNode() ); - aNewPaM.SetIndex( GetChar( pPrevPortion, &rLine2, nX+nOnePixelInRef ) ); + aNewPaM.SetIndex(GetChar(*pPrevPortion, rLine2, nX + mnOnePixelInRef)); } } @@ -1271,27 +1310,27 @@ EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView const * pView ) EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView const * pView ) { - OSL_ENSURE( pView, "No View - No Cursor Movement!" ); + assert(pView); const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() ); - OSL_ENSURE( pPPortion, "No matching portion found: CursorDown" ); + assert(pPPortion); sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() ); tools::Long nX; - if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) + if ( pView->getImpl().mnTravelXPos == TRAVEL_X_DONTKNOW ) { const EditLine& rLine = pPPortion->GetLines()[nLine]; - nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() ); - pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; + nX = GetXPos(*pPPortion, rLine, rPaM.GetIndex()); + pView->getImpl().mnTravelXPos = nX + mnOnePixelInRef; } else - nX = pView->pImpEditView->nTravelXPos; + nX = pView->getImpl().mnTravelXPos; EditPaM aNewPaM( rPaM ); if ( nLine < pPPortion->GetLines().Count()-1 ) { const EditLine& rNextLine = pPPortion->GetLines()[nLine+1]; - aNewPaM.SetIndex( GetChar( pPPortion, &rNextLine, nX ) ); + aNewPaM.SetIndex(GetChar(*pPPortion, rNextLine, nX)); // Special treatment, see CursorUp ... if ( ( aNewPaM.GetIndex() == rNextLine.GetEnd() ) && ( aNewPaM.GetIndex() > rNextLine.GetStart() ) && ( aNewPaM.GetIndex() < pPPortion->GetNode()->Len() ) ) aNewPaM = CursorLeft( aNewPaM ); @@ -1305,7 +1344,7 @@ EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView const * pView ) aNewPaM.SetNode( pNextPortion->GetNode() ); // Never at the very end when several lines, because then a line // below the cursor appears. - aNewPaM.SetIndex( GetChar( pNextPortion, &rLine, nX+nOnePixelInRef ) ); + aNewPaM.SetIndex(GetChar(*pNextPortion, rLine, nX + mnOnePixelInRef)); if ( ( aNewPaM.GetIndex() == rLine.GetEnd() ) && ( aNewPaM.GetIndex() > rLine.GetStart() ) && ( pNextPortion->GetLines().Count() > 1 ) ) aNewPaM = CursorLeft( aNewPaM ); } @@ -1317,7 +1356,7 @@ EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView const * pView ) EditPaM ImpEditEngine::CursorStartOfLine( const EditPaM& rPaM ) { const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() ); - OSL_ENSURE( pCurPortion, "No Portion for the PaM ?" ); + assert(pCurPortion); sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() ); const EditLine& rLine = pCurPortion->GetLines()[nLine]; @@ -1329,7 +1368,7 @@ EditPaM ImpEditEngine::CursorStartOfLine( const EditPaM& rPaM ) EditPaM ImpEditEngine::CursorEndOfLine( const EditPaM& rPaM ) { const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() ); - OSL_ENSURE( pCurPortion, "No Portion for the PaM ?" ); + assert(pCurPortion); sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() ); const EditLine& rLine = pCurPortion->GetLines()[nLine]; @@ -1371,14 +1410,14 @@ EditPaM ImpEditEngine::CursorEndOfParagraph( const EditPaM& rPaM ) EditPaM ImpEditEngine::CursorStartOfDoc() { - EditPaM aPaM( aEditDoc.GetObject( 0 ), 0 ); + EditPaM aPaM( maEditDoc.GetObject( 0 ), 0 ); return aPaM; } EditPaM ImpEditEngine::CursorEndOfDoc() { - ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 ); - ParaPortion* pLastPortion = GetParaPortions().SafeGetObject( aEditDoc.Count()-1 ); + ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 ); + ParaPortion* pLastPortion = GetParaPortions().SafeGetObject( maEditDoc.Count()-1 ); OSL_ENSURE( pLastNode && pLastPortion, "CursorEndOfDoc: Node or Portion not found" ); if (!(pLastNode && pLastPortion)) return EditPaM(); @@ -1388,7 +1427,7 @@ EditPaM ImpEditEngine::CursorEndOfDoc() pLastNode = GetPrevVisNode( pLastPortion->GetNode() ); OSL_ENSURE( pLastNode, "No visible paragraph?" ); if ( !pLastNode ) - pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 ); + pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 ); } EditPaM aPaM( pLastNode, pLastNode->Len() ); @@ -1400,7 +1439,7 @@ EditPaM ImpEditEngine::PageUp( const EditPaM& rPaM, EditView const * pView ) tools::Rectangle aRect = PaMtoEditCursor( rPaM ); Point aTopLeft = aRect.TopLeft(); aTopLeft.AdjustY( -(pView->GetVisArea().GetHeight() *9/10) ); - aTopLeft.AdjustX(nOnePixelInRef ); + aTopLeft.AdjustX(mnOnePixelInRef); if ( aTopLeft.Y() < 0 ) { aTopLeft.setY( 0 ); @@ -1413,7 +1452,7 @@ EditPaM ImpEditEngine::PageDown( const EditPaM& rPaM, EditView const * pView ) tools::Rectangle aRect = PaMtoEditCursor( rPaM ); Point aBottomRight = aRect.BottomRight(); aBottomRight.AdjustY(pView->GetVisArea().GetHeight() *9/10 ); - aBottomRight.AdjustX(nOnePixelInRef ); + aBottomRight.AdjustX(mnOnePixelInRef); tools::Long nHeight = GetTextHeight(); if ( aBottomRight.Y() > nHeight ) { @@ -1429,8 +1468,8 @@ EditPaM ImpEditEngine::WordLeft( const EditPaM& rPaM ) if ( nCurrentPos == 0 ) { // Previous paragraph... - sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() ); - ContentNode* pPrevNode = aEditDoc.GetObject( --nCurPara ); + sal_Int32 nCurPara = maEditDoc.GetPos( aNewPaM.GetNode() ); + ContentNode* pPrevNode = maEditDoc.GetObject( --nCurPara ); if ( pPrevNode ) { aNewPaM.SetNode( pPrevNode ); @@ -1479,8 +1518,8 @@ EditPaM ImpEditEngine::WordRight( const EditPaM& rPaM, sal_Int16 nWordType ) if ( aNewPaM.GetIndex() >= nMax ) { // Next paragraph ... - sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() ); - ContentNode* pNextNode = aEditDoc.GetObject( ++nCurPara ); + sal_Int32 nCurPara = maEditDoc.GetPos( aNewPaM.GetNode() ); + ContentNode* pNextNode = maEditDoc.GetObject( ++nCurPara ); if ( pNextNode ) { aNewPaM.SetNode( pNextNode ); @@ -1502,8 +1541,11 @@ EditPaM ImpEditEngine::StartOfWord( const EditPaM& rPaM ) lang::Locale aLocale( GetLocale( aTmpPaM ) ); uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + // tdf#135761 - since this function is only used when a selection is deleted at the left, + // change the search preference of the word boundary from forward to backward. + // For further details of a deletion of a selection check ImpEditEngine::DeleteLeftOrRight. i18n::Boundary aBoundary = _xBI->getWordBoundary( - rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); + rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, false); aNewPaM.SetIndex( aBoundary.startPos ); return aNewPaM; @@ -1528,7 +1570,7 @@ EditPaM ImpEditEngine::EndOfWord( const EditPaM& rPaM ) return aNewPaM; } -EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 nWordType, bool bAcceptStartOfWord ) +EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 nWordType, bool bAcceptStartOfWord, bool bAcceptEndOfWord ) { EditSelection aNewSel( rCurSel ); EditPaM aPaM( rCurSel.Max() ); @@ -1550,7 +1592,7 @@ EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale, nWordType, true); // don't select when cursor at end of word - if ( ( aBoundary.endPos > aPaM.GetIndex() ) && + if ( ( aBoundary.endPos > aPaM.GetIndex() || ( bAcceptEndOfWord && aBoundary.endPos == aPaM.GetIndex() ) ) && ( ( aBoundary.startPos < aPaM.GetIndex() ) || ( bAcceptStartOfWord && ( aBoundary.startPos == aPaM.GetIndex() ) ) ) ) { aNewSel.Min().SetIndex( aBoundary.startPos ); @@ -1586,15 +1628,13 @@ EditSelection ImpEditEngine::SelectSentence( const EditSelection& rCurSel ) bool ImpEditEngine::IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const { uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); - if (!pCTLOptions) - pCTLOptions.reset( new SvtCTLOptions ); // get the index that really is first const sal_Int32 nFirstPos = std::min(rCurSel.Min().GetIndex(), rCurSel.Max().GetIndex()); bool bIsSequenceChecking = - pCTLOptions->IsCTLFontEnabled() && - pCTLOptions->IsCTLSequenceChecking() && + SvtCTLOptions::IsCTLFontEnabled() && + SvtCTLOptions::IsCTLSequenceChecking() && nFirstPos != 0 && /* first char needs not to be checked */ _xBI.is() && i18n::ScriptType::COMPLEX == _xBI->getScriptType( OUString( nChar ), 0 ); @@ -1621,7 +1661,7 @@ void ImpEditEngine::InitScriptTypes( sal_Int32 nPara ) if (!pParaPortion) return; - ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + ScriptTypePosInfos& rTypes = pParaPortion->getScriptTypePosInfos(); rTypes.clear(); ContentNode* pNode = pParaPortion->GetNode(); @@ -1640,7 +1680,7 @@ void ImpEditEngine::InitScriptTypes( sal_Int32 nPara ) const OUString aFldText = static_cast<const EditCharAttribField*>(pField)->GetFieldValue(); if ( !aFldText.isEmpty() ) { - aText = aText.replaceAt( pField->GetStart(), 1, aFldText.copy(0,1) ); + aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(0,1) ); short nFldScriptType = _xBI->getScriptType( aFldText, 0 ); for ( sal_Int32 nCharInField = 1; nCharInField < aFldText.getLength(); nCharInField++ ) @@ -1651,14 +1691,14 @@ void ImpEditEngine::InitScriptTypes( sal_Int32 nPara ) if ( nFldScriptType == i18n::ScriptType::WEAK ) { nFldScriptType = nTmpType; - aText = aText.replaceAt( pField->GetStart(), 1, aFldText.copy(nCharInField,1) ); + aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) ); } // ... but if the first one is LATIN, and there are CJK or CTL chars too, // we prefer that ScriptType because we need another font. if ( ( nTmpType == i18n::ScriptType::ASIAN ) || ( nTmpType == i18n::ScriptType::COMPLEX ) ) { - aText = aText.replaceAt( pField->GetStart(), 1, aFldText.copy(nCharInField,1) ); + aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) ); break; } } @@ -1687,14 +1727,19 @@ void ImpEditEngine::InitScriptTypes( sal_Int32 nPara ) } else { - if ( _xBI->getScriptType( aText, nPos - 1 ) == i18n::ScriptType::WEAK ) + auto nPrevPos = nPos; + auto nPrevChar = aText.iterateCodePoints(&nPrevPos, -1); + if (_xBI->getScriptType(aText, nPrevPos) == i18n::ScriptType::WEAK) { - switch ( u_charType(aText.iterateCodePoints(&nPos, 0) ) ) { - case U_NON_SPACING_MARK: - case U_ENCLOSING_MARK: - case U_COMBINING_SPACING_MARK: - --nPos; - rTypes.back().nEndPos--; + auto nChar = aText.iterateCodePoints(&nPos, 0); + auto nType = unicode::getUnicodeType(nChar); + if (nType == css::i18n::UnicodeType::NON_SPACING_MARK || + nType == css::i18n::UnicodeType::ENCLOSING_MARK || + nType == css::i18n::UnicodeType::COMBINING_SPACING_MARK || + (nPrevChar == 0x202F /* NNBSP, tdf#112594 */ && + u_getIntPropertyValue(nChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN)) + { + rTypes.back().nEndPos = nPos = nPrevPos; break; } } @@ -1708,11 +1753,11 @@ void ImpEditEngine::InitScriptTypes( sal_Int32 nPara ) rTypes[0].nScriptType = ( rTypes.size() > 1 ) ? rTypes[1].nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() ); // create writing direction information: - if ( pParaPortion->aWritingDirectionInfos.empty() ) + WritingDirectionInfos& rDirInfos = pParaPortion->getWritingDirectionInfos(); + if (rDirInfos.empty()) InitWritingDirections( nPara ); // i89825: Use CTL font for numbers embedded into an RTL run: - WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; for (const WritingDirectionInfo & rDirInfo : rDirInfos) { const sal_Int32 nStart = rDirInfo.nStartPos; @@ -1787,10 +1832,10 @@ sal_uInt16 ImpEditEngine::GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEn const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); if (pParaPortion) { - if ( pParaPortion->aScriptInfos.empty() ) - const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + const ScriptTypePosInfos& rTypes = pParaPortion->getScriptTypePosInfos(); - const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + if (rTypes.empty()) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); const sal_Int32 nPos = rPaM.GetIndex(); ScriptTypePosInfos::const_iterator itr = std::find_if(rTypes.begin(), rTypes.end(), FindByPos(nPos)); @@ -1808,7 +1853,7 @@ sal_uInt16 ImpEditEngine::GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEn SvtScriptType ImpEditEngine::GetItemScriptType( const EditSelection& rSel ) const { EditSelection aSel( rSel ); - aSel.Adjust( aEditDoc ); + aSel.Adjust( maEditDoc ); SvtScriptType nScriptType = SvtScriptType::NONE; @@ -1821,10 +1866,10 @@ SvtScriptType ImpEditEngine::GetItemScriptType( const EditSelection& rSel ) cons if (!pParaPortion) continue; - if ( pParaPortion->aScriptInfos.empty() ) - const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + ScriptTypePosInfos const& rTypes = pParaPortion->getScriptTypePosInfos(); - const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + if (rTypes.empty()) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); // find all the scripts of this range sal_Int32 nS = ( nPara == nStartPara ) ? aSel.Min().GetIndex() : 0; @@ -1867,10 +1912,11 @@ bool ImpEditEngine::IsScriptChange( const EditPaM& rPaM ) const const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); if (pParaPortion) { - if ( pParaPortion->aScriptInfos.empty() ) + ScriptTypePosInfos const& rTypes = pParaPortion->getScriptTypePosInfos(); + + if (rTypes.empty()) const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); - const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; const sal_Int32 nPos = rPaM.GetIndex(); for (const ScriptTypePosInfo & rType : rTypes) { @@ -1892,10 +1938,11 @@ bool ImpEditEngine::HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); if (pParaPortion) { - if ( pParaPortion->aScriptInfos.empty() ) + const ScriptTypePosInfos& rTypes = pParaPortion->getScriptTypePosInfos(); + + if (rTypes.empty()) const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); - const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; for ( size_t n = rTypes.size(); n && !bTypeFound; ) { if ( rTypes[--n].nScriptType == nType ) @@ -1911,10 +1958,10 @@ void ImpEditEngine::InitWritingDirections( sal_Int32 nPara ) if (!pParaPortion) return; - WritingDirectionInfos& rInfos = pParaPortion->aWritingDirectionInfos; + WritingDirectionInfos& rInfos = pParaPortion->getWritingDirectionInfos(); rInfos.clear(); - if (pParaPortion->GetNode()->Len()) + if (pParaPortion->GetNode()->Len() && !mbFuzzing) { const OUString aText = pParaPortion->GetNode()->GetString(); @@ -1959,7 +2006,7 @@ bool ImpEditEngine::IsRightToLeft( sal_Int32 nPara ) const bool bR2L = false; const SvxFrameDirectionItem* pFrameDirItem = nullptr; - if ( !IsVertical() ) + if ( !IsEffectivelyVertical() ) { bR2L = GetDefaultHorizontalTextDirection() == EEHorizontalTextDirection::R2L; pFrameDirItem = &GetParaAttrib( nPara, EE_PARA_WRITINGDIR ); @@ -2011,16 +2058,16 @@ sal_uInt8 ImpEditEngine::GetRightToLeft( sal_Int32 nPara, sal_Int32 nPos, sal_In { sal_uInt8 nRightToLeft = 0; - ContentNode* pNode = aEditDoc.GetObject( nPara ); + ContentNode* pNode = maEditDoc.GetObject( nPara ); if ( pNode && pNode->Len() ) { ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); if (pParaPortion) { - if ( pParaPortion->aWritingDirectionInfos.empty() ) + WritingDirectionInfos& rDirInfos = pParaPortion->getWritingDirectionInfos(); + if (rDirInfos.empty()) InitWritingDirections( nPara ); - WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; for (const WritingDirectionInfo & rDirInfo : rDirInfos) { if ( ( rDirInfo.nStartPos <= nPos ) && ( rDirInfo.nEndPos >= nPos ) ) @@ -2042,7 +2089,7 @@ SvxAdjust ImpEditEngine::GetJustification( sal_Int32 nPara ) const { SvxAdjust eJustification = SvxAdjust::Left; - if ( !aStatus.IsOutliner() ) + if (!maStatus.IsOutliner()) { eJustification = GetParaAttrib( nPara, EE_PARA_JUST ).GetAdjust(); @@ -2091,15 +2138,15 @@ void ImpEditEngine::ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars ) break; // for } } - InsertUndo(std::make_unique<EditUndoRemoveChars>(pEditEngine, CreateEPaM(rPaM), aStr)); + InsertUndo(std::make_unique<EditUndoRemoveChars>(mpEditEngine, CreateEPaM(rPaM), aStr)); } - aEditDoc.RemoveChars( rPaM, nChars ); + maEditDoc.RemoveChars( rPaM, nChars ); } EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 nNewPos ) { - aOldPositions.Justify(); + aOldPositions.Normalize(); bool bValidAction = ( static_cast<tools::Long>(nNewPos) < aOldPositions.Min() ) || ( static_cast<tools::Long>(nNewPos) > aOldPositions.Max() ); OSL_ENSURE( bValidAction, "Move in itself?" ); OSL_ENSURE( aOldPositions.Max() <= static_cast<tools::Long>(GetParaPortions().Count()), "totally over it: MoveParagraphs" ); @@ -2108,7 +2155,7 @@ EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 n if ( !bValidAction ) { - aSelection = aEditDoc.GetStartPaM(); + aSelection = maEditDoc.GetStartPaM(); return aSelection; } @@ -2123,68 +2170,83 @@ EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 n ParaPortion* pRecalc3 = nullptr; ParaPortion* pRecalc4 = nullptr; - if ( nNewPos == 0 ) // Move to Start + if (nNewPos == 0) // Move to Start { - pRecalc1 = GetParaPortions()[0]; - pRecalc2 = GetParaPortions()[aOldPositions.Min()]; + if (GetParaPortions().exists(0)) + pRecalc1 = &GetParaPortions().getRef(0); + if (GetParaPortions().exists(aOldPositions.Min())) + pRecalc2 = &GetParaPortions().getRef(aOldPositions.Min()); } - else if ( nNewPos == nParaCount ) + else if (nNewPos == nParaCount) { - pRecalc1 = GetParaPortions()[nParaCount-1]; - pRecalc2 = GetParaPortions()[aOldPositions.Max()]; + if (GetParaPortions().exists(nParaCount - 1)) + pRecalc1 = &GetParaPortions().getRef(nParaCount - 1); + if (GetParaPortions().exists(aOldPositions.Max())) + pRecalc2 = &GetParaPortions().getRef(aOldPositions.Max()); } - if ( aOldPositions.Min() == 0 ) // Move from Start + if (aOldPositions.Min() == 0) // Move from Start { - pRecalc3 = GetParaPortions()[0]; - pRecalc4 = GetParaPortions()[aOldPositions.Max()+1]; + if (GetParaPortions().exists(0)) + pRecalc3 = &GetParaPortions().getRef(0); + if (GetParaPortions().exists(aOldPositions.Max() + 1)) + pRecalc4 = &GetParaPortions().getRef(aOldPositions.Max() + 1); } - else if ( aOldPositions.Max() == (nParaCount-1) ) + else if (aOldPositions.Max() == nParaCount - 1) { - pRecalc3 = GetParaPortions()[aOldPositions.Max()]; - pRecalc4 = GetParaPortions()[aOldPositions.Min()-1]; + if (GetParaPortions().exists(aOldPositions.Max())) + pRecalc3 = &GetParaPortions().getRef(aOldPositions.Max()); + if (GetParaPortions().exists(aOldPositions.Min() - 1)) + pRecalc4 = &GetParaPortions().getRef(aOldPositions.Min() - 1); } MoveParagraphsInfo aMoveParagraphsInfo( aOldPositions.Min(), aOldPositions.Max(), nNewPos ); - aBeginMovingParagraphsHdl.Call( aMoveParagraphsInfo ); + maBeginMovingParagraphsHdl.Call( aMoveParagraphsInfo ); if ( IsUndoEnabled() && !IsInUndo()) - InsertUndo(std::make_unique<EditUndoMoveParagraphs>(pEditEngine, aOldPositions, nNewPos)); + InsertUndo(std::make_unique<EditUndoMoveParagraphs>(mpEditEngine, aOldPositions, nNewPos)); // do not lose sight of the Position ! ParaPortion* pDestPortion = GetParaPortions().SafeGetObject( nNewPos ); - ParaPortionList aTmpPortionList; + // Temporary containers used for moving the paragraph portions and content nodes to a new location + std::vector<std::unique_ptr<ParaPortion>> aParagraphPortionVector; + std::vector<std::unique_ptr<ContentNode>> aContentNodeVector; + + // Take the paragraph portions and content nodes out of its containers for (tools::Long i = aOldPositions.Min(); i <= aOldPositions.Max(); i++ ) { - // always aOldPositions.Min(), since Remove(). - std::unique_ptr<ParaPortion> pTmpPortion = GetParaPortions().Release(aOldPositions.Min()); - aEditDoc.Release( aOldPositions.Min() ); - aTmpPortionList.Append(std::move(pTmpPortion)); + // always aOldPositions.Min() as the index, since we remove and the elements from the containers and the + // other elements shift to the left. + std::unique_ptr<ParaPortion> pPortion = GetParaPortions().Release(aOldPositions.Min()); + aParagraphPortionVector.push_back(std::move(pPortion)); + + std::unique_ptr<ContentNode> pContentNode = maEditDoc.Release(aOldPositions.Min()); + aContentNodeVector.push_back(std::move(pContentNode)); } + // Determine the new location for paragraphs sal_Int32 nRealNewPos = pDestPortion ? GetParaPortions().GetPos( pDestPortion ) : GetParaPortions().Count(); - OSL_ENSURE( nRealNewPos != EE_PARA_NOT_FOUND, "ImpMoveParagraphs: Invalid Position!" ); + assert( nRealNewPos != EE_PARA_NOT_FOUND && "ImpMoveParagraphs: Invalid Position!" ); + // Add the paragraph portions and content nodes to a new position sal_Int32 i = 0; - while( aTmpPortionList.Count() > 0 ) + for (auto& pPortion : aParagraphPortionVector) { - std::unique_ptr<ParaPortion> pTmpPortion = aTmpPortionList.Release(0); - if ( i == 0 ) - aSelection.Min().SetNode( pTmpPortion->GetNode() ); - - aSelection.Max().SetNode( pTmpPortion->GetNode() ); - aSelection.Max().SetIndex( pTmpPortion->GetNode()->Len() ); + if (i == 0) + aSelection.Min().SetNode(pPortion->GetNode()); + aSelection.Max().SetNode(pPortion->GetNode()); + aSelection.Max().SetIndex(pPortion->GetNode()->Len()); - ContentNode* pN = pTmpPortion->GetNode(); - aEditDoc.Insert(nRealNewPos+i, pN); + maEditDoc.Insert(nRealNewPos + i, std::move(aContentNodeVector[i])); + GetParaPortions().Insert(nRealNewPos + i, std::move(pPortion)); - GetParaPortions().Insert(nRealNewPos+i, std::move(pTmpPortion)); ++i; } - aEndMovingParagraphsHdl.Call( aMoveParagraphsInfo ); + // Signal end of paragraph moving + maEndMovingParagraphsHdl.Call( aMoveParagraphsInfo ); if ( GetNotifyHdl().IsSet() ) { @@ -2195,19 +2257,19 @@ EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 n GetNotifyHdl().Call( aNotify ); } - aEditDoc.SetModified( true ); + maEditDoc.SetModified( true ); - if ( pRecalc1 ) - CalcHeight( pRecalc1 ); - if ( pRecalc2 ) - CalcHeight( pRecalc2 ); - if ( pRecalc3 ) - CalcHeight( pRecalc3 ); - if ( pRecalc4 ) - CalcHeight( pRecalc4 ); + if (pRecalc1) + CalcHeight(*pRecalc1); + if (pRecalc2) + CalcHeight(*pRecalc2); + if (pRecalc3) + CalcHeight(*pRecalc3); + if (pRecalc4) + CalcHeight(*pRecalc4); #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG - ParaPortionList::DbgCheck(GetParaPortions(), aEditDoc); + ParaPortionList::DbgCheck(GetParaPortions(), maEditDoc); #endif return aSelection; } @@ -2216,28 +2278,28 @@ EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 n EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward ) { OSL_ENSURE( pLeft != pRight, "Join together the same paragraph ?" ); - OSL_ENSURE( aEditDoc.GetPos( pLeft ) != EE_PARA_NOT_FOUND, "Inserted node not found (1)" ); - OSL_ENSURE( aEditDoc.GetPos( pRight ) != EE_PARA_NOT_FOUND, "Inserted node not found (2)" ); + OSL_ENSURE( maEditDoc.GetPos( pLeft ) != EE_PARA_NOT_FOUND, "Inserted node not found (1)" ); + OSL_ENSURE( maEditDoc.GetPos( pRight ) != EE_PARA_NOT_FOUND, "Inserted node not found (2)" ); // #i120020# it is possible that left and right are *not* in the desired order (left/right) // so correct it. This correction is needed, else an invalid SfxLinkUndoAction will be // created from ConnectParagraphs below. Assert this situation, it should be corrected by the // caller. - if(aEditDoc.GetPos( pLeft ) > aEditDoc.GetPos( pRight )) + if (maEditDoc.GetPos( pLeft ) > maEditDoc.GetPos( pRight )) { OSL_ENSURE(false, "ImpConnectParagraphs with wrong order of pLeft/pRight nodes (!)"); std::swap(pLeft, pRight); } - sal_Int32 nParagraphTobeDeleted = aEditDoc.GetPos( pRight ); - aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pRight, nParagraphTobeDeleted )); + sal_Int32 nParagraphTobeDeleted = maEditDoc.GetPos( pRight ); + maDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pRight, nParagraphTobeDeleted )); - GetEditEnginePtr()->ParagraphConnected( aEditDoc.GetPos( pLeft ), aEditDoc.GetPos( pRight ) ); + GetEditEnginePtr()->ParagraphConnected( maEditDoc.GetPos( pLeft ), maEditDoc.GetPos( pRight ) ); if ( IsUndoEnabled() && !IsInUndo() ) { - InsertUndo( std::make_unique<EditUndoConnectParas>(pEditEngine, - aEditDoc.GetPos( pLeft ), pLeft->Len(), + InsertUndo( std::make_unique<EditUndoConnectParas>(mpEditEngine, + maEditDoc.GetPos( pLeft ), pLeft->Len(), pLeft->GetContentAttribs().GetItems(), pRight->GetContentAttribs().GetItems(), pLeft->GetStyleSheet(), pRight->GetStyleSheet(), bBackward ) ); } @@ -2255,7 +2317,7 @@ EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pR // First search for Portions since pRight is gone after ConnectParagraphs. ParaPortion* pLeftPortion = FindParaPortion( pLeft ); - OSL_ENSURE( pLeftPortion, "Blind Portion in ImpConnectParagraphs(1)" ); + assert(pLeftPortion); if ( GetStatus().DoOnlineSpelling() ) { @@ -2279,7 +2341,7 @@ EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pR if ( IsCallParaInsertedOrDeleted() ) GetEditEnginePtr()->ParagraphDeleted( nParagraphTobeDeleted ); - EditPaM aPaM = aEditDoc.ConnectParagraphs( pLeft, pRight ); + EditPaM aPaM = maEditDoc.ConnectParagraphs( pLeft, pRight ); GetParaPortions().Remove( nParagraphTobeDeleted ); pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() ); @@ -2292,9 +2354,9 @@ EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pR // the change of the total text height too late... for ( sal_Int32 n = nParagraphTobeDeleted; n < GetParaPortions().Count(); n++ ) { - ParaPortion* pPP = GetParaPortions()[n]; - pPP->MarkSelectionInvalid( 0 ); - pPP->GetLines().Reset(); + ParaPortion& rParaPortion = GetParaPortions().getRef(n); + rParaPortion.MarkSelectionInvalid(0); + rParaPortion.GetLines().Reset(); } } @@ -2305,7 +2367,7 @@ EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pR EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 nMode, DeleteMode nDelMode ) { - OSL_ENSURE( !rSel.DbgIsBuggy( aEditDoc ), "Index out of range in DeleteLeftOrRight" ); + OSL_ENSURE( !rSel.DbgIsBuggy( maEditDoc ), "Index out of range in DeleteLeftOrRight" ); if ( rSel.HasRange() ) // only then Delete Selection return ImpDeleteSelection( rSel ); @@ -2318,17 +2380,15 @@ EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 n if ( nDelMode == DeleteMode::Simple ) { sal_uInt16 nCharMode = i18n::CharacterIteratorMode::SKIPCHARACTER; - // Check if we are deleting a CJK ideograph variance sequence (IVS). + // If we are deleting a variation selector, we want to delete the + // whole sequence (cell). sal_Int32 nIndex = aCurPos.GetIndex(); if (nIndex > 0) { const OUString& rString = aCurPos.GetNode()->GetString(); sal_Int32 nCode = rString.iterateCodePoints(&nIndex, -1); - if (unicode::isIVSSelector(nCode) && nIndex > 0 && - unicode::isCJKIVSCharacter(rString.iterateCodePoints(&nIndex, -1))) - { + if (unicode::isVariationSelector(nCode)) nCharMode = i18n::CharacterIteratorMode::SKIPCELL; - } } aDelStart = CursorLeft(aCurPos, nCharMode); } @@ -2402,18 +2462,20 @@ EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) return rCurSel.Min(); EditSelection aCurSel(rCurSel); - aCurSel.Adjust( aEditDoc ); + aCurSel.Adjust( maEditDoc ); EditPaM aStartPaM(aCurSel.Min()); EditPaM aEndPaM(aCurSel.Max()); - CursorMoved( aStartPaM.GetNode() ); // only so that newly set Attributes disappear... - CursorMoved( aEndPaM.GetNode() ); // only so that newly set Attributes disappear... + if( nullptr != aStartPaM.GetNode() ) + aStartPaM.GetNode()->checkAndDeleteEmptyAttribs(); // only so that newly set Attributes disappear... + if( nullptr != aEndPaM.GetNode() ) + aEndPaM.GetNode()->checkAndDeleteEmptyAttribs(); // only so that newly set Attributes disappear... OSL_ENSURE( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" ); OSL_ENSURE( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" ); - sal_Int32 nStartNode = aEditDoc.GetPos( aStartPaM.GetNode() ); - sal_Int32 nEndNode = aEditDoc.GetPos( aEndPaM.GetNode() ); + sal_Int32 nStartNode = maEditDoc.GetPos( aStartPaM.GetNode() ); + sal_Int32 nEndNode = maEditDoc.GetPos( aEndPaM.GetNode() ); OSL_ENSURE( nEndNode != EE_PARA_NOT_FOUND, "Start > End ?!" ); OSL_ENSURE( nStartNode <= nEndNode, "Start > End ?!" ); @@ -2430,7 +2492,7 @@ EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) // The Rest of the StartNodes... ImpRemoveChars( aStartPaM, aStartPaM.GetNode()->Len() - aStartPaM.GetIndex() ); ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() ); - OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(3)" ); + assert(pPortion); pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() ); // The beginning of the EndNodes... @@ -2438,7 +2500,7 @@ EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) aEndPaM.SetIndex( 0 ); ImpRemoveChars( aEndPaM, nChars ); pPortion = FindParaPortion( aEndPaM.GetNode() ); - OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(4)" ); + assert(pPortion); pPortion->MarkSelectionInvalid( 0 ); // Join together... aStartPaM = ImpConnectParagraphs( aStartPaM.GetNode(), aEndPaM.GetNode() ); @@ -2447,7 +2509,7 @@ EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) { ImpRemoveChars( aStartPaM, aEndPaM.GetIndex() - aStartPaM.GetIndex() ); ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() ); - OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(5)" ); + assert(pPortion); pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() ); } @@ -2458,15 +2520,15 @@ EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) void ImpEditEngine::ImpRemoveParagraph( sal_Int32 nPara ) { - ContentNode* pNode = aEditDoc.GetObject( nPara ); - ContentNode* pNextNode = aEditDoc.GetObject( nPara+1 ); + assert(maEditDoc.GetObject(nPara)); - OSL_ENSURE( pNode, "Blind Node in ImpRemoveParagraph" ); + ContentNode* pNextNode = maEditDoc.GetObject( nPara+1 ); - aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pNode, nPara )); + std::unique_ptr<ContentNode> pNode = maEditDoc.Release(nPara); + maDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>(pNode.get(), nPara)); // The node is managed by the undo and possibly destroyed! - aEditDoc.Release( nPara ); + GetParaPortions().Remove( nPara ); if ( IsCallParaInsertedOrDeleted() ) @@ -2480,14 +2542,15 @@ void ImpEditEngine::ImpRemoveParagraph( sal_Int32 nPara ) if ( pNextNode ) ParaAttribsChanged( pNextNode ); - if ( IsUndoEnabled() && !IsInUndo() ) - InsertUndo(std::make_unique<EditUndoDelContent>(pEditEngine, pNode, nPara)); + if (IsUndoEnabled() && !IsInUndo()) + { + InsertUndo(std::make_unique<EditUndoDelContent>(mpEditEngine, std::move(pNode), nPara)); + } else { - aEditDoc.RemoveItemsFromPool(*pNode); if ( pNode->GetStyleSheet() ) - EndListening( *pNode->GetStyleSheet() ); - delete pNode; + EndListening(*pNode->GetStyleSheet()); + pNode.reset(); } } @@ -2548,7 +2611,7 @@ EditPaM ImpEditEngine::AutoCorrect( const EditSelection& rCurSel, sal_Unicode c, ContentNode* pNode = aSel.Max().GetNode(); const sal_Int32 nIndex = aSel.Max().GetIndex(); - EdtAutoCorrDoc aAuto(pEditEngine, pNode, nIndex, c); + EdtAutoCorrDoc aAuto(mpEditEngine, pNode, nIndex, c); // FIXME: this _must_ be called with reference to the actual node text! OUString const& rNodeString(pNode->GetString()); pAutoCorrect->DoAutoCorrect( @@ -2588,7 +2651,7 @@ EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel, // If selected, then do not also overwrite a character! EditSelection aTmpSel( aPaM ); aTmpSel.Max().SetIndex( aTmpSel.Max().GetIndex()+1 ); - OSL_ENSURE( !aTmpSel.DbgIsBuggy( aEditDoc ), "Overwrite: Wrong selection! "); + OSL_ENSURE( !aTmpSel.DbgIsBuggy( maEditDoc ), "Overwrite: Wrong selection! "); ImpDeleteSelection( aTmpSel ); } @@ -2597,20 +2660,18 @@ EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel, if (IsInputSequenceCheckingRequired( c, rCurSel )) { uno::Reference < i18n::XExtendedInputSequenceChecker > _xISC( ImplGetInputSequenceChecker() ); - if (!pCTLOptions) - pCTLOptions.reset( new SvtCTLOptions ); if (_xISC) { const sal_Int32 nTmpPos = aPaM.GetIndex(); - sal_Int16 nCheckMode = pCTLOptions->IsCTLSequenceCheckingRestricted() ? + sal_Int16 nCheckMode = SvtCTLOptions::IsCTLSequenceCheckingRestricted() ? i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC; // the text that needs to be checked is only the one // before the current cursor position const OUString aOldText( aPaM.GetNode()->Copy(0, nTmpPos) ); OUString aNewText( aOldText ); - if (pCTLOptions->IsCTLSequenceCheckingTypeAndReplace()) + if (SvtCTLOptions::IsCTLSequenceCheckingTypeAndReplace()) { _xISC->correctInputSequence(aNewText, nTmpPos - 1, c, nCheckMode); @@ -2647,14 +2708,14 @@ EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel, if ( IsUndoEnabled() && !IsInUndo() ) { - std::unique_ptr<EditUndoInsertChars> pNewUndo(new EditUndoInsertChars(pEditEngine, CreateEPaM(aPaM), OUString(c))); + std::unique_ptr<EditUndoInsertChars> pNewUndo(new EditUndoInsertChars(mpEditEngine, CreateEPaM(aPaM), OUString(c))); bool bTryMerge = !bDoOverwrite && ( c != ' ' ); InsertUndo( std::move(pNewUndo), bTryMerge ); } - aEditDoc.InsertText( aPaM, OUString(c) ); + maEditDoc.InsertText( aPaM, OUStringChar(c) ); ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); - OSL_ENSURE( pPortion, "Blind Portion in InsertText" ); + assert(pPortion); pPortion->MarkInvalid( aPaM.GetIndex(), 1 ); aPaM.SetIndex( aPaM.GetIndex()+1 ); // does not do EditDoc-Method anymore } @@ -2688,7 +2749,7 @@ EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUStrin aCurWord = SelectWord( aCurPaM, i18n::WordType::DICTIONARY_WORD ); OUString aText(convertLineEnd(rStr, LINEEND_LF)); - if (utl::ConfigManager::IsFuzzing()) //tab expansion performance in editeng is appalling + if (mbFuzzing) //tab expansion performance in editeng is appalling aText = aText.replaceAll("\t","-"); SfxVoidItem aTabItem( EE_FEATURE_TAB ); @@ -2699,7 +2760,8 @@ EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUStrin sal_Int32 nStart = 0; while ( nStart < aText.getLength() ) { - sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart ); + sal_Int32 nEnd = !maStatus.IsSingleLine() ? + aText.indexOf( LINE_SEP, nStart ) : -1; if ( nEnd == -1 ) nEnd = aText.getLength(); // not dereference! @@ -2712,14 +2774,64 @@ EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUStrin if (nChars > MAXCHARSINPARA) { sal_Int32 nMaxNewChars = std::max<sal_Int32>(0, MAXCHARSINPARA - nExistingChars); - nEnd -= ( aLine.getLength() - nMaxNewChars ); // Then the characters end up in the next paragraph. - aLine = aLine.copy( 0, nMaxNewChars ); // Delete the Rest... + // Wherever we break, it may be wrong. However, try to find the + // previous non-alnum/non-letter character. Note this is only + // in the to be appended data, otherwise already existing + // characters would have to be moved and PaM to be updated. + // Restrict to 2*42, if not found by then assume other data or + // language-script uses only letters or idiographs. + sal_Int32 nPos = nMaxNewChars; + while (nPos-- > 0 && (nMaxNewChars - nPos) <= 84) + { + auto nNextPos = nPos; + const auto c = aLine.iterateCodePoints(&nNextPos); + switch (unicode::getUnicodeType(c)) + { + case css::i18n::UnicodeType::UPPERCASE_LETTER: + case css::i18n::UnicodeType::LOWERCASE_LETTER: + case css::i18n::UnicodeType::TITLECASE_LETTER: + case css::i18n::UnicodeType::MODIFIER_LETTER: + case css::i18n::UnicodeType::OTHER_LETTER: + case css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER: + case css::i18n::UnicodeType::LETTER_NUMBER: + case css::i18n::UnicodeType::OTHER_NUMBER: + case css::i18n::UnicodeType::CURRENCY_SYMBOL: + break; + default: + { + // Ignore NO-BREAK spaces, NBSP, NNBSP, ZWNBSP. + if (c == 0x00A0 || c == 0x202F || c == 0xFEFF) + break; + const auto n = aLine.iterateCodePoints(&nNextPos, 0); + if (c == '-' && nNextPos < nMaxNewChars) + { + // Keep HYPHEN-MINUS with a number to the right. + const sal_Int16 t = unicode::getUnicodeType(n); + if ( t == css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER || + t == css::i18n::UnicodeType::LETTER_NUMBER || + t == css::i18n::UnicodeType::OTHER_NUMBER) + nMaxNewChars = nPos; // line break before + else + nMaxNewChars = nNextPos; // line break after + } + else + { + nMaxNewChars = nNextPos; // line break after + } + nPos = 0; // will break loop + } + } + } + // Remaining characters end up in the next paragraph. Note that + // new nStart will be nEnd+1 below so decrement by one more. + nEnd -= (aLine.getLength() - nMaxNewChars + 1); + aLine = aLine.copy( 0, nMaxNewChars ); // Delete the Rest... } if ( IsUndoEnabled() && !IsInUndo() ) - InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), aLine)); + InsertUndo(std::make_unique<EditUndoInsertChars>(mpEditEngine, CreateEPaM(aPaM), aLine)); // Tabs ? if ( aLine.indexOf( '\t' ) == -1 ) - aPaM = aEditDoc.InsertText( aPaM, aLine ); + aPaM = maEditDoc.InsertText( aPaM, aLine ); else { sal_Int32 nStart2 = 0; @@ -2730,16 +2842,16 @@ EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUStrin nEnd2 = aLine.getLength(); // not dereference! if ( nEnd2 > nStart2 ) - aPaM = aEditDoc.InsertText( aPaM, aLine.copy( nStart2, nEnd2-nStart2 ) ); + aPaM = maEditDoc.InsertText( aPaM, aLine.subView( nStart2, nEnd2-nStart2 ) ); if ( nEnd2 < aLine.getLength() ) { - aPaM = aEditDoc.InsertFeature( aPaM, aTabItem ); + aPaM = maEditDoc.InsertFeature( aPaM, aTabItem ); } nStart2 = nEnd2+1; } } ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); - OSL_ENSURE( pPortion, "Blind Portion in InsertText" ); + assert(pPortion); if ( GetStatus().DoOnlineSpelling() ) { @@ -2774,9 +2886,9 @@ EditPaM ImpEditEngine::ImpFastInsertText( EditPaM aPaM, const OUString& rStr ) if ( ( aPaM.GetNode()->Len() + rStr.getLength() ) < MAXCHARSINPARA ) { if ( IsUndoEnabled() && !IsInUndo() ) - InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), rStr)); + InsertUndo(std::make_unique<EditUndoInsertChars>(mpEditEngine, CreateEPaM(aPaM), rStr)); - aPaM = aEditDoc.InsertText( aPaM, rStr ); + aPaM = maEditDoc.InsertText( aPaM, rStr ); TextModified(); } else @@ -2799,12 +2911,12 @@ EditPaM ImpEditEngine::ImpInsertFeature(const EditSelection& rCurSel, const SfxP return aPaM; if ( IsUndoEnabled() && !IsInUndo() ) - InsertUndo(std::make_unique<EditUndoInsertFeature>(pEditEngine, CreateEPaM(aPaM), rItem)); - aPaM = aEditDoc.InsertFeature( aPaM, rItem ); + InsertUndo(std::make_unique<EditUndoInsertFeature>(mpEditEngine, CreateEPaM(aPaM), rItem)); + aPaM = maEditDoc.InsertFeature( aPaM, rItem ); UpdateFields(); ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); - OSL_ENSURE( pPortion, "Blind Portion in InsertFeature" ); + assert(pPortion); pPortion->MarkInvalid( aPaM.GetIndex()-1, 1 ); TextModified(); @@ -2825,7 +2937,7 @@ EditPaM ImpEditEngine::ImpInsertParaBreak( const EditSelection& rCurSel ) EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs ) { - if ( aEditDoc.Count() >= EE_PARA_MAX_COUNT ) + if ( maEditDoc.Count() >= EE_PARA_MAX_COUNT ) { SAL_WARN( "editeng", "ImpEditEngine::ImpInsertParaBreak - can't process more than " << EE_PARA_MAX_COUNT << " paragraphs!"); @@ -2833,9 +2945,11 @@ EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttrib } if ( IsUndoEnabled() && !IsInUndo() ) - InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, aEditDoc.GetPos(rPaM.GetNode()), rPaM.GetIndex())); + InsertUndo(std::make_unique<EditUndoSplitPara>(mpEditEngine, maEditDoc.GetPos(rPaM.GetNode()), rPaM.GetIndex())); - EditPaM aPaM( aEditDoc.InsertParaBreak( rPaM, bKeepEndingAttribs ) ); + EditPaM aPaM( maEditDoc.InsertParaBreak( rPaM, bKeepEndingAttribs ) ); + if (auto pStyle = aPaM.GetNode()->GetStyleSheet()) + StartListening(*pStyle, DuplicateHandling::Allow); if ( GetStatus().DoOnlineSpelling() ) { @@ -2868,7 +2982,7 @@ EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttrib } ParaPortion* pPortion = FindParaPortion( rPaM.GetNode() ); - OSL_ENSURE( pPortion, "Blind Portion in ImpInsertParaBreak" ); + assert(pPortion); pPortion->MarkInvalid( rPaM.GetIndex(), 0 ); // Optimization: Do not place unnecessarily many getPos to Listen! @@ -2880,7 +2994,9 @@ EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttrib if ( IsCallParaInsertedOrDeleted() ) GetEditEnginePtr()->ParagraphInserted( nPos+1 ); - CursorMoved( rPaM.GetNode() ); // if empty Attributes have emerged. + if( nullptr != rPaM.GetNode() ) + rPaM.GetNode()->checkAndDeleteEmptyAttribs(); // if empty Attributes have emerged. + TextModified(); return aPaM; } @@ -2891,21 +3007,21 @@ EditPaM ImpEditEngine::ImpFastInsertParagraph( sal_Int32 nPara ) { if ( nPara ) { - OSL_ENSURE( aEditDoc.GetObject( nPara-1 ), "FastInsertParagraph: Prev does not exist" ); - InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, nPara-1, aEditDoc.GetObject( nPara-1 )->Len())); + assert(maEditDoc.GetObject(nPara - 1)); + InsertUndo(std::make_unique<EditUndoSplitPara>(mpEditEngine, nPara-1, maEditDoc.GetObject(nPara - 1)->Len())); } else - InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, 0, 0)); + InsertUndo(std::make_unique<EditUndoSplitPara>(mpEditEngine, 0, 0)); } - ContentNode* pNode = new ContentNode( aEditDoc.GetItemPool() ); + ContentNode* pNode = new ContentNode( maEditDoc.GetItemPool() ); // If flat mode, then later no Font is set: - pNode->GetCharAttribs().GetDefFont() = aEditDoc.GetDefFont(); + pNode->GetCharAttribs().GetDefFont() = maEditDoc.GetDefFont(); if ( GetStatus().DoOnlineSpelling() ) pNode->CreateWrongList(); - aEditDoc.Insert(nPara, pNode); + maEditDoc.Insert(nPara, std::unique_ptr<ContentNode>(pNode)); GetParaPortions().Insert(nPara, std::make_unique<ParaPortion>( pNode )); if ( IsCallParaInsertedOrDeleted() ) @@ -2917,9 +3033,9 @@ EditPaM ImpEditEngine::ImpFastInsertParagraph( sal_Int32 nPara ) EditPaM ImpEditEngine::InsertParaBreak(const EditSelection& rCurSel) { EditPaM aPaM(ImpInsertParaBreak(rCurSel)); - if ( aStatus.DoAutoIndenting() ) + if ( maStatus.DoAutoIndenting() ) { - sal_Int32 nPara = aEditDoc.GetPos( aPaM.GetNode() ); + sal_Int32 nPara = maEditDoc.GetPos( aPaM.GetNode() ); OSL_ENSURE( nPara > 0, "AutoIndenting: Error!" ); const OUString aPrevParaText( GetEditDoc().GetParaAsString( nPara-1 ) ); sal_Int32 n = 0; @@ -2956,7 +3072,7 @@ bool ImpEditEngine::UpdateFields() { bool bChangesInPara = false; ContentNode* pNode = GetEditDoc().GetObject( nPara ); - OSL_ENSURE( pNode, "NULL-Pointer in Doc" ); + assert(pNode); CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs) { @@ -2964,13 +3080,13 @@ bool ImpEditEngine::UpdateFields() if (rAttr.Which() == EE_FEATURE_FIELD) { EditCharAttribField& rField = static_cast<EditCharAttribField&>(rAttr); - std::unique_ptr<EditCharAttribField> pCurrent(new EditCharAttribField(rField)); + EditCharAttribField aCurrent(rField); rField.Reset(); - if (!aStatus.MarkNonUrlFields() && !aStatus.MarkUrlFields()) + if (!maStatus.MarkNonUrlFields() && !maStatus.MarkUrlFields()) ; // nothing marked - else if (aStatus.MarkNonUrlFields() && aStatus.MarkUrlFields()) - rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor; + else if (maStatus.MarkNonUrlFields() && maStatus.MarkUrlFields()) + rField.GetFieldColor() = GetColorConfig().GetColorValue(svtools::WRITERFIELDSHADINGS).nColor; else { bool bURL = false; @@ -2979,17 +3095,17 @@ bool ImpEditEngine::UpdateFields() if (const SvxFieldData* pFieldData = pFieldItem->GetField()) bURL = (dynamic_cast<const SvxURLField* >(pFieldData) != nullptr); } - if ((bURL && aStatus.MarkUrlFields()) || (!bURL && aStatus.MarkNonUrlFields())) + if ((bURL && maStatus.MarkUrlFields()) || (!bURL && maStatus.MarkNonUrlFields())) rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor; } const OUString aFldValue = GetEditEnginePtr()->CalcFieldValue( static_cast<const SvxFieldItem&>(*rField.GetItem()), - nPara, rField.GetStart(), rField.GetTextColor(), rField.GetFieldColor()); + nPara, rField.GetStart(), rField.GetTextColor(), rField.GetFieldColor(), rField.GetFldLineStyle() ); rField.SetFieldValue(aFldValue); - if (rField != *pCurrent) + if (rField != aCurrent) { bChanges = true; bChangesInPara = true; @@ -2999,9 +3115,9 @@ bool ImpEditEngine::UpdateFields() if ( bChangesInPara ) { // If possible be more precise when invalidate. - ParaPortion* pPortion = GetParaPortions()[nPara]; - OSL_ENSURE( pPortion, "NULL-Pointer in Doc" ); - pPortion->MarkSelectionInvalid( 0 ); + assert(GetParaPortions().exists(nPara)); + ParaPortion& rPortion = GetParaPortions().getRef(nPara); + rPortion.MarkSelectionInvalid( 0 ); } } return bChanges; @@ -3016,78 +3132,251 @@ EditPaM ImpEditEngine::InsertLineBreak(const EditSelection& aCurSel) // Helper functions -tools::Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags ) +tools::Rectangle ImpEditEngine::GetEditCursor(ParaPortion const& rPortion, EditLine const& rLine, + sal_Int32 nIndex, CursorFlags aFlags) +{ + // nIndex might be not in the line + // Search within the line... + tools::Long nX; + + if (nIndex == rLine.GetStart() && aFlags.bStartOfLine) + { + Range aXRange = GetLineXPosStartEnd(rPortion, rLine); + nX = !IsRightToLeft(GetEditDoc().GetPos(rPortion.GetNode())) ? aXRange.Min() + : aXRange.Max(); + } + else if (nIndex == rLine.GetEnd() && aFlags.bEndOfLine) + { + Range aXRange = GetLineXPosStartEnd(rPortion, rLine); + nX = !IsRightToLeft(GetEditDoc().GetPos(rPortion.GetNode())) ? aXRange.Max() + : aXRange.Min(); + } + else + { + nX = GetXPos(rPortion, rLine, nIndex, aFlags.bPreferPortionStart); + } + + tools::Rectangle aEditCursor; + aEditCursor.SetLeft(nX); + aEditCursor.SetRight(nX); + + aEditCursor.SetBottom(rLine.GetHeight() - 1); + if (aFlags.bTextOnly) + aEditCursor.SetTop(aEditCursor.Bottom() - rLine.GetTxtHeight() + 1); + else + aEditCursor.SetTop(aEditCursor.Bottom() - std::min(rLine.GetTxtHeight(), rLine.GetHeight()) + 1); + return aEditCursor; +} + +tools::Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, CursorFlags aFlags) { - OSL_ENSURE( GetUpdateMode(), "Must not be reached when Update=FALSE: PaMtoEditCursor" ); + assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: PaMtoEditCursor" ); tools::Rectangle aEditCursor; - tools::Long nY = 0; - for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) + const sal_Int32 nIndex = aPaM.GetIndex(); + const ParaPortion* pPortion = nullptr; + const EditLine* pLastLine = nullptr; + tools::Rectangle aLineArea; + + auto FindPortionLineAndArea = [&, bEOL(aFlags.bEndOfLine)](const LineAreaInfo& rInfo) { - ParaPortion* pPortion = GetParaPortions()[nPortion]; - ContentNode* pNode = pPortion->GetNode(); - OSL_ENSURE( pNode, "Invalid Node in Portion!" ); - if ( pNode != aPaM.GetNode() ) + if (!rInfo.pLine) // start of ParaPortion { - nY += pPortion->GetHeight(); + ContentNode* pNode = rInfo.rPortion.GetNode(); + OSL_ENSURE(pNode, "Invalid Node in Portion!"); + if (pNode != aPaM.GetNode()) + return CallbackResult::SkipThisPortion; + pPortion = &rInfo.rPortion; } - else + else // guaranteed that this is the correct ParaPortion { - aEditCursor = GetEditCursor( pPortion, aPaM.GetIndex(), nFlags ); - aEditCursor.AdjustTop(nY ); - aEditCursor.AdjustBottom(nY ); - return aEditCursor; + pLastLine = rInfo.pLine; + aLineArea = rInfo.aArea; + if ((rInfo.pLine->GetStart() == nIndex) || (rInfo.pLine->IsIn(nIndex, bEOL))) + return CallbackResult::Stop; } + return CallbackResult::Continue; + }; + IterateLineAreas(FindPortionLineAndArea, IterFlag::none); + + if (pLastLine && pPortion) + { + aEditCursor = GetEditCursor(*pPortion, *pLastLine, nIndex, aFlags); + aEditCursor.Move(getTopLeftDocOffset(aLineArea)); } - OSL_FAIL( "Portion not found!" ); + else + OSL_FAIL("Line not found!"); + return aEditCursor; } -EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart ) +void ImpEditEngine::IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions) { - OSL_ENSURE( GetUpdateMode(), "Must not be reached when Update=FALSE: GetPaM" ); - - tools::Long nY = 0; - EditPaM aPaM; - sal_Int32 nPortion; - for ( nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) - { - ParaPortion* pPortion = GetParaPortions()[nPortion]; - const tools::Long nTmpHeight = pPortion->GetHeight(); // should also be correct for !bVisible! - nY += nTmpHeight; - if ( nY > aDocPos.Y() ) + const Point aOrigin(0, 0); + Point aLineStart(aOrigin); + const tools::Long nVertLineSpacing = CalcVertLineSpacing(aLineStart); + const tools::Long nColumnWidth = GetColumnWidth(maPaperSize); + sal_Int16 nColumn = 0; + for (sal_Int32 n = 0, nPortions = GetParaPortions().Count(); n < nPortions; ++n) + { + ParaPortion& rPortion = GetParaPortions().getRef(n); + bool bSkipThis = true; + if (rPortion.IsVisible()) { - nY -= nTmpHeight; - aDocPos.AdjustY( -nY ); - // Skip invisible Portions: - while ( pPortion && !pPortion->IsVisible() ) + // when typing idle formatting, asynchronous Paint. Invisible Portions may be invalid. + if (rPortion.IsInvalid()) + return; + + LineAreaInfo aInfo{ + rPortion, + nullptr, // pLine + 0, // nHeightNeededToNotWrap + { aLineStart, Size{ nColumnWidth, rPortion.GetFirstLineOffset() } }, // aArea + n, // nPortion + 0, // nLine + nColumn // nColumn + }; + auto eResult = f(aInfo); + if (eResult == CallbackResult::Stop) + return; + bSkipThis = eResult == CallbackResult::SkipThisPortion; + + sal_uInt16 nSBL = 0; + if (!maStatus.IsOutliner()) { - nPortion++; - pPortion = GetParaPortions().SafeGetObject( nPortion ); + const SvxLineSpacingItem& rLSItem + = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); + nSBL = (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix) + ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) + : 0; } - SAL_WARN_IF(!pPortion, "editeng", "worrying lack of any visible paragraph"); - if (!pPortion) - return aPaM; - return GetPaM(pPortion, aDocPos, bSmart); + adjustYDirectionAware(aLineStart, rPortion.GetFirstLineOffset()); + for (sal_Int32 nLine = 0, nLines = rPortion.GetLines().Count(); nLine < nLines; nLine++) + { + EditLine& rLine = rPortion.GetLines()[nLine]; + tools::Long nLineHeight = rLine.GetHeight(); + if (nLine != nLines - 1) + nLineHeight += nVertLineSpacing; + MoveToNextLine(aLineStart, nLineHeight, nColumn, aOrigin, + &aInfo.nHeightNeededToNotWrap); + const bool bInclILS = eOptions & IterFlag::inclILS; + if (bInclILS && (nLine != nLines - 1) && !maStatus.IsOutliner()) + { + adjustYDirectionAware(aLineStart, nSBL); + nLineHeight += nSBL; + } + + if (!bSkipThis) + { + Point aOtherCorner(aLineStart); + adjustXDirectionAware(aOtherCorner, nColumnWidth); + adjustYDirectionAware(aOtherCorner, -nLineHeight); + + // Calls to f() for each line + aInfo.nColumn = nColumn; + aInfo.pLine = &rLine; + aInfo.nLine = nLine; + aInfo.aArea = tools::Rectangle::Normalize(aLineStart, aOtherCorner); + eResult = f(aInfo); + if (eResult == CallbackResult::Stop) + return; + bSkipThis = eResult == CallbackResult::SkipThisPortion; + } + + if (!bInclILS && (nLine != nLines - 1) && !maStatus.IsOutliner()) + adjustYDirectionAware(aLineStart, nSBL); + } + if (!maStatus.IsOutliner()) + { + const SvxULSpaceItem& rULItem = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); + tools::Long nUL = scaleYSpacingValue(rULItem.GetLower()); + adjustYDirectionAware(aLineStart, nUL); + } } + // Invisible ParaPortion has no height (see ParaPortion::GetHeight), don't handle it } - // Then search for the last visible: - nPortion = GetParaPortions().Count()-1; - while ( nPortion && !GetParaPortions()[nPortion]->IsVisible() ) - nPortion--; +} - OSL_ENSURE( GetParaPortions()[nPortion]->IsVisible(), "No visible paragraph found: GetPaM" ); - aPaM.SetNode( GetParaPortions()[nPortion]->GetNode() ); - aPaM.SetIndex( GetParaPortions()[nPortion]->GetNode()->Len() ); - return aPaM; +std::tuple<const ParaPortion*, const EditLine*, tools::Long> +ImpEditEngine::GetPortionAndLine(Point aDocPos) +{ + // First find the column from the point + sal_Int32 nClickColumn = 0; + for (tools::Long nColumnStart = 0, nColumnWidth = GetColumnWidth(maPaperSize);; + nColumnStart += mnColumnSpacing + nColumnWidth, ++nClickColumn) + { + if (aDocPos.X() <= nColumnStart + nColumnWidth + mnColumnSpacing / 2) + break; + if (nClickColumn >= mnColumns - 1) + break; + } + + const ParaPortion* pLastPortion = nullptr; + const EditLine* pLastLine = nullptr; + tools::Long nLineStartX = 0; + Point aPos; + adjustYDirectionAware(aPos, aDocPos.Y()); + + auto FindLastMatchingPortionAndLine = [&](const LineAreaInfo& rInfo) { + if (rInfo.pLine) // Only handle lines, not ParaPortion starts + { + if (rInfo.nColumn > nClickColumn) + return CallbackResult::Stop; + pLastPortion = &rInfo.rPortion; // Candidate paragraph + pLastLine = rInfo.pLine; // Last visible line not later than click position + nLineStartX = getTopLeftDocOffset(rInfo.aArea).Width(); + if (rInfo.nColumn == nClickColumn && getYOverflowDirectionAware(aPos, rInfo.aArea) == 0) + return CallbackResult::Stop; // Found it + } + return CallbackResult::Continue; + }; + IterateLineAreas(FindLastMatchingPortionAndLine, IterFlag::inclILS); + + return { pLastPortion, pLastLine, nLineStartX }; +} + +EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart ) +{ + assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: GetPaM" ); + + if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(aDocPos); pPortion) + { + assert(pLine); + assert(pPortion); + sal_Int32 nCurIndex = GetChar(*pPortion, *pLine, aDocPos.X() - nLineStartX, bSmart); + EditPaM aPaM(pPortion->GetNode(), nCurIndex); + + if (nCurIndex && (nCurIndex == pLine->GetEnd()) + && (pLine != &pPortion->GetLines()[pPortion->GetLines().Count() - 1])) + { + aPaM = CursorLeft(aPaM); + } + + return aPaM; + } + return {}; +} + +bool ImpEditEngine::IsTextPos(const Point& rDocPos, sal_uInt16 nBorder) +{ + if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(rDocPos); pPortion) + { + assert(pLine); + assert(pPortion); + Range aLineXPosStartEnd = GetLineXPosStartEnd(*pPortion, *pLine); + if ((rDocPos.X() >= nLineStartX + aLineXPosStartEnd.Min() - nBorder) + && (rDocPos.X() <= nLineStartX + aLineXPosStartEnd.Max() + nBorder)) + return true; + } + return false; } sal_uInt32 ImpEditEngine::GetTextHeight() const { - OSL_ENSURE( GetUpdateMode(), "Should not be used for Update=FALSE: GetTextHeight" ); + assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" ); OSL_ENSURE( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); - return nCurTextHeight; + return mnCurTextHeight; } sal_uInt32 ImpEditEngine::CalcTextWidth( bool bIgnoreExtraSpace ) @@ -3121,8 +3410,8 @@ sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace // Over all the paragraphs ... - OSL_ENSURE( 0 <= nPara && nPara < GetParaPortions().Count(), "CalcParaWidth: Out of range" ); - ParaPortion* pPortion = GetParaPortions()[nPara]; + OSL_ENSURE(GetParaPortions().exists(nPara), "CalcParaWidth: Out of range"); + ParaPortion* pPortion = GetParaPortions().SafeGetObject(nPara); if ( pPortion && pPortion->IsVisible() ) { const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pPortion->GetNode() ); @@ -3134,16 +3423,16 @@ sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace sal_Int32 nLines = pPortion->GetLines().Count(); for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ ) { - EditLine& rLine = pPortion->GetLines()[nLine]; + EditLine const& rLine = pPortion->GetLines()[nLine]; // nCurWidth = pLine->GetStartPosX(); // For Center- or Right- alignment it depends on the paper // width, here not preferred. I general, it is best not leave it // to StartPosX, also the right indents have to be taken into // account! - tools::Long nCurWidth = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth ); + tools::Long nCurWidth = scaleXSpacingValue(rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth); if ( nLine == 0 ) { - tools::Long nFI = GetXValue( rLRItem.GetTextFirstLineOffset() ); + tools::Long nFI = scaleXSpacingValue(rLRItem.GetTextFirstLineOffset()); nCurWidth -= nFI; if ( pPortion->GetBulletX() > nCurWidth ) { @@ -3152,8 +3441,8 @@ sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace nCurWidth = pPortion->GetBulletX(); } } - nCurWidth += GetXValue( rLRItem.GetRight() ); - nCurWidth += CalcLineWidth( pPortion, &rLine, bIgnoreExtraSpace ); + nCurWidth += scaleXSpacingValue(rLRItem.GetRight()); + nCurWidth += CalcLineWidth(*pPortion, rLine, bIgnoreExtraSpace); if ( nCurWidth > nMaxWidth ) { nMaxWidth = nCurWidth; @@ -3165,24 +3454,24 @@ sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace return static_cast<sal_uInt32>(nMaxWidth); } -sal_uInt32 ImpEditEngine::CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace ) +sal_uInt32 ImpEditEngine::CalcLineWidth(ParaPortion const& rPortion, EditLine const& rLine, bool bIgnoreExtraSpace) { - sal_Int32 nPara = GetEditDoc().GetPos( pPortion->GetNode() ); + sal_Int32 nPara = GetEditDoc().GetPos(rPortion.GetNode()); // #114278# Saving both layout mode and language (since I'm // potentially changing both) - GetRefDevice()->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE ); + GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE ); - ImplInitLayoutMode( GetRefDevice(), nPara, -1 ); + ImplInitLayoutMode(*GetRefDevice(), nPara, -1); SvxAdjust eJustification = GetJustification( nPara ); // Calculation of the width without the Indents ... sal_uInt32 nWidth = 0; - sal_Int32 nPos = pLine->GetStart(); - for ( sal_Int32 nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ ) + sal_Int32 nPos = rLine.GetStart(); + for ( sal_Int32 nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ ) { - const TextPortion& rTextPortion = pPortion->GetTextPortions()[nTP]; + const TextPortion& rTextPortion = rPortion.GetTextPortions()[nTP]; switch ( rTextPortion.GetKind() ) { case PortionKind::FIELD: @@ -3200,11 +3489,12 @@ sal_uInt32 ImpEditEngine::CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, } else { - SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() ); - SeekCursor( pPortion->GetNode(), nPos+1, aTmpFont ); - aTmpFont.SetPhysFont( GetRefDevice() ); - ImplInitDigitMode(GetRefDevice(), aTmpFont.GetLanguage()); - nWidth += aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(), nPos, rTextPortion.GetLen() ).Width(); + SvxFont aTmpFont(rPortion.GetNode()->GetCharAttribs().GetDefFont()); + SeekCursor(rPortion.GetNode(), nPos + 1, aTmpFont); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + nWidth += aTmpFont.QuickGetTextSize( GetRefDevice(), + rPortion.GetNode()->GetString(), nPos, rTextPortion.GetLen(), nullptr ).Width(); } } break; @@ -3220,39 +3510,130 @@ sal_uInt32 ImpEditEngine::CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, sal_uInt32 ImpEditEngine::GetTextHeightNTP() const { - DBG_ASSERT( GetUpdateMode(), "Should not be used for Update=FALSE: GetTextHeight" ); + assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" ); DBG_ASSERT( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); - return nCurTextHeightNTP; + return mnCurTextHeightNTP; } -sal_uInt32 ImpEditEngine::CalcTextHeight( sal_uInt32* pHeightNTP ) +tools::Long ImpEditEngine::Calc1ColumnTextHeight(tools::Long* pHeightNTP) { - OSL_ENSURE( GetUpdateMode(), "Should not be used when Update=FALSE: CalcTextHeight" ); - sal_uInt32 nY = 0; - sal_uInt32 nPH; - sal_uInt32 nEmptyHeight = 0; - for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) { - ParaPortion* pPortion = GetParaPortions()[nPortion]; - nPH = pPortion->GetHeight(); - nY += nPH; - if( pHeightNTP ) { - if ( pPortion->IsEmpty() ) - nEmptyHeight += nPH; - else - nEmptyHeight = 0; + tools::Long nHeight = 0; + if (pHeightNTP) + *pHeightNTP = 0; + // Pretend that we have ~infinite height to get total height + comphelper::ValueRestorationGuard aGuard(mnCurTextHeight, std::numeric_limits<tools::Long>::max()); + + IterateLinesAreasFunc FindLastLineBottom = [&](const LineAreaInfo& rInfo) { + if (rInfo.pLine) + { + // bottom coordinate does not belong to area, so no need to do +1 + nHeight = getBottomDocOffset(rInfo.aArea); + if (pHeightNTP && !rInfo.rPortion.IsEmpty()) + *pHeightNTP = nHeight; } - } - - if ( pHeightNTP ) - *pHeightNTP = nY - nEmptyHeight; + return CallbackResult::Continue; + }; + IterateLineAreas(FindLastLineBottom, IterFlag::none); + return nHeight; +} - return nY; +tools::Long ImpEditEngine::CalcTextHeight(tools::Long* pHeightNTP) +{ + assert( IsUpdateLayout() && "Should not be used when Update=FALSE: CalcTextHeight" ); + + if (mnColumns <= 1) + return Calc1ColumnTextHeight(pHeightNTP); // All text fits into a single column - done! + + // The final column height can be smaller than total height divided by number of columns (taking + // into account first line offset and interline spacing, that aren't considered in positioning + // after the wrap). The wrap should only happen after the minimal height is exceeded. + tools::Long nTentativeColHeight = mnMinColumnWrapHeight; + tools::Long nWantedIncrease = 0; + tools::Long nCurrentTextHeight; + + // This does the necessary column balancing for the case when the text does not fit min height. + // When the height of column (taken from mnCurTextHeight) is too small, the last column will + // overflow, so the resulting height of the text will exceed the set column height. Increasing + // the column height step by step by the minimal value that allows one of columns to accommodate + // one line more, we finally get to the point where all the text fits. At each iteration, the + // height is only increased, so it's impossible to have infinite layout loops. The found value + // is the global minimum. + // + // E.g., given the following four line heights: + // Line 1: 10; + // Line 2: 12; + // Line 3: 10; + // Line 4: 10; + // number of columns 3, and the minimal paper height of 5, the iterations would be: + // * Tentative column height is set to 5 + // <ITERATION 1> + // * Line 1 is attempted to go to column 0. Overflow is 5 => moved to column 1. + // * Line 2 is attempted to go to column 1 after Line 1; overflow is 17 => moved to column 2. + // * Line 3 is attempted to go to column 2 after Line 2; overflow is 17, stays in max column 2. + // * Line 4 goes to column 2 after Line 3. + // * Final iteration columns are: {empty}, {Line 1}, {Line 2, Line 3, Line 4} + // * Total text height is max({0, 10, 32}) == 32 > Tentative column height 5 => NEXT ITERATION + // * Minimal height increase that allows at least one column to accommodate one more line is + // min({5, 17, 17}) = 5. + // * Tentative column height is set to 5 + 5 = 10. + // <ITERATION 2> + // * Line 1 goes to column 0, no overflow. + // * Line 2 is attempted to go to column 0 after Line 1; overflow is 12 => moved to column 1. + // * Line 3 is attempted to go to column 1 after Line 2; overflow is 12 => moved to column 2. + // * Line 4 is attempted to go to column 2 after Line 3; overflow is 10, stays in max column 2. + // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} + // * Total text height is max({10, 12, 20}) == 20 > Tentative column height 10 => NEXT ITERATION + // * Minimal height increase that allows at least one column to accommodate one more line is + // min({12, 12, 10}) = 10. + // * Tentative column height is set to 10 + 10 == 20. + // <ITERATION 3> + // * Line 1 goes to column 0, no overflow. + // * Line 2 is attempted to go to column 0 after Line 1; overflow is 2 => moved to column 1. + // * Line 3 is attempted to go to column 1 after Line 2; overflow is 2 => moved to column 2. + // * Line 4 is attempted to go to column 2 after Line 3; no overflow. + // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} + // * Total text height is max({10, 12, 20}) == 20 == Tentative column height 20 => END. + do + { + nTentativeColHeight += nWantedIncrease; + nWantedIncrease = std::numeric_limits<tools::Long>::max(); + nCurrentTextHeight = 0; + if (pHeightNTP) + *pHeightNTP = 0; + auto GetHeightAndWantedIncrease = [&, minHeight = tools::Long(0), lastCol = sal_Int16(0)]( + const LineAreaInfo& rInfo) mutable { + if (rInfo.pLine) + { + if (lastCol != rInfo.nColumn) + { + minHeight = std::max(nCurrentTextHeight, + minHeight); // total height can't be less than previous columns + nWantedIncrease = std::min(rInfo.nHeightNeededToNotWrap, nWantedIncrease); + lastCol = rInfo.nColumn; + } + // bottom coordinate does not belong to area, so no need to do +1 + nCurrentTextHeight = std::max(getBottomDocOffset(rInfo.aArea), minHeight); + if (pHeightNTP) + { + if (rInfo.rPortion.IsEmpty()) + *pHeightNTP = std::max(*pHeightNTP, minHeight); + else + *pHeightNTP = nCurrentTextHeight; + } + } + return CallbackResult::Continue; + }; + comphelper::ValueRestorationGuard aGuard(mnCurTextHeight, nTentativeColHeight); + IterateLineAreas(GetHeightAndWantedIncrease, IterFlag::none); + } while (nCurrentTextHeight > nTentativeColHeight && nWantedIncrease > 0 + && nWantedIncrease != std::numeric_limits<tools::Long>::max()); + return nCurrentTextHeight; } sal_Int32 ImpEditEngine::GetLineCount( sal_Int32 nParagraph ) const { - OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); - const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE(GetParaPortions().exists(nParagraph), "GetLineCount: Out of range"); + const ParaPortion* pPPortion = GetParaPortions().SafeGetObject(nParagraph); OSL_ENSURE( pPPortion, "Paragraph not found: GetLineCount" ); if ( pPPortion ) return pPPortion->GetLines().Count(); @@ -3262,9 +3643,9 @@ sal_Int32 ImpEditEngine::GetLineCount( sal_Int32 nParagraph ) const sal_Int32 ImpEditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const { - OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineLen: Out of range" ); - const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); - OSL_ENSURE( pPPortion, "Paragraph not found: GetLineLen" ); + OSL_ENSURE(GetParaPortions().exists(nParagraph), "GetLineLen: Out of range"); + const ParaPortion* pPPortion = GetParaPortions().SafeGetObject(nParagraph); + OSL_ENSURE(pPPortion, "Paragraph not found: GetLineLen"); if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) { const EditLine& rLine = pPPortion->GetLines()[nLine]; @@ -3276,7 +3657,7 @@ sal_Int32 ImpEditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) con void ImpEditEngine::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const { - OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); + OSL_ENSURE(GetParaPortions().exists(nParagraph), "GetLineCount: Out of range"); const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); OSL_ENSURE( pPPortion, "Paragraph not found: GetLineBoundaries" ); rStart = rEnd = -1; // default values in case of error @@ -3317,7 +3698,7 @@ sal_Int32 ImpEditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine ) { - OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); + OSL_ENSURE(GetParaPortions().exists(nParagraph), "GetLineCount: Out of range"); ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); OSL_ENSURE( pPPortion, "Paragraph not found: GetLineHeight" ); if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) @@ -3329,11 +3710,11 @@ sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine ) return 0xFFFF; } -sal_uInt32 ImpEditEngine::GetParaHeight( sal_Int32 nParagraph ) +sal_uInt32 ImpEditEngine::GetParaHeight(sal_Int32 nParagraph) const { sal_uInt32 nHeight = 0; - ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + ParaPortion const* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); OSL_ENSURE( pPPortion, "Paragraph not found: GetParaHeight" ); if ( pPPortion ) @@ -3346,11 +3727,11 @@ void ImpEditEngine::UpdateSelections() { // Check whether one of the selections is at a deleted node... // If the node is valid, the index has yet to be examined! - for (EditView* pView : aEditViews) + for (EditView* pView : maEditViews) { - EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); + EditSelection aCurSel( pView->getImpl().GetEditSelection() ); bool bChanged = false; - for (const std::unique_ptr<DeletedNodeInfo> & aDeletedNode : aDeletedNodes) + for (const std::unique_ptr<DeletedNodeInfo> & aDeletedNode : maDeletedNodes) { const DeletedNodeInfo& rInf = *aDeletedNode; if ( ( aCurSel.Min().GetNode() == rInf.GetNode() ) || @@ -3359,27 +3740,27 @@ void ImpEditEngine::UpdateSelections() // Use ParaPortions, as now also hidden paragraphs have to be // taken into account! sal_Int32 nPara = rInf.GetPosition(); - if (!GetParaPortions().SafeGetObject(nPara)) // Last paragraph + if (!GetParaPortions().exists(nPara)) // Last paragraph { - nPara = GetParaPortions().Count()-1; + nPara = GetParaPortions().lastIndex(); } - assert(GetParaPortions()[nPara] && "Empty Document in UpdateSelections ?"); + assert(GetParaPortions().exists(nPara) && "Empty Document in UpdateSelections ?"); // Do not end up from a hidden paragraph: - sal_Int32 nCurPara = nPara; - sal_Int32 nLastPara = GetParaPortions().Count()-1; - while ( nPara <= nLastPara && !GetParaPortions()[nPara]->IsVisible() ) + sal_Int32 nCurrentPara = nPara; + sal_Int32 nLastParaIndex = GetParaPortions().lastIndex(); + while (nPara <= nLastParaIndex && !GetParaPortions().getRef(nPara).IsVisible()) nPara++; - if ( nPara > nLastPara ) // then also backwards ... + if (nPara > nLastParaIndex) // then also backwards ... { - nPara = nCurPara; - while ( nPara && !GetParaPortions()[nPara]->IsVisible() ) + nPara = nCurrentPara; + while ( nPara && !GetParaPortions().getRef(nPara).IsVisible() ) nPara--; } - OSL_ENSURE( GetParaPortions()[nPara]->IsVisible(), "No visible paragraph found: UpdateSelections" ); + OSL_ENSURE(GetParaPortions().getRef(nPara).IsVisible(), "No visible paragraph found: UpdateSelections" ); - ParaPortion* pParaPortion = GetParaPortions()[nPara]; - EditSelection aTmpSelection( EditPaM( pParaPortion->GetNode(), 0 ) ); - pView->pImpEditView->SetEditSelection( aTmpSelection ); + ParaPortion& rParaPortion = GetParaPortions().getRef(nPara); + EditSelection aTmpSelection(EditPaM(rParaPortion.GetNode(), 0)); + pView->getImpl().SetEditSelection( aTmpSelection ); bChanged=true; break; // for loop } @@ -3390,16 +3771,16 @@ void ImpEditEngine::UpdateSelections() if ( aCurSel.Min().GetIndex() > aCurSel.Min().GetNode()->Len() ) { aCurSel.Min().SetIndex( aCurSel.Min().GetNode()->Len() ); - pView->pImpEditView->SetEditSelection( aCurSel ); + pView->getImpl().SetEditSelection( aCurSel ); } if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() ) { aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() ); - pView->pImpEditView->SetEditSelection( aCurSel ); + pView->getImpl().SetEditSelection( aCurSel ); } } } - aDeletedNodes.clear(); + maDeletedNodes.clear(); } EditSelection ImpEditEngine::ConvertSelection( @@ -3408,11 +3789,11 @@ EditSelection ImpEditEngine::ConvertSelection( EditSelection aNewSelection; // Start... - ContentNode* pNode = aEditDoc.GetObject( nStartPara ); + ContentNode* pNode = maEditDoc.GetObject( nStartPara ); sal_Int32 nIndex = nStartPos; if ( !pNode ) { - pNode = aEditDoc[ aEditDoc.Count()-1 ]; + pNode = maEditDoc.GetObject(maEditDoc.Count() - 1); nIndex = pNode->Len(); } else if ( nIndex > pNode->Len() ) @@ -3422,11 +3803,11 @@ EditSelection ImpEditEngine::ConvertSelection( aNewSelection.Min().SetIndex( nIndex ); // End... - pNode = aEditDoc.GetObject( nEndPara ); + pNode = maEditDoc.GetObject( nEndPara ); nIndex = nEndPos; if ( !pNode ) { - pNode = aEditDoc[ aEditDoc.Count()-1 ]; + pNode = maEditDoc.GetObject(maEditDoc.Count() - 1); nIndex = pNode->Len(); } else if ( nIndex > pNode->Len() ) @@ -3443,16 +3824,16 @@ void ImpEditEngine::SetActiveView( EditView* pView ) // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Actually, now bHasVisSel and HideSelection would be necessary !!! - if ( pView == pActiveView ) + if (pView == mpActiveView) return; - if ( pActiveView && pActiveView->HasSelection() ) - pActiveView->pImpEditView->DrawSelectionXOR(); + if (mpActiveView && mpActiveView->HasSelection()) + mpActiveView->getImpl().DrawSelectionXOR(); - pActiveView = pView; + mpActiveView = pView; - if ( pActiveView && pActiveView->HasSelection() ) - pActiveView->pImpEditView->DrawSelectionXOR(); + if (mpActiveView && mpActiveView->HasSelection()) + mpActiveView->getImpl().DrawSelectionXOR(); // NN: Quick fix for #78668#: // When editing of a cell in Calc is ended, the edit engine is not deleted, @@ -3473,8 +3854,7 @@ uno::Reference< datatransfer::XTransferable > ImpEditEngine::CreateTransferable( EditSelection aSelection( rSelection ); aSelection.Adjust( GetEditDoc() ); - EditDataObject* pDataObj = new EditDataObject; - uno::Reference< datatransfer::XTransferable > xDataObj = pDataObj; + rtl::Reference<EditDataObject> pDataObj = new EditDataObject; pDataObj->GetString() = convertLineEnd(GetSelected(aSelection), GetSystemLineEnd()); // System specific @@ -3513,10 +3893,10 @@ uno::Reference< datatransfer::XTransferable > ImpEditEngine::CreateTransferable( } } - return xDataObj; + return pDataObj; } -EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial ) +EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial, SotClipboardFormatId format) { EditSelection aNewSelection( rPaM ); @@ -3530,7 +3910,7 @@ EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransfera { // XML SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aFlavor ); - if ( rxDataObj->isDataFlavorSupported( aFlavor ) ) + if ( rxDataObj->isDataFlavorSupported( aFlavor ) && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT == format)) { try { @@ -3558,7 +3938,7 @@ EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransfera SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aFlavorRichtext ); bool bRtfSupported = rxDataObj->isDataFlavorSupported( aFlavor ); bool bRichtextSupported = rxDataObj->isDataFlavorSupported( aFlavorRichtext ); - if ( bRtfSupported || bRichtextSupported ) + if ( (bRtfSupported || bRichtextSupported) && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::RICHTEXT == format || SotClipboardFormatId::RTF == format)) { if(bRichtextSupported) { @@ -3580,6 +3960,55 @@ EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransfera } } } + if (!bDone) { + // HTML_SIMPLE + SotExchange::GetFormatDataFlavor(SotClipboardFormatId::HTML_SIMPLE, aFlavor); + bool bHtmlSupported = rxDataObj->isDataFlavorSupported(aFlavor); + if (bHtmlSupported && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::HTML_SIMPLE == format)) { + MSE40HTMLClipFormatObj aMSE40HTMLClipFormatObj; + try + { + uno::Any aData = rxDataObj->getTransferData(aFlavor); + uno::Sequence< sal_Int8 > aSeq; + aData >>= aSeq; + { + SvMemoryStream aHtmlStream(aSeq.getArray(), aSeq.getLength(), StreamMode::READ); + SvStream* pHtmlStream = aMSE40HTMLClipFormatObj.IsValid(aHtmlStream); + if (pHtmlStream != nullptr) { + aNewSelection = Read(*pHtmlStream, rBaseURL, EETextFormat::Html, rPaM); + } + } + bDone = true; + } + catch (const css::uno::Exception&) + { + } + } + } + + if (!bDone) + { + // HTML + SotExchange::GetFormatDataFlavor(SotClipboardFormatId::HTML, aFlavor); + bool bHtmlSupported = rxDataObj->isDataFlavorSupported(aFlavor); + if (bHtmlSupported + && (format == SotClipboardFormatId::NONE || format == SotClipboardFormatId::HTML)) + { + try + { + uno::Any aData = rxDataObj->getTransferData(aFlavor); + uno::Sequence<sal_Int8> aSeq; + aData >>= aSeq; + SvMemoryStream aHtmlStream(aSeq.getArray(), aSeq.getLength(), StreamMode::READ); + aNewSelection = Read(aHtmlStream, rBaseURL, EETextFormat::Html, rPaM); + bDone = true; + } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("editeng", "HTML paste failed"); + } + } + } } if ( !bDone ) { @@ -3603,145 +4032,17 @@ EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransfera return aNewSelection; } -Range ImpEditEngine::GetInvalidYOffsets( ParaPortion* pPortion ) -{ - Range aRange( 0, 0 ); - - if ( pPortion->IsVisible() ) - { - const SvxULSpaceItem& rULSpace = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); - const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); - sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) - ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; - - // only from the top ... - sal_Int32 nFirstInvalid = -1; - sal_Int32 nLine; - for ( nLine = 0; nLine < pPortion->GetLines().Count(); nLine++ ) - { - const EditLine& rL = pPortion->GetLines()[nLine]; - if ( rL.IsInvalid() ) - { - nFirstInvalid = nLine; - break; - } - if ( nLine && !aStatus.IsOutliner() ) // not the first line - aRange.Min() += nSBL; - aRange.Min() += rL.GetHeight(); - } - OSL_ENSURE( nFirstInvalid != -1, "No invalid line found in GetInvalidYOffset(1)" ); - - - // Syndicate and more ... - aRange.Max() = aRange.Min(); - aRange.Max() += pPortion->GetFirstLineOffset(); - if (nFirstInvalid >= 0) // Only if the first line is invalid - aRange.Min() = aRange.Max(); - - sal_Int32 nLastInvalid = pPortion->GetLines().Count()-1; - if (nFirstInvalid >= 0) - { - for ( nLine = nFirstInvalid; nLine < pPortion->GetLines().Count(); nLine++ ) - { - const EditLine& rL = pPortion->GetLines()[nLine]; - if ( rL.IsValid() ) - { - nLastInvalid = nLine; - break; - } - if ( nLine && !aStatus.IsOutliner() ) - aRange.Max() += nSBL; - aRange.Max() += rL.GetHeight(); - } - - sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace(); - if ( ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) - && nPropLineSpace && ( nPropLineSpace < 100 ) ) - { - const EditLine& rL = pPortion->GetLines()[nFirstInvalid]; - auto n = rL.GetTxtHeight() * ( 100 - nPropLineSpace ); - n /= 100; - aRange.Min() -= n; - aRange.Max() += n; - } - - if ( ( nLastInvalid == pPortion->GetLines().Count()-1 ) && ( !aStatus.IsOutliner() ) ) - aRange.Max() += GetYValue( rULSpace.GetLower() ); - } - } - return aRange; -} - -EditPaM ImpEditEngine::GetPaM( ParaPortion* pPortion, Point aDocPos, bool bSmart ) -{ - OSL_ENSURE( pPortion->IsVisible(), "Why GetPaM() for an invisible paragraph?" ); - OSL_ENSURE( IsFormatted(), "GetPaM: Not formatted" ); - - sal_Int32 nCurIndex = 0; - EditPaM aPaM; - aPaM.SetNode( pPortion->GetNode() ); - - const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); - sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) - ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; - - tools::Long nY = pPortion->GetFirstLineOffset(); - - OSL_ENSURE( pPortion->GetLines().Count(), "Empty ParaPortion in GetPaM!" ); - - const EditLine* pLine = nullptr; - for ( sal_Int32 nLine = 0; nLine < pPortion->GetLines().Count(); nLine++ ) - { - const EditLine& rTmpLine = pPortion->GetLines()[nLine]; - nY += rTmpLine.GetHeight(); - if ( !aStatus.IsOutliner() ) - nY += nSBL; - if ( nY > aDocPos.Y() ) - { - pLine = &rTmpLine; - break; // correct Y-position is not of interest - } - - nCurIndex = nCurIndex + rTmpLine.GetLen(); - } - - if ( !pLine ) // may happen only in the range of SA! - { -#if OSL_DEBUG_LEVEL > 0 - const SvxULSpaceItem& rULSpace = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); - OSL_ENSURE( nY+GetYValue( rULSpace.GetLower() ) >= aDocPos.Y() , "Index in no line, GetPaM ?" ); -#endif - aPaM.SetIndex( pPortion->GetNode()->Len() ); - return aPaM; - } - - // If no line found, only just X-Position => Index - nCurIndex = GetChar( pPortion, pLine, aDocPos.X(), bSmart ); - aPaM.SetIndex( nCurIndex ); - - if ( nCurIndex && ( nCurIndex == pLine->GetEnd() ) && - ( pLine != &pPortion->GetLines()[pPortion->GetLines().Count()-1] ) ) - { - aPaM = CursorLeft( aPaM ); - } - - return aPaM; -} - -sal_Int32 ImpEditEngine::GetChar( - const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nXPos, bool bSmart) +sal_Int32 ImpEditEngine::GetChar(ParaPortion const& rParaPortion, EditLine const& rLine, tools::Long nXPos, bool bSmart) { - OSL_ENSURE( pLine, "No line received: GetChar" ); - sal_Int32 nChar = -1; - sal_Int32 nCurIndex = pLine->GetStart(); + sal_Int32 nCurIndex = rLine.GetStart(); // Search best matching portion with GetPortionXOffset() - for ( sal_Int32 i = pLine->GetStartPortion(); i <= pLine->GetEndPortion(); i++ ) + for ( sal_Int32 i = rLine.GetStartPortion(); i <= rLine.GetEndPortion(); i++ ) { - const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; - tools::Long nXLeft = GetPortionXOffset( pParaPortion, pLine, i ); + const TextPortion& rPortion = rParaPortion.GetTextPortions()[i]; + tools::Long nXLeft = GetPortionXOffset(rParaPortion, rLine, i); tools::Long nXRight = nXLeft + rPortion.GetSize().Width(); if ( ( nXLeft <= nXPos ) && ( nXRight >= nXPos ) ) { @@ -3765,7 +4066,7 @@ sal_Int32 ImpEditEngine::GetChar( { sal_Int32 nMax = rPortion.GetLen(); sal_Int32 nOffset = -1; - sal_Int32 nTmpCurIndex = nChar - pLine->GetStart(); + sal_Int32 nTmpCurIndex = nChar - rLine.GetStart(); tools::Long nXInPortion = nXPos - nXLeft; if ( rPortion.IsRightToLeft() ) @@ -3774,25 +4075,26 @@ sal_Int32 ImpEditEngine::GetChar( // Search in Array... for ( sal_Int32 x = 0; x < nMax; x++ ) { - tools::Long nTmpPosMax = pLine->GetCharPosArray()[nTmpCurIndex+x]; + tools::Long nTmpPosMax = rLine.GetCharPosArray()[nTmpCurIndex+x]; if ( nTmpPosMax > nXInPortion ) { // Check whether this or the previous... - tools::Long nTmpPosMin = x ? pLine->GetCharPosArray()[nTmpCurIndex+x-1] : 0; + tools::Long nTmpPosMin = x ? rLine.GetCharPosArray()[nTmpCurIndex+x-1] : 0; tools::Long nDiffLeft = nXInPortion - nTmpPosMin; tools::Long nDiffRight = nTmpPosMax - nXInPortion; OSL_ENSURE( nDiffLeft >= 0, "DiffLeft negative" ); OSL_ENSURE( nDiffRight >= 0, "DiffRight negative" ); - nOffset = ( bSmart && ( nDiffRight < nDiffLeft ) ) ? x+1 : x; - // I18N: If there are character position with the length of 0, - // they belong to the same character, we can not use this position as an index. - // Skip all 0-positions, cheaper than using XBreakIterator: - if ( nOffset < nMax ) + + if (bSmart && nDiffRight < nDiffLeft) { - const tools::Long nX = pLine->GetCharPosArray()[nOffset]; - while ( ( (nOffset+1) < nMax ) && ( pLine->GetCharPosArray()[nOffset+1] == nX ) ) - nOffset++; + // I18N: If there are character position with the length of 0, + // they belong to the same character, we can not use this position as an index. + // Skip all 0-positions, cheaper than using XBreakIterator: + tools::Long nX = rLine.GetCharPosArray()[nTmpCurIndex + x]; + while(x < nMax && rLine.GetCharPosArray()[nTmpCurIndex + x] == nX) + ++x; } + nOffset = x; break; } } @@ -3808,9 +4110,9 @@ sal_Int32 ImpEditEngine::GetChar( nChar = nChar + nOffset; // Check if index is within a cell: - if ( nChar && ( nChar < pParaPortion->GetNode()->Len() ) ) + if ( nChar && ( nChar < rParaPortion.GetNode()->Len() ) ) { - EditPaM aPaM( pParaPortion->GetNode(), nChar+1 ); + EditPaM aPaM( rParaPortion.GetNode(), nChar+1 ); sal_uInt16 nScriptType = GetI18NScriptType( aPaM ); if ( nScriptType == i18n::ScriptType::COMPLEX ) { @@ -3818,9 +4120,9 @@ sal_Int32 ImpEditEngine::GetChar( sal_Int32 nCount = 1; lang::Locale aLocale = GetLocale( aPaM ); sal_Int32 nRight = _xBI->nextCharacters( - pParaPortion->GetNode()->GetString(), nChar, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); + rParaPortion.GetNode()->GetString(), nChar, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); sal_Int32 nLeft = _xBI->previousCharacters( - pParaPortion->GetNode()->GetString(), nRight, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); + rParaPortion.GetNode()->GetString(), nRight, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); if ( ( nLeft != nChar ) && ( nRight != nChar ) ) { nChar = ( std::abs( nRight - nChar ) < std::abs( nLeft - nChar ) ) ? nRight : nLeft; @@ -3828,7 +4130,7 @@ sal_Int32 ImpEditEngine::GetChar( } else { - OUString aStr(pParaPortion->GetNode()->GetString()); + OUString aStr(rParaPortion.GetNode()->GetString()); // tdf#102625: don't select middle of a pair of surrogates with mouse cursor if (rtl::isSurrogate(aStr[nChar])) --nChar; @@ -3842,40 +4144,38 @@ sal_Int32 ImpEditEngine::GetChar( if ( nChar == -1 ) { - nChar = ( nXPos <= pLine->GetStartPosX() ) ? pLine->GetStart() : pLine->GetEnd(); + nChar = ( nXPos <= rLine.GetStartPosX() ) ? rLine.GetStart() : rLine.GetEnd(); } return nChar; } -Range ImpEditEngine::GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const +Range ImpEditEngine::GetLineXPosStartEnd(ParaPortion const& rParaPortion, EditLine const& rLine) const { Range aLineXPosStartEnd; - sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); + sal_Int32 nPara = GetEditDoc().GetPos(rParaPortion.GetNode()); if ( !IsRightToLeft( nPara ) ) { - aLineXPosStartEnd.Min() = pLine->GetStartPosX(); - aLineXPosStartEnd.Max() = pLine->GetStartPosX() + pLine->GetTextWidth(); + aLineXPosStartEnd.Min() = rLine.GetStartPosX(); + aLineXPosStartEnd.Max() = rLine.GetStartPosX() + rLine.GetTextWidth(); } else { - aLineXPosStartEnd.Min() = GetPaperSize().Width() - ( pLine->GetStartPosX() + pLine->GetTextWidth() ); - aLineXPosStartEnd.Max() = GetPaperSize().Width() - pLine->GetStartPosX(); + aLineXPosStartEnd.Min() = GetPaperSize().Width() - (rLine.GetStartPosX() + rLine.GetTextWidth()); + aLineXPosStartEnd.Max() = GetPaperSize().Width() - rLine.GetStartPosX(); } - return aLineXPosStartEnd; } -tools::Long ImpEditEngine::GetPortionXOffset( - const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const +tools::Long ImpEditEngine::GetPortionXOffset(ParaPortion const& rParaPortion, EditLine const& rLine, sal_Int32 nTextPortion) const { - tools::Long nX = pLine->GetStartPosX(); + tools::Long nX = rLine.GetStartPosX(); - for ( sal_Int32 i = pLine->GetStartPortion(); i < nTextPortion; i++ ) + for ( sal_Int32 i = rLine.GetStartPortion(); i < nTextPortion; i++ ) { - const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; + const TextPortion& rPortion = rParaPortion.GetTextPortions()[i]; switch ( rPortion.GetKind() ) { case PortionKind::FIELD: @@ -3890,19 +4190,19 @@ tools::Long ImpEditEngine::GetPortionXOffset( } } - sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); + sal_Int32 nPara = GetEditDoc().GetPos(rParaPortion.GetNode()); bool bR2LPara = IsRightToLeft( nPara ); - const TextPortion& rDestPortion = pParaPortion->GetTextPortions()[nTextPortion]; + const TextPortion& rDestPortion = rParaPortion.GetTextPortions()[nTextPortion]; if ( rDestPortion.GetKind() != PortionKind::TAB ) { if ( !bR2LPara && rDestPortion.GetRightToLeftLevel() ) { // Portions behind must be added, visual before this portion sal_Int32 nTmpPortion = nTextPortion+1; - while ( nTmpPortion <= pLine->GetEndPortion() ) + while ( nTmpPortion <= rLine.GetEndPortion() ) { - const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + const TextPortion& rNextTextPortion = rParaPortion.GetTextPortions()[nTmpPortion]; if ( rNextTextPortion.GetRightToLeftLevel() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) nX += rNextTextPortion.GetSize().Width(); else @@ -3911,10 +4211,10 @@ tools::Long ImpEditEngine::GetPortionXOffset( } // Portions before must be removed, visual behind this portion nTmpPortion = nTextPortion; - while ( nTmpPortion > pLine->GetStartPortion() ) + while ( nTmpPortion > rLine.GetStartPortion() ) { --nTmpPortion; - const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + const TextPortion& rPrevTextPortion = rParaPortion.GetTextPortions()[nTmpPortion]; if ( rPrevTextPortion.GetRightToLeftLevel() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) nX -= rPrevTextPortion.GetSize().Width(); else @@ -3925,9 +4225,9 @@ tools::Long ImpEditEngine::GetPortionXOffset( { // Portions behind must be removed, visual behind this portion sal_Int32 nTmpPortion = nTextPortion+1; - while ( nTmpPortion <= pLine->GetEndPortion() ) + while ( nTmpPortion <= rLine.GetEndPortion() ) { - const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + const TextPortion& rNextTextPortion = rParaPortion.GetTextPortions()[nTmpPortion]; if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) nX += rNextTextPortion.GetSize().Width(); else @@ -3936,10 +4236,10 @@ tools::Long ImpEditEngine::GetPortionXOffset( } // Portions before must be added, visual before this portion nTmpPortion = nTextPortion; - while ( nTmpPortion > pLine->GetStartPortion() ) + while ( nTmpPortion > rLine.GetStartPortion() ) { --nTmpPortion; - const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + const TextPortion& rPrevTextPortion = rParaPortion.GetTextPortions()[nTmpPortion]; if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) nX -= rPrevTextPortion.GetSize().Width(); else @@ -3959,33 +4259,31 @@ tools::Long ImpEditEngine::GetPortionXOffset( return nX; } -tools::Long ImpEditEngine::GetXPos( - const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart) const +tools::Long ImpEditEngine::GetXPos(ParaPortion const& rParaPortion, EditLine const& rLine, sal_Int32 nIndex, bool bPreferPortionStart) const { - OSL_ENSURE( pLine, "No line received: GetXPos" ); - OSL_ENSURE( ( nIndex >= pLine->GetStart() ) && ( nIndex <= pLine->GetEnd() ) , "GetXPos has to be called properly!" ); + OSL_ENSURE( ( nIndex >= rLine.GetStart() ) && ( nIndex <= rLine.GetEnd() ) , "GetXPos has to be called properly!" ); bool bDoPreferPortionStart = bPreferPortionStart; // Assure that the portion belongs to this line: - if ( nIndex == pLine->GetStart() ) + if ( nIndex == rLine.GetStart() ) bDoPreferPortionStart = true; - else if ( nIndex == pLine->GetEnd() ) + else if ( nIndex == rLine.GetEnd() ) bDoPreferPortionStart = false; sal_Int32 nTextPortionStart = 0; - sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart ); + sal_Int32 nTextPortion = rParaPortion.GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart ); - OSL_ENSURE( ( nTextPortion >= pLine->GetStartPortion() ) && ( nTextPortion <= pLine->GetEndPortion() ), "GetXPos: Portion not in current line! " ); + OSL_ENSURE( ( nTextPortion >= rLine.GetStartPortion() ) && ( nTextPortion <= rLine.GetEndPortion() ), "GetXPos: Portion not in current line! " ); - const TextPortion& rPortion = pParaPortion->GetTextPortions()[nTextPortion]; + const TextPortion& rPortion = rParaPortion.GetTextPortions()[nTextPortion]; - tools::Long nX = GetPortionXOffset( pParaPortion, pLine, nTextPortion ); + tools::Long nX = GetPortionXOffset(rParaPortion, rLine, nTextPortion); // calc text width, portion size may include CJK/CTL spacing... // But the array might not be init yet, if using text ranger this method is called within CreateLines()... tools::Long nPortionTextWidth = rPortion.GetSize().Width(); if ( ( rPortion.GetKind() == PortionKind::TEXT ) && rPortion.GetLen() && !GetTextRanger() ) - nPortionTextWidth = pLine->GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - pLine->GetStart()]; + nPortionTextWidth = rLine.GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - rLine.GetStart()]; if ( nTextPortionStart != nIndex ) { @@ -3995,18 +4293,18 @@ tools::Long ImpEditEngine::GetXPos( // End of Portion if ( rPortion.GetKind() == PortionKind::TAB ) { - if ( nTextPortion+1 < pParaPortion->GetTextPortions().Count() ) + if ( nTextPortion+1 < rParaPortion.GetTextPortions().Count() ) { - const TextPortion& rNextPortion = pParaPortion->GetTextPortions()[nTextPortion+1]; + const TextPortion& rNextPortion = rParaPortion.GetTextPortions()[nTextPortion+1]; if ( rNextPortion.GetKind() != PortionKind::TAB ) { if ( !bPreferPortionStart ) - nX = GetXPos( pParaPortion, pLine, nIndex, true ); - else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) + nX = GetXPos(rParaPortion, rLine, nIndex, true ); + else if ( !IsRightToLeft( GetEditDoc().GetPos(rParaPortion.GetNode()) ) ) nX += nPortionTextWidth; } } - else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) + else if ( !IsRightToLeft( GetEditDoc().GetPos(rParaPortion.GetNode()) ) ) { nX += nPortionTextWidth; } @@ -4018,20 +4316,20 @@ tools::Long ImpEditEngine::GetXPos( } else if ( rPortion.GetKind() == PortionKind::TEXT ) { - OSL_ENSURE( nIndex != pLine->GetStart(), "Strange behavior in new GetXPos()" ); - OSL_ENSURE( pLine && !pLine->GetCharPosArray().empty(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" ); + OSL_ENSURE( nIndex != rLine.GetStart(), "Strange behavior in new GetXPos()" ); + OSL_ENSURE( !rLine.GetCharPosArray().empty(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" ); - if( !pLine->GetCharPosArray().empty() ) + if( !rLine.GetCharPosArray().empty() ) { - sal_Int32 nPos = nIndex - 1 - pLine->GetStart(); - if (nPos < 0 || nPos >= static_cast<sal_Int32>(pLine->GetCharPosArray().size())) + sal_Int32 nPos = nIndex - 1 - rLine.GetStart(); + if (nPos < 0 || o3tl::make_unsigned(nPos) >= rLine.GetCharPosArray().size()) { - nPos = pLine->GetCharPosArray().size()-1; + nPos = rLine.GetCharPosArray().size()-1; OSL_FAIL("svx::ImpEditEngine::GetXPos(), index out of range!"); } // old code restored see #i112788 (which leaves #i74188 unfixed again) - tools::Long nPosInPortion = pLine->GetCharPosArray()[nPos]; + tools::Long nPosInPortion = rLine.GetCharPosArray()[nPos]; if ( !rPortion.IsRightToLeft() ) { @@ -4047,17 +4345,17 @@ tools::Long ImpEditEngine::GetXPos( nX += rPortion.GetExtraInfos()->nPortionOffsetX; if ( rPortion.GetExtraInfos()->nAsianCompressionTypes & AsianCompressionFlags::PunctuationRight ) { - AsianCompressionFlags nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex ) ); - if ( nType == AsianCompressionFlags::PunctuationRight && !pLine->GetCharPosArray().empty() ) + AsianCompressionFlags nType = GetCharTypeForCompression(rParaPortion.GetNode()->GetChar(nIndex)); + if ( nType == AsianCompressionFlags::PunctuationRight && !rLine.GetCharPosArray().empty() ) { sal_Int32 n = nIndex - nTextPortionStart; - const tools::Long* pDXArray = pLine->GetCharPosArray().data()+( nTextPortionStart-pLine->GetStart() ); + const sal_Int32* pDXArray = rLine.GetCharPosArray().data() + (nTextPortionStart - rLine.GetStart()); sal_Int32 nCharWidth = ( ( (n+1) < rPortion.GetLen() ) ? pDXArray[n] : rPortion.GetSize().Width() ) - ( n ? pDXArray[n-1] : 0 ); if ( (n+1) < rPortion.GetLen() ) { // smaller, when char behind is AsianCompressionFlags::PunctuationRight also - nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex+1 ) ); + nType = GetCharTypeForCompression(rParaPortion.GetNode()->GetChar(nIndex + 1)); if ( nType == AsianCompressionFlags::PunctuationRight ) { sal_Int32 nNextCharWidth = ( ( (n+2) < rPortion.GetLen() ) ? pDXArray[n+1] : rPortion.GetSize().Width() ) @@ -4079,7 +4377,7 @@ tools::Long ImpEditEngine::GetXPos( } } } - else // if ( nIndex == pLine->GetStart() ) + else // if ( nIndex == rLine.GetStart() ) { if ( rPortion.IsRightToLeft() ) { @@ -4090,48 +4388,66 @@ tools::Long ImpEditEngine::GetXPos( return nX; } -void ImpEditEngine::CalcHeight( ParaPortion* pPortion ) +/** Is true if paragraph is in the empty cluster of paragraphs at the end */ +bool ImpEditEngine::isInEmptyClusterAtTheEnd(ParaPortion& rPortion) { - pPortion->nHeight = 0; - pPortion->nFirstLineOffset = 0; + sal_Int32 nPortion = GetParaPortions().GetPos(&rPortion); - if ( !pPortion->IsVisible() ) + auto& rParagraphs = GetParaPortions(); + if (rParagraphs.Count() <= 0) + return false; + + sal_Int32 nCurrent = rParagraphs.lastIndex(); + while (nCurrent > 0 && rParagraphs.getRef(nCurrent).IsEmpty()) + { + if (nCurrent == nPortion) + return true; + nCurrent--; + } + return false; +} + +void ImpEditEngine::CalcHeight(ParaPortion& rPortion) +{ + rPortion.mnHeight = 0; + rPortion.mnFirstLineOffset = 0; + + if (!rPortion.IsVisible() || isInEmptyClusterAtTheEnd(rPortion)) return; - OSL_ENSURE( pPortion->GetLines().Count(), "Paragraph with no lines in ParaPortion::CalcHeight" ); - for (sal_Int32 nLine = 0; nLine < pPortion->GetLines().Count(); ++nLine) - pPortion->nHeight += pPortion->GetLines()[nLine].GetHeight(); + OSL_ENSURE(rPortion.GetLines().Count(), "Paragraph with no lines in ParaPortion::CalcHeight"); + for (sal_Int32 nLine = 0; nLine < rPortion.GetLines().Count(); ++nLine) + rPortion.mnHeight += rPortion.GetLines()[nLine].GetHeight(); - if ( aStatus.IsOutliner() ) + if (maStatus.IsOutliner()) return; - const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); - const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); - sal_Int32 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; + const SvxULSpaceItem& rULItem = rPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); + const SvxLineSpacingItem& rLSItem = rPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + sal_Int32 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0; if ( nSBL ) { - if ( pPortion->GetLines().Count() > 1 ) - pPortion->nHeight += ( pPortion->GetLines().Count() - 1 ) * nSBL; - if ( aStatus.ULSpaceSummation() ) - pPortion->nHeight += nSBL; + if (rPortion.GetLines().Count() > 1) + rPortion.mnHeight += (rPortion.GetLines().Count() - 1) * nSBL; + if (maStatus.ULSpaceSummation()) + rPortion.mnHeight += nSBL; } - sal_Int32 nPortion = GetParaPortions().GetPos( pPortion ); + sal_Int32 nPortion = GetParaPortions().GetPos(&rPortion); if ( nPortion ) { - sal_uInt16 nUpper = GetYValue( rULItem.GetUpper() ); - pPortion->nHeight += nUpper; - pPortion->nFirstLineOffset = nUpper; + sal_uInt16 nUpper = scaleYSpacingValue(rULItem.GetUpper()); + rPortion.mnHeight += nUpper; + rPortion.mnFirstLineOffset = nUpper; } - if ( nPortion != (GetParaPortions().Count()-1) ) + if (nPortion != GetParaPortions().lastIndex()) { - pPortion->nHeight += GetYValue( rULItem.GetLower() ); // not in the last + rPortion.mnHeight += scaleYSpacingValue(rULItem.GetLower()); // not in the last } - - if ( !nPortion || aStatus.ULSpaceSummation() ) + if ( !nPortion || maStatus.ULSpaceSummation() ) return; ParaPortion* pPrev = GetParaPortions().SafeGetObject( nPortion-1 ); @@ -4147,30 +4463,29 @@ void ImpEditEngine::CalcHeight( ParaPortion* pPortion ) // Only Writer3: Do not add up, but minimum distance. // check if distance by LineSpacing > Upper: - sal_uInt16 nExtraSpace = GetYValue( lcl_CalcExtraSpace( rLSItem ) ); - if ( nExtraSpace > pPortion->nFirstLineOffset ) + sal_uInt16 nExtraSpace = scaleYSpacingValue(lcl_CalcExtraSpace(rLSItem)); + if (nExtraSpace > rPortion.mnFirstLineOffset) { // Paragraph becomes 'bigger': - pPortion->nHeight += ( nExtraSpace - pPortion->nFirstLineOffset ); - pPortion->nFirstLineOffset = nExtraSpace; + rPortion.mnHeight += (nExtraSpace - rPortion.mnFirstLineOffset); + rPortion.mnFirstLineOffset = nExtraSpace; } // Determine nFirstLineOffset now f(pNode) => now f(pNode, pPrev): - sal_uInt16 nPrevLower = GetYValue( rPrevULItem.GetLower() ); + sal_uInt16 nPrevLower = scaleYSpacingValue(rPrevULItem.GetLower()); // This PrevLower is still in the height of PrevPortion ... - if ( nPrevLower > pPortion->nFirstLineOffset ) + if (nPrevLower > rPortion.mnFirstLineOffset) { // Paragraph is 'small': - pPortion->nHeight -= pPortion->nFirstLineOffset; - pPortion->nFirstLineOffset = 0; + rPortion.mnHeight -= rPortion.mnFirstLineOffset; + rPortion.mnFirstLineOffset = 0; } else if ( nPrevLower ) { // Paragraph becomes 'somewhat smaller': - pPortion->nHeight -= nPrevLower; - pPortion->nFirstLineOffset = - pPortion->nFirstLineOffset - nPrevLower; + rPortion.mnHeight -= nPrevLower; + rPortion.mnFirstLineOffset = rPortion.mnFirstLineOffset - nPrevLower; } // I find it not so good, but Writer3 feature: // Check if distance by LineSpacing > Lower: this value is not @@ -4178,124 +4493,39 @@ void ImpEditEngine::CalcHeight( ParaPortion* pPortion ) if ( pPrev->IsInvalid() ) return; - nExtraSpace = GetYValue( lcl_CalcExtraSpace( rPrevLSItem ) ); + nExtraSpace = scaleYSpacingValue(lcl_CalcExtraSpace(rPrevLSItem)); if ( nExtraSpace > nPrevLower ) { sal_uInt16 nMoreLower = nExtraSpace - nPrevLower; // Paragraph becomes 'bigger', 'grows' downwards: - if ( nMoreLower > pPortion->nFirstLineOffset ) + if ( nMoreLower > rPortion.mnFirstLineOffset ) { - pPortion->nHeight += ( nMoreLower - pPortion->nFirstLineOffset ); - pPortion->nFirstLineOffset = nMoreLower; + rPortion.mnHeight += (nMoreLower - rPortion.mnFirstLineOffset); + rPortion.mnFirstLineOffset = nMoreLower; } } } -tools::Rectangle ImpEditEngine::GetEditCursor( ParaPortion* pPortion, sal_Int32 nIndex, GetCursorFlags nFlags ) -{ - OSL_ENSURE( pPortion->IsVisible(), "Why GetEditCursor() for an invisible paragraph?" ); - OSL_ENSURE( IsFormatted() || GetTextRanger(), "GetEditCursor: Not formatted" ); - - /* - GetCursorFlags::EndOfLine: If after the last character of a wrapped line, remaining - at the end of the line, not the beginning of the next one. - Purpose: - END => really after the last character - - Selection... - */ - - tools::Long nY = pPortion->GetFirstLineOffset(); - - const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); - sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) - ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; - - sal_Int32 nCurIndex = 0; - sal_Int32 nLineCount = pPortion->GetLines().Count(); - OSL_ENSURE( nLineCount, "Empty ParaPortion in GetEditCursor!" ); - if (nLineCount == 0) - return tools::Rectangle(); - const EditLine* pLine = nullptr; - bool bEOL( nFlags & GetCursorFlags::EndOfLine ); - for (sal_Int32 nLine = 0; nLine < nLineCount; ++nLine) - { - const EditLine& rTmpLine = pPortion->GetLines()[nLine]; - if ( ( rTmpLine.GetStart() == nIndex ) || ( rTmpLine.IsIn( nIndex, bEOL ) ) ) - { - pLine = &rTmpLine; - break; - } - - nCurIndex = nCurIndex + rTmpLine.GetLen(); - nY += rTmpLine.GetHeight(); - if ( !aStatus.IsOutliner() ) - nY += nSBL; - } - if ( !pLine ) - { - // Cursor at the End of the paragraph. - OSL_ENSURE( nIndex == nCurIndex, "Index dead wrong in GetEditCursor!" ); - - pLine = &pPortion->GetLines()[nLineCount-1]; - nY -= pLine->GetHeight(); - if ( !aStatus.IsOutliner() ) - nY -= nSBL; - } - - tools::Rectangle aEditCursor; - - aEditCursor.SetTop( nY ); - nY += pLine->GetHeight(); - aEditCursor.SetBottom( nY-1 ); - - // Search within the line... - tools::Long nX; - - if ( ( nIndex == pLine->GetStart() ) && ( nFlags & GetCursorFlags::StartOfLine ) ) - { - Range aXRange = GetLineXPosStartEnd( pPortion, pLine ); - nX = !IsRightToLeft( GetEditDoc().GetPos( pPortion->GetNode() ) ) ? aXRange.Min() : aXRange.Max(); - } - else if ( ( nIndex == pLine->GetEnd() ) && ( nFlags & GetCursorFlags::EndOfLine ) ) - { - Range aXRange = GetLineXPosStartEnd( pPortion, pLine ); - nX = !IsRightToLeft( GetEditDoc().GetPos( pPortion->GetNode() ) ) ? aXRange.Max() : aXRange.Min(); - } - else - { - nX = GetXPos( pPortion, pLine, nIndex, bool( nFlags & GetCursorFlags::PreferPortionStart ) ); - } - - aEditCursor.SetLeft(nX); - aEditCursor.SetRight(nX); - - if ( nFlags & GetCursorFlags::TextOnly ) - aEditCursor.SetTop( aEditCursor.Bottom() - pLine->GetTxtHeight() + 1 ); - else - aEditCursor.SetTop( aEditCursor.Bottom() - std::min( pLine->GetTxtHeight(), pLine->GetHeight() ) + 1 ); - - return aEditCursor; -} - void ImpEditEngine::SetValidPaperSize( const Size& rNewSz ) { - aPaperSize = rNewSz; + maPaperSize = rNewSz; - tools::Long nMinWidth = aStatus.AutoPageWidth() ? aMinAutoPaperSize.Width() : 0; - tools::Long nMaxWidth = aStatus.AutoPageWidth() ? aMaxAutoPaperSize.Width() : 0x7FFFFFFF; - tools::Long nMinHeight = aStatus.AutoPageHeight() ? aMinAutoPaperSize.Height() : 0; - tools::Long nMaxHeight = aStatus.AutoPageHeight() ? aMaxAutoPaperSize.Height() : 0x7FFFFFFF; + tools::Long nMinWidth = maStatus.AutoPageWidth() ? maMinAutoPaperSize.Width() : 0; + tools::Long nMaxWidth = maStatus.AutoPageWidth() ? maMaxAutoPaperSize.Width() : 0x7FFFFFFF; + tools::Long nMinHeight = maStatus.AutoPageHeight() ? maMinAutoPaperSize.Height() : 0; + tools::Long nMaxHeight = maStatus.AutoPageHeight() ? maMaxAutoPaperSize.Height() : 0x7FFFFFFF; // Minimum/Maximum width: - if ( aPaperSize.Width() < nMinWidth ) - aPaperSize.setWidth( nMinWidth ); - else if ( aPaperSize.Width() > nMaxWidth ) - aPaperSize.setWidth( nMaxWidth ); + if ( maPaperSize.Width() < nMinWidth ) + maPaperSize.setWidth( nMinWidth ); + else if ( maPaperSize.Width() > nMaxWidth ) + maPaperSize.setWidth( nMaxWidth ); // Minimum/Maximum height: - if ( aPaperSize.Height() < nMinHeight ) - aPaperSize.setHeight( nMinHeight ); - else if ( aPaperSize.Height() > nMaxHeight ) - aPaperSize.setHeight( nMaxHeight ); + if ( maPaperSize.Height() < nMinHeight ) + maPaperSize.setHeight( nMinHeight ); + else if ( maPaperSize.Height() > nMaxHeight ) + maPaperSize.setHeight( nMaxHeight ); } std::shared_ptr<SvxForbiddenCharactersTable> const & ImpEditEngine::GetForbiddenCharsTable() @@ -4312,10 +4542,7 @@ bool ImpEditEngine::IsVisualCursorTravelingEnabled() { bool bVisualCursorTravaling = false; - if( !pCTLOptions ) - pCTLOptions.reset( new SvtCTLOptions ); - - if ( pCTLOptions->IsCTLFontEnabled() && ( pCTLOptions->GetCTLCursorMovement() == SvtCTLOptions::MOVEMENT_VISUAL ) ) + if ( SvtCTLOptions::IsCTLFontEnabled() && ( SvtCTLOptions::GetCTLCursorMovement() == SvtCTLOptions::MOVEMENT_VISUAL ) ) { bVisualCursorTravaling = true; } @@ -4332,7 +4559,7 @@ bool ImpEditEngine::DoVisualCursorTraveling() IMPL_LINK_NOARG(ImpEditEngine, DocModified, LinkParamNone*, void) { - aModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner + maModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |