/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// Destroying templates may otherwise cause unnecessary formatting, // when a parent template is destroyed. // And this after the destruction of the data!
mbDowning = true;
SetUpdateLayout( false );
Dispose(); // 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(!mpUndoManager || typeid(*mpUndoManager) == typeid(EditUndoManager)); delete mpUndoManager;
mpTextRanger.reset();
mpIMEInfos.reset();
mpSpellInfo.reset();
}
GetSelEngine().SetCurView( pView );
SetActiveView( pView ); if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
{ 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 );
}
} elseif ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
{ if (!pView->IsReadOnly())
{
OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => No start ?" ); if( mpIMEInfos )
{ // #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 )
{
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 );
}
}
// in the tiled rendering case, setting bInSelection here has unexpected // consequences - further tiles painting removes the selection // FIXME I believe resetting bInSelection should not be here even in the // 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())
mbInSelection = false;
// Special treatments
EditSelection aCurSel( pView->getImpl().GetEditSelection() ); if ( aCurSel.HasRange() ) returntrue;
// tdf#121039 When in edit mode, editeng is responsible for opening the URL on mouse click bool bUrlOpened = GetEditEnginePtr()->FieldClicked( *pFld ); if (bUrlOpened) returntrue;
// 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();
}
void ImpEditEngine::SetText(const OUString& rText)
{ // RemoveText deletes the undo list!
EditPaM aStartPaM = RemoveText(); bool bUndoCurrentlyEnabled = IsUndoEnabled(); // The text inserted manually can not be made reversible by the user
EnableUndo( false );
for (EditView* pView : maEditViews)
{
pView->getImpl().SetEditSelection( EditSelection( aPaM, aPaM ) ); // If no text then also no Format&Update // => The text remains. if (rText.isEmpty() && IsUpdateLayout())
{
tools::Rectangle aTmpRect( pView->GetOutputArea().TopLeft(),
Size( maPaperSize.Width(), mnCurTextHeight ) );
aTmpRect.Intersection( pView->GetOutputArea() );
pView->InvalidateWindow( aTmpRect );
}
} if (rText.isEmpty()) // otherwise it must be invalidated later, !bFormatted is enough.
mnCurTextHeight = 0;
EnableUndo( bUndoCurrentlyEnabled );
OSL_ENSURE( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "Undo after SetText?" );
}
// tdf#151336: Moving the cursor may advance to another paragraph. When that happens, // special handling is needed for correct visual cursor placement in RTL text. auto fnSetStartOfLineFlag = [&]
{ if (pOutCursorFlags)
{
pOutCursorFlags->bStartOfLine = true;
}
};
auto fnSetParaChangeStartOfLineFlag = [&]
{ if (pOutCursorFlags && aPaM.GetNode() != aOldPaM.GetNode())
{
pOutCursorFlags->bStartOfLine = true;
}
};
auto fnSetEndOfLineFlag = [&]
{ if (pOutCursorFlags)
{
pOutCursorFlags->bEndOfLine = true;
}
};
auto fnSetParaChangeEndOfLineFlag = [&]
{ if (pOutCursorFlags && aPaM.GetNode() != aOldPaM.GetNode())
{
pOutCursorFlags->bEndOfLine = true;
}
};
// May cause, a CreateAnchor or deselection all
maSelEngine.SetCurView(pEditView);
maSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() );
EditPaM aOldEnd(pEditView->getImpl().GetEditSelection().Max());
if ( bKeyModifySelection )
{ // Then the selection is expanded ... or the whole selection is painted in case of tiled rendering.
EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? pEditView->getImpl().GetEditSelection().Min() : aOldEnd, aPaM );
pEditView->getImpl().DrawSelectionXOR( aTmpNewSel );
} else
{
EditSelection aNewSelection(pEditView->getImpl().GetEditSelection());
aNewSelection.Min() = aPaM;
pEditView->getImpl().SetEditSelection(aNewSelection); // const_cast<EditPaM&>(pEditView->getImpl().GetEditSelection().Min()) = aPaM;
}
if ( !bDone && pEditView->IsInsertMode() )
{ // Check if we are within a portion and don't have overwrite mode, then it's easy...
sal_Int32 nPortionStart;
sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart ); const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
EditPaM aNewPaM( rPaM ); if ( nLine ) // same paragraph
{
assert(nLine >= 1); const EditLine& rPrevLine = pPPortion->GetLines()[nLine-1];
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 // Row = cursor if ( aNewPaM.GetIndex() && ( aNewPaM.GetIndex() == rLine.GetStart() ) )
aNewPaM = CursorLeft( aNewPaM );
} else// previous paragraph
{ const ParaPortion* pPrevPortion = GetPrevVisPortion( pPPortion ); if ( pPrevPortion )
{ const EditLine& rLine2 = pPrevPortion->GetLines()[pPrevPortion->GetLines().Count()-1];
aNewPaM.SetNode( pPrevPortion->GetNode() );
aNewPaM.SetIndex(GetChar(*pPrevPortion, rLine2, nX + mnOnePixelInRef));
}
}
EditPaM aNewPaM( rPaM );
aNewPaM.SetIndex( rLine.GetEnd() ); if ( rLine.GetEnd() > rLine.GetStart() )
{ if ( aNewPaM.GetNode()->IsFeature( aNewPaM.GetIndex() - 1 ) )
{ // When a soft break, be in front of it! const EditCharAttrib* pNextFeature = aNewPaM.GetNode()->GetCharAttribs().FindFeature( aNewPaM.GetIndex()-1 ); if ( pNextFeature && ( pNextFeature->GetItem()->Which() == EE_FEATURE_LINEBR ) )
aNewPaM = CursorLeft( aNewPaM );
} elseif ( ( aNewPaM.GetNode()->GetChar( aNewPaM.GetIndex() - 1 ) == ' ' ) && ( aNewPaM.GetIndex() != aNewPaM.GetNode()->Len() ) )
{ // For a Blank in an auto wrapped line, it makes sense, to stand // in front of it, since the user wants to be after the word. // If this is changed, special treatment for Pos1 to End!
aNewPaM = CursorLeft( aNewPaM );
}
} return aNewPaM;
}
EditPaM ImpEditEngine::WordLeft( const EditPaM& rPaM )
{ const sal_Int32 nCurrentPos = rPaM.GetIndex();
EditPaM aNewPaM( rPaM ); if ( nCurrentPos == 0 )
{ // Previous paragraph...
sal_Int32 nCurPara = maEditDoc.GetPos( aNewPaM.GetNode() );
ContentNode* pPrevNode = maEditDoc.GetObject( --nCurPara ); if ( pPrevNode )
{
aNewPaM.SetNode( pPrevNode );
aNewPaM.SetIndex( pPrevNode->Len() );
}
} else
{ // we need to increase the position by 1 when retrieving the locale // since the attribute for the char left to the cursor position is returned
EditPaM aTmpPaM( aNewPaM ); if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
lang::Locale aLocale( GetLocale( aTmpPaM ) );
EditPaM ImpEditEngine::WordRight( const EditPaM& rPaM, sal_Int16 nWordType )
{ const sal_Int32 nMax = rPaM.GetNode()->Len();
EditPaM aNewPaM( rPaM ); if ( aNewPaM.GetIndex() < nMax )
{ // we need to increase the position by 1 when retrieving the locale // since the attribute for the char left to the cursor position is returned
EditPaM aTmpPaM( aNewPaM );
aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
lang::Locale aLocale( GetLocale( aTmpPaM ) );
// we need to increase the position by 1 when retrieving the locale // since the attribute for the char left to the cursor position is returned
EditPaM aTmpPaM( aNewPaM ); if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
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, false);
// we need to increase the position by 1 when retrieving the locale // since the attribute for the char left to the cursor position is returned
EditPaM aTmpPaM( aNewPaM ); if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
lang::Locale aLocale( GetLocale( aTmpPaM ) );
// we need to increase the position by 1 when retrieving the locale // since the attribute for the char left to the cursor position is returned
EditPaM aTmpPaM( aPaM ); if ( aTmpPaM.GetIndex() < aPaM.GetNode()->Len() )
aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
lang::Locale aLocale( GetLocale( aTmpPaM ) );
// To handle fields put the character from the field in the string, // because endOfScript( ... ) will skip the CH_FEATURE, because this is WEAK const EditCharAttrib* pField = pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, 0 ); while ( pField )
{ const OUString aFldText = static_cast<const EditCharAttribField*>(pField)->GetFieldValue(); if ( !aFldText.isEmpty() )
{
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++ )
{ short nTmpType = _xBI->getScriptType( aFldText, nCharInField );
// First char from field wins... if ( nFldScriptType == i18n::ScriptType::WEAK )
{
nFldScriptType = nTmpType;
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.subView(nCharInField,1) ); break;
}
}
} // #112831# Last Field might go from 0xffff to 0x0000
pField = pField->GetEnd() ? pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, pField->GetEnd() ) : nullptr;
}
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;
sal_Int32 nE = ( nPara == nEndPara ) ? aSel.Max().GetIndex() : pParaPortion->GetNode()->Len();
//no selection, just bare cursor if (nStartPara == nEndPara && nS == nE)
{ //If we are not at the start of the paragraph we want the properties of the //preceding character. Otherwise get the properties of the next (or what the //next would have if it existed) if (nS != 0)
--nS; else
++nE;
}
for (const ScriptTypePosInfo & rType : rTypes)
{ bool bStartInRange = rType.nStartPos <= nS && nS < rType.nEndPos; bool bEndInRange = rType.nStartPos < nE && nE <= rType.nEndPos;
if ( !IsEffectivelyVertical() )
{
bR2L = GetDefaultHorizontalTextDirection() == EEHorizontalTextDirection::R2L;
pFrameDirItem = &GetParaAttrib( nPara, EE_PARA_WRITINGDIR ); if ( pFrameDirItem->GetValue() == SvxFrameDirection::Environment )
{ // #103045# if DefaultHorizontalTextDirection is set, use that value, otherwise pool default. if ( GetDefaultHorizontalTextDirection() != EEHorizontalTextDirection::Default )
{
pFrameDirItem = nullptr; // bR2L already set to default horizontal text direction
} else
{ // Use pool default
pFrameDirItem = &GetEmptyItemSet().Get(EE_PARA_WRITINGDIR);
}
}
}
if ( pFrameDirItem )
bR2L = pFrameDirItem->GetValue() == SvxFrameDirection::Horizontal_RL_TB;
// tdf#36709: Metrics conversion should use em and ic values from the bound fonts. // Unfortunately, this currently poses a problem due to font substitution: tests // abort when a missing font is set on a device. // In the interim, use height for all metrics. This is technically not correct, but // should be close enough for common fonts. auto dTextLineHeight = static_cast<double>(aTmpFont.GetPhysTxtSize(GetRefDevice()).Height()); return SvxFontUnitMetrics{ /*em*/ dTextLineHeight, /*ic*/ dTextLineHeight };
}
if ( nNewPos >= nParaCount )
nNewPos = nParaCount;
// Height may change when moving first or last Paragraph
ParaPortion* pRecalc1 = nullptr;
ParaPortion* pRecalc2 = nullptr;
ParaPortion* pRecalc3 = nullptr;
ParaPortion* pRecalc4 = nullptr;
if (nNewPos == 0) // Move to Start
{ if (GetParaPortions().exists(0))
pRecalc1 = &GetParaPortions().getRef(0); if (GetParaPortions().exists(aOldPositions.Min()))
pRecalc2 = &GetParaPortions().getRef(aOldPositions.Min());
if ( IsUndoEnabled() && !IsInUndo())
InsertUndo(std::make_unique<EditUndoMoveParagraphs>(mpEditEngine, aOldPositions, nNewPos));
// do not lose sight of the Position !
ParaPortion* pDestPortion = GetParaPortions().SafeGetObject( nNewPos );
// 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() 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));
// Determine the new location for paragraphs
sal_Int32 nRealNewPos = pDestPortion ? GetParaPortions().GetPos( pDestPortion ) : GetParaPortions().Count();
assert(nRealNewPos != EE_PARA_MAX && "ImpMoveParagraphs: Invalid Position!");
// Add the paragraph portions and content nodes to a new position
sal_Int32 i = 0; for (auto& pPortion : aParagraphPortionVector)
{ if (i == 0)
aSelection.Min().SetNode(pPortion->GetNode());
aSelection.Max().SetNode(pPortion->GetNode());
aSelection.Max().SetIndex(pPortion->GetNode()->Len());
maEditDoc.Insert(nRealNewPos + i, std::move(aContentNodeVector[i]));
GetParaPortions().Insert(nRealNewPos + i, std::move(pPortion));
++i;
}
// Signal end of paragraph moving
maEndMovingParagraphsHdl.Call( aMoveParagraphsInfo );
EditPaM ImpEditEngine::ImpConnectParagraphs(ContentNode* pLeft, ContentNode* pRight, bool bBackward, boolconst isUpdateCursors)
{
OSL_ENSURE( pLeft != pRight, "Join together the same paragraph ?" );
OSL_ENSURE(maEditDoc.GetPos(pLeft) != EE_PARA_MAX, "Inserted node not found (1)");
OSL_ENSURE(maEditDoc.GetPos(pRight) != EE_PARA_MAX, "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 (maEditDoc.GetPos( pLeft ) > maEditDoc.GetPos( pRight ))
{
OSL_ENSURE(false, "ImpConnectParagraphs with wrong order of pLeft/pRight nodes (!)");
std::swap(pLeft, pRight);
}
if ( bBackward )
{
pLeft->SetStyleSheet( pRight->GetStyleSheet() ); // it feels wrong to set pLeft's attribs if pRight is empty, tdf#128046 if ( pRight->Len() )
pLeft->GetContentAttribs().GetItems().Set( pRight->GetContentAttribs().GetItems() );
pLeft->GetCharAttribs().GetDefFont() = pRight->GetCharAttribs().GetDefFont();
}
ParaAttribsChanged( pLeft, true );
// First search for Portions since pRight is gone after ConnectParagraphs.
ParaPortion* pLeftPortion = FindParaPortion( pLeft );
assert(pLeftPortion);
if ( GetStatus().DoOnlineSpelling() )
{
sal_Int32 nEnd = pLeft->Len();
sal_Int32 nInv = nEnd ? nEnd-1 : nEnd;
pLeft->GetWrongList()->ClearWrongs( nInv, static_cast<size_t>(-1), pLeft ); // Possibly remove one
pLeft->GetWrongList()->SetInvalidRange(nInv, nEnd+1); // Take over misspelled words
WrongList* pRWrongs = pRight->GetWrongList(); for (auto & elem : *pRWrongs)
{ if (elem.mnStart != 0) // Not a subsequent
{
elem.mnStart = elem.mnStart + nEnd;
elem.mnEnd = elem.mnEnd + nEnd;
pLeft->GetWrongList()->push_back(elem);
}
}
}
if ( IsCallParaInsertedOrDeleted() )
GetEditEnginePtr()->ParagraphDeleted( nParagraphTobeDeleted );
// the right node is deleted by EditDoc:ConnectParagraphs(). if ( GetTextRanger() )
{ // By joining together the two, the left is although reformatted, // however if its height does not change then the formatting receives // the change of the total text height too late... for ( sal_Int32 n = nParagraphTobeDeleted; n < GetParaPortions().Count(); n++ )
{
ParaPortion& rParaPortion = GetParaPortions().getRef(n);
rParaPortion.MarkSelectionInvalid(0);
rParaPortion.GetLines().Reset();
}
}
if (isUpdateCursors)
{
UpdateSelectionsDelete(deleted);
}
TextModified();
return aPaM;
}
EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 nMode, DeleteMode nDelMode )
{
OSL_ENSURE( !rSel.DbgIsBuggy( maEditDoc ), "Index out of range in DeleteLeftOrRight" );
if ( rSel.HasRange() ) // only then Delete Selection return ImpDeleteSelection( rSel );
EditPaM aCurPos( rSel.Max() );
EditPaM aDelStart( aCurPos );
EditPaM aDelEnd( aCurPos ); if ( nMode == DEL_LEFT )
{ if ( nDelMode == DeleteMode::Simple )
{
sal_uInt16 nCharMode = i18n::CharacterIteratorMode::SKIPCHARACTER; // 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::isVariationSelector(nCode))
nCharMode = i18n::CharacterIteratorMode::SKIPCELL;
}
aDelStart = CursorLeft(aCurPos, nCharMode);
} elseif ( nDelMode == DeleteMode::RestOfWord )
{
aDelStart = StartOfWord( aCurPos ); if ( aDelStart.GetIndex() == aCurPos.GetIndex() )
aDelStart = WordLeft( aCurPos );
} else// DELMODE_RESTOFCONTENT
{
aDelStart.SetIndex( 0 ); if ( aDelStart == aCurPos )
{ // Complete paragraph previous
ContentNode* pPrev = GetPrevVisNode( aCurPos.GetNode() ); if ( pPrev )
aDelStart = EditPaM( pPrev, 0 );
}
}
} else
{ if ( nDelMode == DeleteMode::Simple )
{
aDelEnd = CursorRight( aCurPos );
} elseif ( nDelMode == DeleteMode::RestOfWord )
{
aDelEnd = EndOfWord( aCurPos ); if (aDelEnd.GetIndex() == aCurPos.GetIndex())
{ const sal_Int32 nLen(aCurPos.GetNode()->Len()); // end of para? if (aDelEnd.GetIndex() == nLen)
{
ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() ); if ( pNext )
aDelEnd = EditPaM( pNext, 0 );
} else// there's still something to delete on the right
{
aDelEnd = EndOfWord( WordRight( aCurPos ) );
}
}
} else// DELMODE_RESTOFCONTENT
{
aDelEnd.SetIndex( aCurPos.GetNode()->Len() ); if ( aDelEnd == aCurPos )
{ // Complete paragraph next
ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() ); if ( pNext )
aDelEnd = EditPaM( pNext, pNext->Len() );
}
}
}
// ConnectParagraphs not enough for different Nodes when // DeleteMode::RestOfContent. if ( ( nDelMode == DeleteMode::RestOfContent ) || ( aDelStart.GetNode() == aDelEnd.GetNode() ) ) return ImpDeleteSelection( EditSelection( aDelStart, aDelEnd ) );
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" );
assert(nEndNode != EE_PARA_MAX && "Start > End ?!");
assert( nStartNode <= nEndNode && "Start > End ?!" );
// Remove all nodes in between... for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ )
{ // Always nStartNode+1, due to Remove()!
ImpRemoveParagraph( nStartNode+1 );
}
if ( aStartPaM.GetNode() != aEndPaM.GetNode() )
{ // The Rest of the StartNodes...
ImpRemoveChars( aStartPaM, aStartPaM.GetNode()->Len() - aStartPaM.GetIndex() );
ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() );
assert(pPortion);
pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
void ImpEditEngine::RemoveParagraph( sal_Int32 nPara )
{
DBG_ASSERT(maEditDoc.Count() > 1, "The first paragraph should not be deleted!"); if (maEditDoc.Count() <= 1) return;
// The node is managed by the undo and possibly destroyed!
GetParaPortions().Remove( nPara );
if ( IsCallParaInsertedOrDeleted() )
{
GetEditEnginePtr()->ParagraphDeleted( nPara );
}
// Extra-Space may be determined again in the following. For // ParaAttribsChanged the paragraph is unfortunately formatted again, // however this method should not be time critical! if ( pNextNode )
ParaAttribsChanged( pNextNode );
if (IsUndoEnabled() && !IsInUndo())
{
InsertUndo(std::make_unique<EditUndoDelContent>(mpEditEngine, std::move(pNode), nPara));
} else
{ if ( pNode->GetStyleSheet() )
EndListening(*pNode->GetStyleSheet());
pNode.reset();
}
}
EditPaM ImpEditEngine::AutoCorrect( const EditSelection& rCurSel, sal_Unicode c, bool bOverwrite, vcl::Window const * pFrameWin )
{ // i.e. Calc has special needs regarding a leading single quotation mark // when starting cell input. if (c == '\'' && !IsReplaceLeadingSingleQuotationMark() &&
rCurSel.Min() == rCurSel.Max() && rCurSel.Max().GetIndex() == 0)
{ return InsertTextUserInput( rCurSel, c, bOverwrite );
}
// #i78661 allow application to turn off capitalization of // start sentence explicitly. // (This is done by setting IsFirstWordCapitalization to sal_False.) bool bOldCapitalStartSentence = pAutoCorrect->IsAutoCorrFlag( ACFlags::CapitalStartSentence ); if (!IsFirstWordCapitalization())
{
ESelection aESel( CreateESel(aSel) );
EditSelection aFirstWordSel;
EditSelection aSecondWordSel; if (aESel.end.nPara == 0) // is this the first para?
{ // select first word... // start by checking if para starts with word.
aFirstWordSel = SelectWord( CreateSel(ESelection()) ); if (aFirstWordSel.Min().GetIndex() == 0 && aFirstWordSel.Max().GetIndex() == 0)
{ // para does not start with word -> select next/first word
EditPaM aRightWord( WordRight( aFirstWordSel.Max() ) );
aFirstWordSel = SelectWord( EditSelection( aRightWord ) );
}
// select second word // (sometimes aSel might not point to the end of the first word // but to some following char like '.'. ':', ... // In those cases we need aSecondWordSel to see if aSel // will actually effect the first word.)
EditPaM aRight2Word( WordRight( aFirstWordSel.Max() ) );
aSecondWordSel = SelectWord( EditSelection( aRight2Word ) );
} bool bIsFirstWordInFirstPara = aESel.end.nPara == 0 &&
aFirstWordSel.Max().GetIndex() <= aSel.Max().GetIndex() &&
aSel.Max().GetIndex() <= aSecondWordSel.Min().GetIndex();
if (bIsFirstWordInFirstPara)
pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, IsFirstWordCapitalization() );
}
ContentNode* pNode = aSel.Max().GetNode(); const sal_Int32 nIndex = aSel.Max().GetIndex();
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(
aAuto, rNodeString, nIndex, c, !bOverwrite, mbNbspRunNext, pFrameWin );
aSel.Max().SetIndex( aAuto.GetCursor() );
// #i78661 since the SvxAutoCorrect object used here is // shared we need to reset the value to its original state.
pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, bOldCapitalStartSentence );
} return aSel.Max();
}
EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel,
sal_Unicode c, bool bOverwrite )
{
OSL_ENSURE( c != '\t', "Tab for InsertText ?" );
OSL_ENSURE( c != '\n', "Word wrapping for InsertText ?");
// 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 (SvtCTLOptions::IsCTLSequenceCheckingTypeAndReplace())
{
_xISC->correctInputSequence(aNewText, nTmpPos - 1, c, nCheckMode);
// find position of first character that has changed
sal_Int32 nOldLen = aOldText.getLength();
sal_Int32 nNewLen = aNewText.getLength(); const sal_Unicode *pOldTxt = aOldText.getStr(); const sal_Unicode *pNewTxt = aNewText.getStr();
sal_Int32 nChgPos = 0; while ( nChgPos < nOldLen && nChgPos < nNewLen &&
pOldTxt[nChgPos] == pNewTxt[nChgPos] )
++nChgPos;
// select text from first pos to be changed to current pos
EditSelection aSel( EditPaM( aPaM.GetNode(), nChgPos ), aPaM );
if (!aChgText.isEmpty()) return InsertText( aSel, aChgText ); // implicitly handles undo else return aPaM;
} else
{ // should the character be ignored (i.e. not get inserted) ? if (!_xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode )) return aPaM; // nothing to be done -> no need for undo
}
}
// at this point now we will insert the character 'normally' some lines below...
}
// get word boundaries in order to clear possible WrongList entries // and invalidate all the necessary text (everything after and including the // start of the word) // #i107201# do the expensive SelectWord call only if online spelling is active
EditSelection aCurWord; if ( GetStatus().DoOnlineSpelling() )
aCurWord = SelectWord( EditSelection(aCurPaM), i18n::WordType::DICTIONARY_WORD );
OUString aText(convertLineEnd(rStr, LINEEND_LF)); if (mbFuzzing) //tab expansion performance in editeng is appalling
aText = aText.replaceAll("\t","-");
SfxVoidItem aTabItem( EE_FEATURE_TAB );
// Converts to linesep = \n // Token LINE_SEP query, // since the MAC-Compiler makes something else from \n !
// Start == End => empty line if ( nEnd > nStart )
{
OUString aLine = aText.copy( nStart, nEnd-nStart );
sal_Int32 nExistingChars = aPaM.GetNode()->Len();
sal_Int32 nChars = nExistingChars + aLine.getLength(); if (nChars > MAXCHARSINPARA)
{
sal_Int32 nMaxNewChars = std::max<sal_Int32>(0, MAXCHARSINPARA - nExistingChars); // 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; constauto 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; constauto 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>(mpEditEngine, CreateEPaM(aPaM), aLine)); // Tabs ? if ( aLine.indexOf( '\t' ) == -1 )
aPaM = maEditDoc.InsertText( aPaM, aLine ); else
{
sal_Int32 nStart2 = 0; while ( nStart2 < aLine.getLength() )
{
sal_Int32 nEnd2 = aLine.indexOf( "\t", nStart2 ); if ( nEnd2 == -1 )
nEnd2 = aLine.getLength(); // not dereference!
if ( GetStatus().DoOnlineSpelling() )
{ // now remove the Wrongs (red spell check marks) from both words...
WrongList *pWrongs = aCurPaM.GetNode()->GetWrongList(); if (pWrongs && !pWrongs->empty())
pWrongs->ClearWrongs( aCurWord.Min().GetIndex(), aPaM.GetIndex(), aPaM.GetNode() ); // ... and mark both words as 'to be checked again'
pPortion->MarkInvalid( aCurWord.Min().GetIndex(), aLine.getLength() );
} else
pPortion->MarkInvalid( aCurPaM.GetIndex(), aLine.getLength() );
} if ( nEnd < aText.getLength() )
aPaM = ImpInsertParaBreak( aPaM );
// Optimization: Do not place unnecessarily many getPos to Listen! // Here, as in undo, but also in all other methods.
sal_Int32 nPos = GetParaPortions().GetPos( pPortion );
assert(nPos != EE_PARA_MAX);
ParaPortion* pNewPortion = new ParaPortion( aPaM.GetNode() );
GetParaPortions().Insert(nPos+1, std::unique_ptr<ParaPortion>(pNewPortion));
ParaAttribsChanged( pNewPortion->GetNode() ); if ( IsCallParaInsertedOrDeleted() )
GetEditEnginePtr()->ParagraphInserted( nPos+1 );
if( nullptr != rPaM.GetNode() )
rPaM.GetNode()->checkAndDeleteEmptyAttribs(); // if empty Attributes have emerged.
ContentNode* pNode = new ContentNode( maEditDoc.GetItemPool() ); // If flat mode, then later no Font is set:
pNode->GetCharAttribs().GetDefFont() = maEditDoc.GetDefFont();
if ( GetStatus().DoOnlineSpelling() )
pNode->CreateWrongList();
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 (!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
}
}
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;
}
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);
sal_uInt32 ImpEditEngine::GetTextHeight() const
{
assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" );
OSL_ENSURE( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); return mnCurTextHeight;
}
sal_uInt32 ImpEditEngine::CalcTextWidth( bool bIgnoreExtraSpace )
{ // If still not formatted and not in the process. // Will be brought in the formatting for AutoPageSize. if ( !IsFormatted() && !IsFormatting() )
FormatDoc();
sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace )
{ // If still not formatted and not in the process. // Will be brought in the formatting for AutoPageSize. if ( !IsFormatted() && !IsFormatting() )
FormatDoc();
tools::Long nMaxWidth = 0;
// Over all the paragraphs ...
OSL_ENSURE(GetParaPortions().exists(nPara), "CalcParaWidth: Out of range");
ParaPortion* pPortion = GetParaPortions().SafeGetObject(nPara); if ( pPortion && pPortion->IsVisible() )
{ const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pPortion->GetNode() );
sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pPortion->GetNode() );
auto stMetrics = GetFontUnitMetrics(pPortion->GetNode());
// On the lines of the paragraph ...
sal_Int32 nLines = pPortion->GetLines().Count(); for ( sal_Int32 nLine = 0; nLine < nLines; 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
= scaleXSpacingValue(rLRItem.ResolveTextLeft({}) + nSpaceBeforeAndMinLabelWidth); if ( nLine == 0 )
{
tools::Long nFI = scaleXSpacingValue(rLRItem.ResolveTextFirstLineOffset(stMetrics));
nCurWidth -= nFI; if ( pPortion->GetBulletX() > nCurWidth )
{
nCurWidth += nFI; // LI? if ( pPortion->GetBulletX() > nCurWidth )
nCurWidth = pPortion->GetBulletX();
}
}
nCurWidth += scaleXSpacingValue(rLRItem.ResolveRight({}));
nCurWidth += CalcLineWidth(*pPortion, rLine, bIgnoreExtraSpace); if ( nCurWidth > nMaxWidth )
{
nMaxWidth = nCurWidth;
}
}
}
nMaxWidth++; // widen it, because in CreateLines for >= is wrapped. returnstatic_cast<sal_uInt32>(nMaxWidth);
}
// Calculation of the width without the Indents ...
sal_uInt32 nWidth = 0;
sal_Int32 nPos = rLine.GetStart(); for ( sal_Int32 nTP = rLine.GetStartPortion(); nTP <= rLine.GetEndPortion(); nTP++ )
{ const TextPortion& rTextPortion = rPortion.GetTextPortions()[nTP]; switch ( rTextPortion.GetKind() )
{ case PortionKind::FIELD: case PortionKind::HYPHENATOR: case PortionKind::TAB:
{
nWidth += rTextPortion.GetSize().Width();
} break; case PortionKind::TEXT:
{ if ( ( eJustification != SvxAdjust::Block ) || ( !bIgnoreExtraSpace ) )
{
nWidth += rTextPortion.GetSize().Width();
} else
{
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; case PortionKind::LINEBREAK: break;
}
nPos = nPos + rTextPortion.GetLen();
}
GetRefDevice()->Pop();
return nWidth;
}
tools::Long ImpEditEngine::Calc1ColumnTextHeight()
{
tools::Long nHeight = 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);
} return CallbackResult::Continue;
};
IterateLineAreas(FindLastLineBottom, IterFlag::none); return nHeight;
}
tools::Long ImpEditEngine::CalcTextHeight()
{
assert( IsUpdateLayout() && "Should not be used when Update=FALSE: CalcTextHeight" );
if (mnColumns <= 1) return Calc1ColumnTextHeight(); // 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; 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);
} 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 )
{ if (!IsFormatted())
FormatDoc();
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();
return -1;
}
sal_Int32 ImpEditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine )
{ if (!IsFormatted())
FormatDoc();
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]; return rLine.GetLen();
}
return -1;
}
void ImpEditEngine::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine )
{ if (!IsFormatted())
FormatDoc();
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 if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
{ const EditLine& rLine = pPPortion->GetLines()[nLine];
rStart = rLine.GetStart();
rEnd = rLine.GetEnd();
}
}
sal_Int32 ImpEditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex )
{ if (!IsFormatted())
FormatDoc(); const ContentNode* pNode = GetEditDoc().GetObject( nPara );
OSL_ENSURE( pNode, "GetLineNumberAtIndex: invalid paragraph index" ); if (!pNode) return -1; // we explicitly allow for the index to point at the character right behind the text constbool bValidIndex = /*0 <= nIndex &&*/ nIndex <= pNode->Len();
OSL_ENSURE( bValidIndex, "GetLineNumberAtIndex: invalid index" ); const ParaPortion* pPPortion = maParaPortionList.SafeGetObject(nPara); if (!pPPortion)
{
SAL_WARN( "editeng", "ImpEditEngine::GetLineNumberAtIndex missing ParaPortion"); return -1;
} const EditLineList& rLineList = pPPortion->GetLines(); const sal_Int32 nLineCount = rLineList.Count();
sal_Int32 nLineNo = -1; if (nIndex == pNode->Len())
nLineNo = nLineCount > 0 ? nLineCount - 1 : 0; elseif (bValidIndex) // nIndex < pNode->Len()
{
sal_Int32 nStart = -1, nEnd = -1; for (sal_Int32 i = 0; i < nLineCount && nLineNo == -1; ++i)
{ const EditLine& rLine = rLineList[i];
nStart = rLine.GetStart();
nEnd = rLine.GetEnd(); if (nStart >= 0 && nStart <= nIndex && nEnd >= 0 && nIndex < nEnd)
nLineNo = i;
}
} return nLineNo;
}
sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine )
{ if (!IsFormatted())
FormatDoc();
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() ) )
{ const EditLine& rLine = pPPortion->GetLines()[nLine]; return rLine.GetHeight();
}
return 0xFFFF;
}
tools::Rectangle ImpEditEngine::GetParaBounds( sal_Int32 nPara )
{ if (!IsFormatted())
FormatDoc();
Point aPnt = GetDocPosTopLeft( nPara );
if ( pPPortion )
nHeight = pPPortion->GetHeight();
return nHeight;
}
bool ImpEditEngine::UpdateSelection(EditSelection & rCurSel)
{ bool bChanged = false; for (const std::unique_ptr<DeletedNodeInfo> & aDeletedNode : maDeletedNodes)
{ const DeletedNodeInfo& rInf = *aDeletedNode; if ((rCurSel.Min().GetNode() == rInf.GetNode()) ||
(rCurSel.Max().GetNode() == rInf.GetNode()))
{ // Use ParaPortions, as now also hidden paragraphs have to be // taken into account!
sal_Int32 nPara = rInf.GetPosition(); if (!GetParaPortions().exists(nPara)) // Last paragraph
{
nPara = GetParaPortions().lastIndex();
}
assert(GetParaPortions().exists(nPara) && "Empty Document in UpdateSelections ?"); // Do not end up from a hidden paragraph:
sal_Int32 nCurrentPara = nPara;
sal_Int32 nLastParaIndex = GetParaPortions().lastIndex(); while (nPara <= nLastParaIndex && !GetParaPortions().getRef(nPara).IsVisible())
nPara++; if (nPara > nLastParaIndex) // then also backwards ...
{
nPara = nCurrentPara; while ( nPara && !GetParaPortions().getRef(nPara).IsVisible() )
nPara--;
}
OSL_ENSURE(GetParaPortions().getRef(nPara).IsVisible(), "No visible paragraph found: UpdateSelections" );
ParaPortion& rParaPortion = GetParaPortions().getRef(nPara);
EditSelection aTmpSelection(EditPaM(rParaPortion.GetNode(), 0));
rCurSel = aTmpSelection;
bChanged=true; break; // for loop
}
} if ( !bChanged )
{ // Check Index if node shrunk. if (rCurSel.Min().GetIndex() > rCurSel.Min().GetNode()->Len())
{
rCurSel.Min().SetIndex(rCurSel.Min().GetNode()->Len());
bChanged = true;
} if (rCurSel.Max().GetIndex() > rCurSel.Max().GetNode()->Len())
{
rCurSel.Max().SetIndex(rCurSel.Max().GetNode()->Len());
bChanged = true;
}
} return bChanged;
}
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 : maEditViews)
{
EditSelection aCurSel( pView->getImpl().GetEditSelection() ); if (UpdateSelection(aCurSel))
{
pView->getImpl().SetEditSelection(aCurSel);
} #if ENABLE_YRS for (auto & it : pView->getImpl().m_PeerCursors)
{
EditSelection sel(CreateSel(it.second.second)); if (UpdateSelection(sel))
{
assert(false); // should have called Insert/Delete?
it.second.second = CreateESel(sel);
}
} #endif
}
maDeletedNodes.clear();
}
void ImpEditEngine::UpdateSelectionsDelete(ESelection const& rDeleted)
{
ESelection deleted(rDeleted);
deleted.Adjust(); for (EditView *const pView : maEditViews)
{ // FIXME fails because CreateESel needs the deleted nodes! but if it's // called before deleting the nodes, it will assign wrong nodes... // perhaps the cursors could be stored as ESelection in the first // place, but that would require reviewing all code that inserts or // deletes nodes if it does the update in the correct place... #if 0
EditSelection const sel{pView->getImpl().GetEditSelection()};
ESelection esel{CreateESel(sel)}; bool isChanged{false};
isChanged |= UpdatePosDelete(esel.start, deleted);
isChanged |= UpdatePosDelete(esel.end, deleted); if (isChanged)
{
pView->getImpl().SetEditSelection(CreateSel(esel));
} #else
(void)pView;
(void)deleted; #endif #if ENABLE_YRS for (auto & it : pView->getImpl().m_PeerCursors)
{
UpdatePosDelete(it.second.second.start, deleted);
UpdatePosDelete(it.second.second.end, deleted);
} #endif
}
}
void ImpEditEngine::UpdateSelectionsInsert(ESelection const& rInserted)
{ for (EditView *const pView : maEditViews)
{
EditSelection const sel{pView->getImpl().GetEditSelection()};
ESelection esel{CreateESel(sel)}; // nodes have been inserted, but CreateESel counts them: adjust for this! if (rInserted.start.nPara < esel.start.nPara)
{
esel.start.nPara -= rInserted.end.nPara - rInserted.start.nPara;
} if (rInserted.start.nPara < esel.end.nPara)
{
esel.end.nPara -= rInserted.end.nPara - rInserted.start.nPara;
} bool isChanged{false};
isChanged |= UpdatePosInsert(esel.start, rInserted);
isChanged |= UpdatePosInsert(esel.end, rInserted); if (isChanged)
{
pView->getImpl().SetEditSelection(CreateSel(esel));
} #if ENABLE_YRS for (auto & it : pView->getImpl().m_PeerCursors)
{
UpdatePosInsert(it.second.second.start, rInserted);
UpdatePosInsert(it.second.second.end, rInserted);
} #endif
}
}
void ImpEditEngine::SetActiveView( EditView* pView )
{ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Actually, now bHasVisSel and HideSelection would be necessary !!!
if (pView == mpActiveView) return;
if (mpActiveView && mpActiveView->HasSelection())
mpActiveView->getImpl().DrawSelectionXOR();
mpActiveView = pView;
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, // only the edit views are removed. If mpIMEInfos is still set in that case, // mpIMEInfos->aPos points to an invalid selection. // -> reset mpIMEInfos now // (probably something like this is necessary whenever the content is modified // from the outside)
if ( !pView && mpIMEInfos )
{
mpIMEInfos.reset();
}
}
//Dumping the ODFStream to a XML file for testing purpose /* std::filebuf afilebuf; afilebuf.open ("gsoc17_clipboard_test.xml",std::ios::out); std::ostream os(&afilebuf); os.write((const char*)(pDataObj->GetODFStream().GetData()), pDataObj->GetODFStream().remainingSize()); afilebuf.close();
*/ //dumping ends
// Search in Array... for ( sal_Int32 x = 0; x < nMax; x++ )
{
tools::Long nTmpPosMax = rLine.GetCharPosArray()[nTmpCurIndex+x]; if ( nTmpPosMax > nXInPortion )
{ // Check whether this or the previous...
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" );
if (bSmart && nDiffRight < nDiffLeft)
{ // 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 && static_cast<tools::Long>(rLine.GetCharPosArray()[nTmpCurIndex + x]) == nX)
++x;
}
nOffset = x; break;
}
}
// There should not be any inaccuracies when using the // CharPosArray! Maybe for kerning? // 0xFFF happens for example for Outline-Font when at the very end. if ( nOffset < 0 )
nOffset = nMax;
// 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 = rLine.GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - rLine.GetStart()];
if ( nTextPortionStart != nIndex )
{ // Search within portion... if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) )
{ // End of Portion if ( rPortion.GetKind() == PortionKind::TAB )
{ if ( nTextPortion+1 < rParaPortion.GetTextPortions().Count() )
{ const TextPortion& rNextPortion = rParaPortion.GetTextPortions()[nTextPortion+1]; if ( rNextPortion.GetKind() != PortionKind::TAB )
{ if ( !bPreferPortionStart )
nX = GetXPos(rParaPortion, rLine, nIndex, true ); elseif ( !IsRightToLeft( GetEditDoc().GetPos(rParaPortion.GetNode()) ) )
nX += nPortionTextWidth;
}
} elseif ( !IsRightToLeft( GetEditDoc().GetPos(rParaPortion.GetNode()) ) )
{
nX += nPortionTextWidth;
}
} elseif ( !rPortion.IsRightToLeft() )
{
nX += nPortionTextWidth;
}
} elseif ( rPortion.GetKind() == PortionKind::TEXT )
{
OSL_ENSURE( nIndex != rLine.GetStart(), "Strange behavior in new GetXPos()" );
OSL_ENSURE( !rLine.GetCharPosArray().empty(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" );
if( !rLine.GetCharPosArray().empty() )
{
sal_Int32 nPos = nIndex - 1 - rLine.GetStart(); if (nPos < 0 || o3tl::make_unsigned(nPos) >= rLine.GetCharPosArray().size())
{
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 = rLine.GetCharPosArray()[nPos];
/** Is true if paragraph is in the empty cluster of paragraphs at the end */ bool ImpEditEngine::isInEmptyClusterAtTheEnd(const ParaPortion& rPortion, bool bIsScaling)
{
sal_Int32 nPortion = GetParaPortions().GetPos(&rPortion);
auto& rParagraphs = GetParaPortions(); if (rParagraphs.Count() <= 0) returnfalse;
sal_Int32 nCurrent = rParagraphs.lastIndex();
while (nCurrent > 0 && rParagraphs.getRef(nCurrent).IsEmpty())
{ if (nCurrent == nPortion)
{
OutlinerEditEng* pOutlEditEng{ dynamic_cast<OutlinerEditEng*>(mpEditEngine)}; if (!bIsScaling && pOutlEditEng) return pOutlEditEng->GetDepth(nCurrent) < 0; else returntrue;
}
nCurrent--;
} returnfalse;
}
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();
// In relation between WinWord6/Writer3: // With a proportional line spacing the paragraph spacing is // also manipulated. // Only Writer3: Do not add up, but minimum distance.
// check if distance by LineSpacing > Upper:
sal_uInt16 nExtraSpace = scaleYSpacingValue(lcl_CalcExtraSpace(rLSItem)); if (nExtraSpace > rPortion.mnFirstLineOffset)
{ // Paragraph becomes 'bigger':
rPortion.mnHeight += (nExtraSpace - rPortion.mnFirstLineOffset);
rPortion.mnFirstLineOffset = nExtraSpace;
}
// Determine nFirstLineOffset now f(pNode) => now f(pNode, pPrev):
sal_uInt16 nPrevLower = scaleYSpacingValue(rPrevULItem.GetLower());
// This PrevLower is still in the height of PrevPortion ... if (nPrevLower > rPortion.mnFirstLineOffset)
{ // Paragraph is 'small':
rPortion.mnHeight -= rPortion.mnFirstLineOffset;
rPortion.mnFirstLineOffset = 0;
} elseif ( nPrevLower )
{ // Paragraph becomes 'somewhat smaller':
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 // stuck in the height of PrevPortion. if ( pPrev->IsInvalid() ) return;
for (EditView* pView : maEditViews)
{ if ( bAutoPageSize )
pView->getImpl().RecalcOutputArea(); elseif (pView->getImpl().DoAutoSize())
{
pView->getImpl().ResetOutputArea(tools::Rectangle(pView->getImpl().GetOutputArea().TopLeft(), aNewSize));
}
}
if ( bAutoPageSize || IsFormatted() )
{ // Changing the width has no effect for AutoPageSize, as this is // determined by the text width. // Optimization first after Vobis delivery was enabled ...
FormatFullDoc();
UpdateViews(mpActiveView);
if (IsUpdateLayout() && mpActiveView)
mpActiveView->ShowCursor(false, false);
}
}
bool ImpEditEngine::DoVisualCursorTraveling()
{ // Don't check if it's necessary, because we also need it when leaving the paragraph return IsVisualCursorTravelingEnabled();
}
IMPL_LINK_NOARG(ImpEditEngine, DocModified, LinkParamNone*, void)
{
maModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.105Bemerkung:
(vorverarbeitet am 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.