/* -*- 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; static sal_uInt16 lcl_CalcExtraSpace( ParaPortion*, const SvxLineSpacingItem& rLSItem ) { sal_uInt16 nExtra = 0; if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) { nExtra = rLSItem.GetInterLineSpace(); } return nExtra; } ImpEditEngine::ImpEditEngine( EditEngine* pEE, SfxItemPool* pItemPool ) : aPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), aMinAutoPaperSize( 0x0, 0x0 ), aMaxAutoPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), aEditDoc( pItemPool ), aWordDelimiters(" .,;:-`'?!_=\"{}()[]"), bKernAsianPunctuation(false), bAddExtLeading(false), bIsFormatting(false), bFormatted(false), bInSelection(false), bIsInUndo(false), bUpdate(true), bUndoEnabled(true), bDowning(false), bUseAutoColor(true), bForceAutoColor(false), bCallParaInsertedOrDeleted(false), bImpConvertFirstCall(false), bFirstWordCapitalization(true), mbLastTryMerge(false), mbReplaceLeadingSingleQuotationMark(true) { pEditEngine = pEE; pRefDev = nullptr; pVirtDev = nullptr; pEmptyItemSet = nullptr; pActiveView = nullptr; pSpellInfo = nullptr; pConvInfo = nullptr; pTextObjectPool = nullptr; mpIMEInfos = nullptr; pStylePool = nullptr; pUndoManager = nullptr; pUndoMarkSelection = nullptr; pTextRanger = nullptr; pColorConfig = nullptr; pCTLOptions = nullptr; nCurTextHeight = 0; nCurTextHeightNTP = 0; nBlockNotifications = 0; nBigTextObjectStart = 20; nStretchX = 100; nStretchY = 100; eDefLanguage = LANGUAGE_DONTKNOW; maBackgroundColor = COL_AUTO; nAsianCompressionMode = CharCompressType::NONE; eDefaultHorizontalTextDirection = EE_HTEXTDIR_DEFAULT; aStatus.GetControlWord() = EEControlBits::USECHARATTRIBS | EEControlBits::DOIDLEFORMAT | EEControlBits::PASTESPECIAL | EEControlBits::UNDOATTRIBS | EEControlBits::ALLOWBIGOBJS | EEControlBits::RTFSTYLESHEETS | EEControlBits::FORMAT100; aSelEngine.SetFunctionSet( &aSelFuncSet ); aStatusTimer.SetTimeout( 200 ); aStatusTimer.SetInvokeHandler( LINK( this, ImpEditEngine, StatusTimerHdl ) ); aStatusTimer.SetDebugName( "editeng::ImpEditEngine aStatusTimer" ); aIdleFormatter.SetPriority( TaskPriority::REPAINT ); aIdleFormatter.SetInvokeHandler( LINK( this, ImpEditEngine, IdleFormatHdl ) ); aIdleFormatter.SetDebugName( "editeng::ImpEditEngine aIdleFormatter" ); aOnlineSpellTimer.SetTimeout( 100 ); aOnlineSpellTimer.SetInvokeHandler( LINK( this, ImpEditEngine, OnlineSpellHdl ) ); aOnlineSpellTimer.SetDebugName( "editeng::ImpEditEngine aOnlineSpellTimer" ); // Access data already from here on! SetRefDevice( nullptr ); InitDoc( false ); bCallParaInsertedOrDeleted = true; aEditDoc.SetModifyHdl( LINK( this, ImpEditEngine, DocModified ) ); } ImpEditEngine::~ImpEditEngine() { aStatusTimer.Stop(); aOnlineSpellTimer.Stop(); aIdleFormatter.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 ); mpOwnDev.disposeAndClear(); pVirtDev.disposeAndClear(); delete pEmptyItemSet; delete pUndoManager; delete pTextRanger; delete mpIMEInfos; delete pColorConfig; delete pCTLOptions; delete pSpellInfo; } void ImpEditEngine::SetRefDevice( OutputDevice* pRef ) { if (pRef) pRefDev = pRef; else pRefDev = EditDLL::Get().GetGlobalData()->GetStdVirtualDevice(); nOnePixelInRef = (sal_uInt16)pRefDev->PixelToLogic( Size( 1, 0 ) ).Width(); if ( IsFormatted() ) { FormatFullDoc(); UpdateViews(); } } void ImpEditEngine::SetRefMapMode( const MapMode& rMapMode ) { if ( GetRefDevice()->GetMapMode() == rMapMode ) return; mpOwnDev.disposeAndClear(); mpOwnDev = VclPtr::Create(); pRefDev = mpOwnDev; pRefDev->SetMapMode( MapUnit::MapTwip ); SetRefDevice( pRefDev ); pRefDev->SetMapMode( rMapMode ); nOnePixelInRef = (sal_uInt16)pRefDev->PixelToLogic( Size( 1, 0 ) ).Width(); if ( IsFormatted() ) { FormatFullDoc(); UpdateViews(); } } void ImpEditEngine::InitDoc(bool bKeepParaAttribs) { sal_Int32 nParas = aEditDoc.Count(); for ( sal_Int32 n = bKeepParaAttribs ? 1 : 0; n < nParas; n++ ) { if ( aEditDoc[n]->GetStyleSheet() ) EndListening( *aEditDoc[n]->GetStyleSheet() ); } if ( bKeepParaAttribs ) aEditDoc.RemoveText(); else aEditDoc.Clear(); GetParaPortions().Reset(); ParaPortion* pIniPortion = new ParaPortion( aEditDoc[0] ); GetParaPortions().Insert(0, pIniPortion); bFormatted = false; if ( IsCallParaInsertedOrDeleted() ) { GetEditEnginePtr()->ParagraphDeleted( EE_PARA_ALL ); GetEditEnginePtr()->ParagraphInserted( 0 ); } if ( GetStatus().DoOnlineSpelling() ) aEditDoc.GetObject( 0 )->CreateWrongList(); } EditPaM ImpEditEngine::DeleteSelected(const EditSelection& rSel) { EditPaM aPaM (ImpDeleteSelection(rSel)); return aPaM; } OUString ImpEditEngine::GetSelected( const EditSelection& rSel ) const { OUString aText; if ( !rSel.HasRange() ) return aText; OUString aSep = EditDoc::GetSepStr( LINEEND_LF ); EditSelection aSel( rSel ); aSel.Adjust( aEditDoc ); ContentNode* pStartNode = aSel.Min().GetNode(); ContentNode* pEndNode = aSel.Max().GetNode(); sal_Int32 nStartNode = aEditDoc.GetPos( pStartNode ); sal_Int32 nEndNode = aEditDoc.GetPos( pEndNode ); OSL_ENSURE( nStartNode <= nEndNode, "Selection not sorted ?" ); // 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 sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart! aText += EditDoc::GetParaAsString( pNode, nStartPos, nEndPos ); if ( nNode < nEndNode ) aText += aSep; } return aText; } bool ImpEditEngine::MouseButtonDown( const MouseEvent& rMEvt, EditView* pView ) { GetSelEngine().SetCurView( pView ); SetActiveView( pView ); if (!GetAutoCompleteText().isEmpty()) SetAutoCompleteText( OUString(), true ); GetSelEngine().SelMouseButtonDown( rMEvt ); // Special treatment EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); if ( !rMEvt.IsShift() ) { if ( rMEvt.GetClicks() == 2 ) { // So that the SelectionEngine knows about the anchor. aSelEngine.CursorPosChanging( true, false ); EditSelection aNewSelection( SelectWord( aCurSel ) ); pView->pImpEditView->DrawSelection(); pView->pImpEditView->SetEditSelection( aNewSelection ); pView->pImpEditView->DrawSelection(); 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->DrawSelection(); pView->pImpEditView->SetEditSelection( aNewSelection ); pView->pImpEditView->DrawSelection(); pView->ShowCursor(); } } return true; } void ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) { GetSelEngine().SetCurView( pView ); SetActiveView( pView ); if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput ) { pView->DeleteSelected(); delete mpIMEInfos; 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 = new ImplIMEInfos( aPaM, aOldTextAfterStartPos ); mpIMEInfos->bWasCursorOverwrite = !pView->IsInsertMode(); UndoActionStart( EDITUNDO_INSERT ); } else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput ) { OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => Kein Start ?" ); if( mpIMEInfos ) { // #102812# convert quotes in IME text // works on the last input character, this is escpecially 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 escpecially 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 == '\'' ) ) ) { aSel = DeleteSelected( aSel ); aSel = AutoCorrect( aSel, nCharCode, mpIMEInfos->bWasCursorOverwrite ); pView->pImpEditView->SetEditSelection( aSel ); } } ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex(), 0 ); bool bWasCursorOverwrite = mpIMEInfos->bWasCursorOverwrite; delete mpIMEInfos; mpIMEInfos = nullptr; FormatAndUpdate( pView ); pView->SetInsertMode( !bWasCursorOverwrite ); } UndoActionEnd( EDITUNDO_INSERT ); } else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput ) { OSL_ENSURE( mpIMEInfos, "CommandEventId::ExtTextInput => No Start ?" ); if( mpIMEInfos ) { const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData(); if ( !pData->IsOnlyCursorChanged() ) { EditSelection aSel( mpIMEInfos->aPos ); aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen ); aSel = DeleteSelected( aSel ); aSel = ImpInsertText( aSel, pData->GetText() ); if ( mpIMEInfos->bWasCursorOverwrite ) { sal_Int32 nOldIMETextLen = mpIMEInfos->nLen; sal_Int32 nNewIMETextLen = pData->GetText().getLength(); if ( ( nOldIMETextLen > nNewIMETextLen ) && ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) { // restore old characters sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen; EditPaM aPaM( mpIMEInfos->aPos ); aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen ); ImpInsertText( aPaM, mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) ); } else if ( ( nOldIMETextLen < nNewIMETextLen ) && ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) { // overwrite sal_Int32 nOverwrite = nNewIMETextLen - nOldIMETextLen; if ( ( nOldIMETextLen + nOverwrite ) > mpIMEInfos->aOldTextAfterStartPos.getLength() ) nOverwrite = mpIMEInfos->aOldTextAfterStartPos.getLength() - nOldIMETextLen; OSL_ENSURE( nOverwrite && (nOverwrite < 0xFF00), "IME Overwrite?!" ); EditPaM aPaM( mpIMEInfos->aPos ); aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen ); EditSelection _aSel( aPaM ); _aSel.Max().SetIndex( _aSel.Max().GetIndex() + nOverwrite ); DeleteSelected( _aSel ); } } if ( pData->GetTextAttr() ) { mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() ); mpIMEInfos->bCursor = pData->IsCursorVisible(); } else { mpIMEInfos->DestroyAttribs(); mpIMEInfos->nLen = pData->GetText().getLength(); } ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex(), 0 ); FormatAndUpdate( pView ); } EditSelection aNewSel = EditPaM( mpIMEInfos->aPos.GetNode(), mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() ); pView->SetSelection( CreateESel( aNewSel ) ); pView->SetInsertMode( !pData->IsCursorOverwrite() ); if ( pData->IsCursorVisible() ) pView->ShowCursor(); else pView->HideCursor(); } } else if ( rCEvt.GetCommand() == CommandEventId::InputContextChange ) { } else if ( rCEvt.GetCommand() == CommandEventId::CursorPos ) { if ( mpIMEInfos && mpIMEInfos->nLen ) { EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); Rectangle aR1 = PaMtoEditCursor( aPaM ); sal_Int32 nInputEnd = mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen; if ( !IsFormatted() ) FormatDoc(); ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) ); sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true ); const EditLine& rLine = pParaPortion->GetLines()[nLine]; if ( nInputEnd > rLine.GetEnd() ) nInputEnd = rLine.GetEnd(); Rectangle aR2 = PaMtoEditCursor( EditPaM( aPaM.GetNode(), nInputEnd ), GetCursorFlags::EndOfLine ); Rectangle aRect = pView->GetImpEditView()->GetWindowPos( aR1 ); pView->GetWindow()->SetCursorRect( &aRect, aR2.Left()-aR1.Right() ); } else { pView->GetWindow()->SetCursorRect(); } } else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange ) { const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData(); ESelection aSelection = pView->GetSelection(); aSelection.Adjust(); if( pView->HasSelection() ) { aSelection.nEndPos = aSelection.nStartPos; aSelection.nStartPos += pData->GetStart(); aSelection.nEndPos += pData->GetEnd(); } else { aSelection.nStartPos = pData->GetStart(); aSelection.nEndPos = pData->GetEnd(); } pView->SetSelection( aSelection ); } else if ( rCEvt.GetCommand() == CommandEventId::PrepareReconversion ) { if ( pView->HasSelection() ) { ESelection aSelection = pView->GetSelection(); aSelection.Adjust(); if ( aSelection.nStartPara != aSelection.nEndPara ) { sal_Int32 aParaLen = pEditEngine->GetTextLen( aSelection.nStartPara ); aSelection.nEndPara = aSelection.nStartPara; aSelection.nEndPos = aParaLen; pView->SetSelection( aSelection ); } } } else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition ) { if ( mpIMEInfos && mpIMEInfos->nLen ) { EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); if ( !IsFormatted() ) FormatDoc(); ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) ); sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true ); const EditLine& rLine = pParaPortion->GetLines()[nLine]; std::unique_ptr aRects(new 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(); Rectangle aR2 = GetEditCursor( pParaPortion, nInputPos ); aRects[ i ] = pView->GetImpEditView()->GetWindowPos( aR2 ); } pView->GetWindow()->SetCompositionCharRect( aRects.get(), mpIMEInfos->nLen ); } } GetSelEngine().Command( rCEvt ); } bool ImpEditEngine::MouseButtonUp( const MouseEvent& rMEvt, EditView* pView ) { GetSelEngine().SetCurView( pView ); GetSelEngine().SelMouseButtonUp( rMEvt ); // 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()) bInSelection = false; // Special treatments EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); if ( !aCurSel.HasRange() ) { if ( ( rMEvt.GetClicks() == 1 ) && rMEvt.IsLeft() && !rMEvt.IsMod2() ) { const SvxFieldItem* pFld = pView->GetFieldUnderMousePointer(); if ( pFld ) { EditPaM aPaM( aCurSel.Max() ); sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() ); GetEditEnginePtr()->FieldClicked( *pFld, nPara, aPaM.GetIndex() ); } } } return true; } void ImpEditEngine::ReleaseMouse() { GetSelEngine().ReleaseMouse(); } bool ImpEditEngine::MouseMove( const MouseEvent& rMEvt, EditView* pView ) { // MouseMove is called directly after ShowQuickHelp()! GetSelEngine().SetCurView( pView ); GetSelEngine().SelMouseMove( rMEvt ); return true; } EditPaM ImpEditEngine::InsertText(const EditSelection& aSel, const OUString& rStr) { EditPaM aPaM = ImpInsertText( aSel, rStr ); return aPaM; } void ImpEditEngine::Clear() { InitDoc( false ); EditPaM aPaM = aEditDoc.GetStartPaM(); EditSelection aSel( aPaM ); nCurTextHeight = 0; nCurTextHeightNTP = 0; ResetUndoManager(); for (size_t nView = aEditViews.size(); nView; ) { EditView* pView = aEditViews[--nView]; pView->pImpEditView->SetEditSelection( aSel ); } } EditPaM ImpEditEngine::RemoveText() { InitDoc( true ); EditPaM aStartPaM = aEditDoc.GetStartPaM(); EditSelection aEmptySel( aStartPaM, aStartPaM ); for (EditView* pView : aEditViews) { pView->pImpEditView->SetEditSelection( aEmptySel ); } ResetUndoManager(); return aEditDoc.GetStartPaM(); } 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 ); EditSelection aEmptySel( aStartPaM, aStartPaM ); EditPaM aPaM = aStartPaM; if (!rText.isEmpty()) aPaM = ImpInsertText( aEmptySel, rText ); for (EditView* pView : aEditViews) { pView->pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) ); // If no text then also no Format&Update // => The text remains. if (rText.isEmpty() && GetUpdateMode()) { Rectangle aTmpRect( pView->GetOutputArea().TopLeft(), Size( aPaperSize.Width(), nCurTextHeight ) ); aTmpRect.Intersection( pView->GetOutputArea() ); pView->GetWindow()->Invalidate( aTmpRect ); } } if (rText.isEmpty()) { // otherwise it must be invalidated later, !bFormatted is enough. nCurTextHeight = 0; nCurTextHeightNTP = 0; } EnableUndo( bUndoCurrentlyEnabled ); OSL_ENSURE( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "Undo after SetText?" ); } const SfxItemSet& ImpEditEngine::GetEmptyItemSet() { if ( !pEmptyItemSet ) { pEmptyItemSet = new SfxItemSet( aEditDoc.GetItemPool(), EE_ITEMS_START, EE_ITEMS_END ); for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++) { pEmptyItemSet->ClearItem( nWhich ); } } return *pEmptyItemSet; } // MISC void ImpEditEngine::CursorMoved( ContentNode* pPrevNode ) { // Delete empty attributes, but only if paragraph is not empty! if ( pPrevNode->GetCharAttribs().HasEmptyAttribs() && pPrevNode->Len() ) pPrevNode->GetCharAttribs().DeleteEmptyAttribs( aEditDoc.GetItemPool() ); } void ImpEditEngine::TextModified() { bFormatted = false; if ( GetNotifyHdl().IsSet() ) { EENotify aNotify( EE_NOTIFY_TEXTMODIFIED ); aNotify.pEditEngine = GetEditEnginePtr(); CallNotify( aNotify ); } } void ImpEditEngine::ParaAttribsChanged( ContentNode* pNode ) { assert(pNode && "ParaAttribsChanged: Which one?"); aEditDoc.SetModified( true ); bFormatted = false; ParaPortion* pPortion = FindParaPortion( pNode ); OSL_ENSURE( pPortion, "ParaAttribsChanged: Portion?" ); pPortion->MarkSelectionInvalid( 0, pNode->Len() ); sal_Int32 nPara = aEditDoc.GetPos( pNode ); pEditEngine->ParaAttribsChanged( nPara ); ParaPortion* pNextPortion = GetParaPortions().SafeGetObject( nPara+1 ); // => is formatted again anyway, if Invalid. if ( pNextPortion && !pNextPortion->IsInvalid() ) CalcHeight( pNextPortion ); } // Cursor movements EditSelection ImpEditEngine::MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView ) { // Actually, only necessary for up/down, but whatever. CheckIdleFormatter(); EditPaM aPaM( pEditView->pImpEditView->GetEditSelection().Max() ); EditPaM aOldPaM( aPaM ); TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom; if ( IsVertical() ) eTextDirection = TextDirectionality::TopToBottom_RightToLeft; else if ( IsRightToLeft( GetEditDoc().GetPos( aPaM.GetNode() ) ) ) eTextDirection = TextDirectionality::RightToLeft_TopToBottom; KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection ); bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1(); sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode(); if ( DoVisualCursorTraveling( aPaM.GetNode() ) ) { // Only for simple cursor movement... if ( !bCtrl && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) ) { aPaM = CursorVisualLeftRight( pEditView, aPaM, rKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL, rKeyEvent.GetKeyCode().GetCode() == KEY_LEFT ); nCode = 0; // skip switch statement } } bool bKeyModifySelection = aTranslatedKeyEvent.GetKeyCode().IsShift(); switch ( nCode ) { case KEY_UP: aPaM = CursorUp( aPaM, pEditView ); break; case KEY_DOWN: aPaM = CursorDown( aPaM, pEditView ); break; case KEY_LEFT: aPaM = bCtrl ? WordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL ); break; case KEY_RIGHT: aPaM = bCtrl ? WordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL ); break; case KEY_HOME: aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM ); break; case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM ); break; case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM, pEditView ); break; case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM, pEditView ); break; case css::awt::Key::MOVE_TO_BEGIN_OF_LINE: aPaM = CursorStartOfLine( aPaM ); bKeyModifySelection = false; break; case css::awt::Key::MOVE_TO_END_OF_LINE: aPaM = CursorEndOfLine( aPaM ); bKeyModifySelection = false; break; case css::awt::Key::MOVE_WORD_BACKWARD: aPaM = WordLeft( aPaM ); bKeyModifySelection = false; break; case css::awt::Key::MOVE_WORD_FORWARD: aPaM = WordRight( aPaM ); bKeyModifySelection = false; break; case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH: aPaM = CursorStartOfParagraph( aPaM ); if( aPaM == aOldPaM ) { aPaM = CursorLeft( aPaM ); aPaM = CursorStartOfParagraph( aPaM ); } bKeyModifySelection = false; break; case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH: aPaM = CursorEndOfParagraph( aPaM ); if( aPaM == aOldPaM ) { aPaM = CursorRight( aPaM ); aPaM = CursorEndOfParagraph( aPaM ); } bKeyModifySelection = false; break; case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT: aPaM = CursorStartOfDoc(); bKeyModifySelection = false; break; case css::awt::Key::MOVE_TO_END_OF_DOCUMENT: aPaM = CursorEndOfDoc(); bKeyModifySelection = false; break; case css::awt::Key::SELECT_TO_BEGIN_OF_LINE: aPaM = CursorStartOfLine( aPaM ); bKeyModifySelection = true; break; case css::awt::Key::SELECT_TO_END_OF_LINE: aPaM = CursorEndOfLine( aPaM ); bKeyModifySelection = true; break; case css::awt::Key::SELECT_BACKWARD: aPaM = CursorLeft( aPaM ); bKeyModifySelection = true; break; case css::awt::Key::SELECT_FORWARD: aPaM = CursorRight( aPaM ); bKeyModifySelection = true; break; case css::awt::Key::SELECT_WORD_BACKWARD: aPaM = WordLeft( aPaM ); bKeyModifySelection = true; break; case css::awt::Key::SELECT_WORD_FORWARD: aPaM = WordRight( aPaM ); bKeyModifySelection = true; break; case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH: aPaM = CursorStartOfParagraph( aPaM ); if( aPaM == aOldPaM ) { aPaM = CursorLeft( aPaM ); aPaM = CursorStartOfParagraph( aPaM ); } bKeyModifySelection = true; break; case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH: aPaM = CursorEndOfParagraph( aPaM ); if( aPaM == aOldPaM ) { aPaM = CursorRight( aPaM ); aPaM = CursorEndOfParagraph( aPaM ); } bKeyModifySelection = true; break; case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT: aPaM = CursorStartOfDoc(); bKeyModifySelection = true; break; case css::awt::Key::SELECT_TO_END_OF_DOCUMENT: aPaM = CursorEndOfDoc(); bKeyModifySelection = true; break; } if ( aOldPaM != aPaM ) { CursorMoved( aOldPaM.GetNode() ); if ( aStatus.NotifyCursorMovements() && ( aOldPaM.GetNode() != aPaM.GetNode() ) ) { aStatus.GetStatusWord() = aStatus.GetStatusWord() | EditStatusFlags::CRSRLEFTPARA; aStatus.GetPrevParagraph() = aEditDoc.GetPos( aOldPaM.GetNode() ); } } else aStatus.GetStatusWord() = aStatus.GetStatusWord() | EditStatusFlags::CRSRMOVEFAIL; // May cause, an CreateAnchor or deselection all aSelEngine.SetCurView( pEditView ); aSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() ); EditPaM aOldEnd( pEditView->pImpEditView->GetEditSelection().Max() ); pEditView->pImpEditView->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->DrawSelection( aTmpNewSel ); } else pEditView->pImpEditView->GetEditSelection().Min() = aPaM; return pEditView->pImpEditView->GetEditSelection(); } EditPaM ImpEditEngine::CursorVisualStartEnd( EditView* pEditView, const EditPaM& rPaM, bool bStart ) { EditPaM aPaM( rPaM ); sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() ); ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false ); const EditLine& rLine = pParaPortion->GetLines()[nLine]; bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; if ( !bEmptyLine ) { OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()); const sal_Unicode* pLineString = aLine.getStr(); UErrorCode nError = U_ZERO_ERROR; UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError ); const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/; ubidi_setPara( pBidi, reinterpret_cast(pLineString), aLine.getLength(), nBidiLevel, nullptr, &nError ); sal_Int32 nVisPos = bStart ? 0 : aLine.getLength()-1; const sal_Int32 nLogPos = ubidi_getLogicalIndex( pBidi, nVisPos, &nError ); ubidi_close( pBidi ); aPaM.SetIndex( nLogPos + rLine.GetStart() ); sal_Int32 nTmp; sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTmp, true ); const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; bool bPortionRTL = rTextPortion.IsRightToLeft(); if ( bStart ) { pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 0 : 1 ); // Maybe we must be *behind* the character if ( bPortionRTL && pEditView->IsInsertMode() ) aPaM.SetIndex( aPaM.GetIndex()+1 ); } else { pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 1 : 0 ); if ( !bPortionRTL && pEditView->IsInsertMode() ) aPaM.SetIndex( aPaM.GetIndex()+1 ); } } return aPaM; } EditPaM ImpEditEngine::CursorVisualLeftRight( EditView* pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bVisualToLeft ) { EditPaM aPaM( rPaM ); sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() ); ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false ); const EditLine& rLine = pParaPortion->GetLines()[nLine]; bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; bool bParaRTL = IsRightToLeft( nPara ); bool bDone = false; if ( bEmptyLine ) { if ( bVisualToLeft ) { aPaM = CursorUp( aPaM, pEditView ); if ( aPaM != rPaM ) aPaM = CursorVisualStartEnd( pEditView, aPaM, false ); } else { aPaM = CursorDown( aPaM, pEditView ); if ( aPaM != rPaM ) aPaM = CursorVisualStartEnd( pEditView, aPaM, true ); } bDone = true; } bool bLogicalBackward = bParaRTL ? !bVisualToLeft : bVisualToLeft; 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]; bool bPortionBoundary = ( aPaM.GetIndex() == nPortionStart ) || ( aPaM.GetIndex() == (nPortionStart+rTextPortion.GetLen()) ); sal_uInt16 nRTLLevel = rTextPortion.GetRightToLeftLevel(); // Portion boundary doesn't matter if both have same RTL level sal_Int32 nRTLLevelNextPortion = -1; if ( bPortionBoundary && aPaM.GetIndex() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) ) { sal_Int32 nTmp; sal_Int32 nNextTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex()+1, nTmp, !bLogicalBackward ); const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nNextTextPortion]; nRTLLevelNextPortion = rNextTextPortion.GetRightToLeftLevel(); } if ( !bPortionBoundary || ( nRTLLevel == nRTLLevelNextPortion ) ) { if ( ( bVisualToLeft && !(nRTLLevel%2) ) || ( !bVisualToLeft && (nRTLLevel%2) ) ) { aPaM = CursorLeft( aPaM, nCharacterIteratorMode ); pEditView->pImpEditView->SetCursorBidiLevel( 1 ); } else { aPaM = CursorRight( aPaM, nCharacterIteratorMode ); pEditView->pImpEditView->SetCursorBidiLevel( 0 ); } bDone = true; } } if ( !bDone ) { bool bGotoStartOfNextLine = false; bool bGotoEndOfPrevLine = false; OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()); const sal_Int32 nPosInLine = aPaM.GetIndex() - rLine.GetStart(); const sal_Unicode* pLineString = aLine.getStr(); UErrorCode nError = U_ZERO_ERROR; UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError ); const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/; ubidi_setPara( pBidi, reinterpret_cast(pLineString), aLine.getLength(), nBidiLevel, nullptr, &nError ); if ( !pEditView->IsInsertMode() ) { bool bEndOfLine = nPosInLine == aLine.getLength(); sal_Int32 nVisPos = ubidi_getVisualIndex( pBidi, !bEndOfLine ? nPosInLine : nPosInLine-1, &nError ); if ( bVisualToLeft ) { bGotoEndOfPrevLine = nVisPos == 0; if ( !bEndOfLine ) nVisPos--; } else { bGotoStartOfNextLine = nVisPos == (aLine.getLength() - 1); if ( !bEndOfLine ) nVisPos++; } if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine ) { aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) ); pEditView->pImpEditView->SetCursorBidiLevel( 0 ); } } else { bool bWasBehind = false; bool bBeforePortion = !nPosInLine || pEditView->pImpEditView->GetCursorBidiLevel() == 1; if ( nPosInLine && ( !bBeforePortion ) ) // before the next portion bWasBehind = true; // step one back, otherwise visual will be unusable when rtl portion follows. sal_Int32 nPortionStart; sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, bBeforePortion ); const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; bool bRTLPortion = rTextPortion.IsRightToLeft(); // -1: We are 'behind' the character long nVisPos = (long)ubidi_getVisualIndex( pBidi, bWasBehind ? nPosInLine-1 : nPosInLine, &nError ); if ( bVisualToLeft ) { if ( !bWasBehind || bRTLPortion ) nVisPos--; } else { if ( bWasBehind || bRTLPortion || bBeforePortion ) nVisPos++; } bGotoEndOfPrevLine = nVisPos < 0; bGotoStartOfNextLine = nVisPos >= aLine.getLength(); if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine ) { aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) ); // RTL portion, stay visually on the left side. sal_Int32 _nPortionStart; // sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, !bRTLPortion ); sal_Int32 _nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), _nPortionStart, true ); const TextPortion& _rTextPortion = pParaPortion->GetTextPortions()[_nTextPortion]; if ( bVisualToLeft && !bRTLPortion && _rTextPortion.IsRightToLeft() ) aPaM.SetIndex( aPaM.GetIndex()+1 ); else if ( !bVisualToLeft && bRTLPortion && ( bWasBehind || !_rTextPortion.IsRightToLeft() ) ) aPaM.SetIndex( aPaM.GetIndex()+1 ); pEditView->pImpEditView->SetCursorBidiLevel( _nPortionStart ); } } ubidi_close( pBidi ); if ( bGotoEndOfPrevLine ) { aPaM = CursorUp( aPaM, pEditView ); if ( aPaM != rPaM ) aPaM = CursorVisualStartEnd( pEditView, aPaM, false ); } else if ( bGotoStartOfNextLine ) { aPaM = CursorDown( aPaM, pEditView ); if ( aPaM != rPaM ) aPaM = CursorVisualStartEnd( pEditView, aPaM, true ); } } return aPaM; } EditPaM ImpEditEngine::CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) { EditPaM aCurPaM( rPaM ); EditPaM aNewPaM( aCurPaM ); if ( aCurPaM.GetIndex() ) { sal_Int32 nCount = 1; uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); aNewPaM.SetIndex( _xBI->previousCharacters( aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount)); } else { ContentNode* pNode = aCurPaM.GetNode(); pNode = GetPrevVisNode( pNode ); if ( pNode ) { aNewPaM.SetNode( pNode ); aNewPaM.SetIndex( pNode->Len() ); } } return aNewPaM; } EditPaM ImpEditEngine::CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) { EditPaM aCurPaM( rPaM ); EditPaM aNewPaM( aCurPaM ); if ( aCurPaM.GetIndex() < aCurPaM.GetNode()->Len() ) { uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); sal_Int32 nCount = 1; aNewPaM.SetIndex( _xBI->nextCharacters( aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount)); } else { ContentNode* pNode = aCurPaM.GetNode(); pNode = GetNextVisNode( pNode ); if ( pNode ) { aNewPaM.SetNode( pNode ); aNewPaM.SetIndex( 0 ); } } return aNewPaM; } EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView* pView ) { assert(pView && "No View - No Cursor Movement!"); const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() ); OSL_ENSURE( pPPortion, "No matching portion found: CursorUp "); sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() ); const EditLine& rLine = pPPortion->GetLines()[nLine]; long nX; if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) { nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() ); pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; } else nX = pView->pImpEditView->nTravelXPos; EditPaM aNewPaM( rPaM ); if ( nLine ) // same paragraph { 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+nOnePixelInRef ) ); } } return aNewPaM; } EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView* pView ) { OSL_ENSURE( pView, "No View - No Cursor Movement!" ); const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() ); OSL_ENSURE( pPPortion, "No matching portion found: CursorDown" ); sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() ); long nX; if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) { const EditLine& rLine = pPPortion->GetLines()[nLine]; nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() ); pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; } else nX = pView->pImpEditView->nTravelXPos; EditPaM aNewPaM( rPaM ); if ( nLine < pPPortion->GetLines().Count()-1 ) { const EditLine& rNextLine = pPPortion->GetLines()[nLine+1]; 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 ); } else // next paragraph { const ParaPortion* pNextPortion = GetNextVisPortion( pPPortion ); if ( pNextPortion ) { const EditLine& rLine = pNextPortion->GetLines()[0]; 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 ) ); if ( ( aNewPaM.GetIndex() == rLine.GetEnd() ) && ( aNewPaM.GetIndex() > rLine.GetStart() ) && ( pNextPortion->GetLines().Count() > 1 ) ) aNewPaM = CursorLeft( aNewPaM ); } } return aNewPaM; } EditPaM ImpEditEngine::CursorStartOfLine( const EditPaM& rPaM ) { const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() ); OSL_ENSURE( pCurPortion, "No Portion for the PaM ?" ); sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() ); const EditLine& rLine = pCurPortion->GetLines()[nLine]; EditPaM aNewPaM( rPaM ); aNewPaM.SetIndex( rLine.GetStart() ); return aNewPaM; } EditPaM ImpEditEngine::CursorEndOfLine( const EditPaM& rPaM ) { const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() ); OSL_ENSURE( pCurPortion, "No Portion for the PaM ?" ); sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() ); const EditLine& rLine = pCurPortion->GetLines()[nLine]; 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 ); } else if ( ( 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::CursorStartOfParagraph( const EditPaM& rPaM ) { EditPaM aPaM(rPaM); aPaM.SetIndex(0); return aPaM; } EditPaM ImpEditEngine::CursorEndOfParagraph( const EditPaM& rPaM ) { EditPaM aPaM(rPaM); aPaM.SetIndex(rPaM.GetNode()->Len()); return aPaM; } EditPaM ImpEditEngine::CursorStartOfDoc() { EditPaM aPaM( aEditDoc.GetObject( 0 ), 0 ); return aPaM; } EditPaM ImpEditEngine::CursorEndOfDoc() { ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 ); ParaPortion* pLastPortion = GetParaPortions().SafeGetObject( aEditDoc.Count()-1 ); OSL_ENSURE( pLastNode && pLastPortion, "CursorEndOfDoc: Node or Portion not found" ); if ( !pLastPortion->IsVisible() ) { pLastNode = GetPrevVisNode( pLastPortion->GetNode() ); OSL_ENSURE( pLastNode, "Kein sichtbarer Absatz?" ); if ( !pLastNode ) pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 ); } EditPaM aPaM( pLastNode, pLastNode->Len() ); return aPaM; } EditPaM ImpEditEngine::PageUp( const EditPaM& rPaM, EditView* pView ) { Rectangle aRect = PaMtoEditCursor( rPaM ); Point aTopLeft = aRect.TopLeft(); aTopLeft.Y() -= pView->GetVisArea().GetHeight() *9/10; aTopLeft.X() += nOnePixelInRef; if ( aTopLeft.Y() < 0 ) { aTopLeft.Y() = 0; } return GetPaM( aTopLeft ); } EditPaM ImpEditEngine::PageDown( const EditPaM& rPaM, EditView* pView ) { Rectangle aRect = PaMtoEditCursor( rPaM ); Point aBottomRight = aRect.BottomRight(); aBottomRight.Y() += pView->GetVisArea().GetHeight() *9/10; aBottomRight.X() += nOnePixelInRef; long nHeight = GetTextHeight(); if ( aBottomRight.Y() > nHeight ) { aBottomRight.Y() = nHeight-2; } return GetPaM( aBottomRight ); } EditPaM ImpEditEngine::WordLeft( const EditPaM& rPaM ) { const sal_Int32 nCurrentPos = rPaM.GetIndex(); EditPaM aNewPaM( rPaM ); if ( nCurrentPos == 0 ) { // Previous paragraph... sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() ); ContentNode* pPrevNode = aEditDoc.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 ) ); uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); i18n::Boundary aBoundary = _xBI->getWordBoundary(aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); if ( aBoundary.startPos >= nCurrentPos ) aBoundary = _xBI->previousWord( aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES); aNewPaM.SetIndex( ( aBoundary.startPos != (-1) ) ? aBoundary.startPos : 0 ); } return aNewPaM; } 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 ) ); uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); i18n::Boundary aBoundary = _xBI->nextWord( aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), aLocale, nWordType); aNewPaM.SetIndex( aBoundary.startPos ); } // not 'else', maybe the index reached nMax now... if ( aNewPaM.GetIndex() >= nMax ) { // Next paragraph ... sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() ); ContentNode* pNextNode = aEditDoc.GetObject( ++nCurPara ); if ( pNextNode ) { aNewPaM.SetNode( pNextNode ); aNewPaM.SetIndex( 0 ); } } return aNewPaM; } EditPaM ImpEditEngine::StartOfWord( const EditPaM& rPaM ) { EditPaM aNewPaM( rPaM ); // 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() ); i18n::Boundary aBoundary = _xBI->getWordBoundary( rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); aNewPaM.SetIndex( aBoundary.startPos ); return aNewPaM; } EditPaM ImpEditEngine::EndOfWord( const EditPaM& rPaM ) { EditPaM aNewPaM( rPaM ); // 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() ); i18n::Boundary aBoundary = _xBI->getWordBoundary( rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); aNewPaM.SetIndex( aBoundary.endPos ); return aNewPaM; } EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 nWordType, bool bAcceptStartOfWord ) { EditSelection aNewSel( rCurSel ); EditPaM aPaM( rCurSel.Max() ); // 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 ) ); uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); sal_Int16 nType = _xBI->getWordType( aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale); if ( nType == i18n::WordType::ANY_WORD ) { i18n::Boundary aBoundary = _xBI->getWordBoundary( aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale, nWordType, true); // don't select when cursor at end of word if ( ( aBoundary.endPos > aPaM.GetIndex() ) && ( ( aBoundary.startPos < aPaM.GetIndex() ) || ( bAcceptStartOfWord && ( aBoundary.startPos == aPaM.GetIndex() ) ) ) ) { aNewSel.Min().SetIndex( aBoundary.startPos ); aNewSel.Max().SetIndex( aBoundary.endPos ); } } return aNewSel; } EditSelection ImpEditEngine::SelectSentence( const EditSelection& rCurSel ) const { uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); const EditPaM& rPaM = rCurSel.Min(); const ContentNode* pNode = rPaM.GetNode(); // #i50710# line breaks are marked with 0x01 - the break iterator prefers 0x0a for that OUString sParagraph = pNode->GetString(); sParagraph = sParagraph.replaceAll("\x01", "\x0a"); //return Null if search starts at the beginning of the string sal_Int32 nStart = rPaM.GetIndex() ? _xBI->beginOfSentence( sParagraph, rPaM.GetIndex(), GetLocale( rPaM ) ) : 0; sal_Int32 nEnd = _xBI->endOfSentence( pNode->GetString(), rPaM.GetIndex(), GetLocale(rPaM)); EditSelection aNewSel( rCurSel ); OSL_ENSURE(pNode->Len() ? (nStart < pNode->Len()) : (nStart == 0), "sentence start index out of range"); OSL_ENSURE(nEnd <= pNode->Len(), "sentence end index out of range"); aNewSel.Min().SetIndex( nStart ); aNewSel.Max().SetIndex( nEnd ); return aNewSel; } bool ImpEditEngine::IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const { uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); if (!pCTLOptions) pCTLOptions = 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() && nFirstPos != 0 && /* first char needs not to be checked */ _xBI.is() && i18n::ScriptType::COMPLEX == _xBI->getScriptType( OUString( nChar ), 0 ); return bIsSequenceChecking; } static bool lcl_HasStrongLTR ( const OUString& rTxt, sal_Int32 nStart, sal_Int32 nEnd ) { for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx ) { const UCharDirection nCharDir = u_charDirection ( rTxt[ nCharIdx ] ); if ( nCharDir == U_LEFT_TO_RIGHT || nCharDir == U_LEFT_TO_RIGHT_EMBEDDING || nCharDir == U_LEFT_TO_RIGHT_OVERRIDE ) return true; } return false; } void ImpEditEngine::InitScriptTypes( sal_Int32 nPara ) { ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; rTypes.clear(); ContentNode* pNode = pParaPortion->GetNode(); if ( pNode->Len() ) { uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); OUString aText = pNode->GetString(); // 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 ) { OUString aFldText = static_cast(pField)->GetFieldValue(); if ( !aFldText.isEmpty() ) { aText = aText.replaceAt( pField->GetStart(), 1, aFldText.copy(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.copy(nCharInField,1) ); } // ... but if the first one is LATIN, and there are CJK or CTL chars too, // we prefer that ScripType because we need an other font. if ( ( nTmpType == i18n::ScriptType::ASIAN ) || ( nTmpType == i18n::ScriptType::COMPLEX ) ) { aText = aText.replaceAt( pField->GetStart(), 1, aFldText.copy(nCharInField,1) ); break; } } } // #112831# Last Field might go from 0xffff to 0x0000 pField = pField->GetEnd() ? pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, pField->GetEnd() ) : nullptr; } OUString aOUText( aText ); sal_Int32 nTextLen = aOUText.getLength(); sal_Int32 nPos = 0; short nScriptType = _xBI->getScriptType( aOUText, nPos ); rTypes.push_back( ScriptTypePosInfo( nScriptType, nPos, nTextLen ) ); nPos = _xBI->endOfScript( aOUText, nPos, nScriptType ); while ( ( nPos != (-1) ) && ( nPos < nTextLen ) ) { rTypes.back().nEndPos = nPos; nScriptType = _xBI->getScriptType( aOUText, nPos ); long nEndPos = _xBI->endOfScript( aOUText, nPos, nScriptType ); if ( ( nScriptType == i18n::ScriptType::WEAK ) || ( nScriptType == rTypes.back().nScriptType ) ) { // Expand last ScriptTypePosInfo, don't create weak or unnecessary portions rTypes.back().nEndPos = nEndPos; } else { if ( _xBI->getScriptType( aOUText, nPos - 1 ) == i18n::ScriptType::WEAK ) { switch ( u_charType(aOUText.iterateCodePoints(&nPos, 0) ) ) { case U_NON_SPACING_MARK: case U_ENCLOSING_MARK: case U_COMBINING_SPACING_MARK: --nPos; rTypes.back().nEndPos--; break; } } rTypes.push_back( ScriptTypePosInfo( nScriptType, nPos, nTextLen ) ); } nPos = nEndPos; } if ( rTypes[0].nScriptType == i18n::ScriptType::WEAK ) rTypes[0].nScriptType = ( rTypes.size() > 1 ) ? rTypes[1].nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() ); // create writing direction information: if ( pParaPortion->aWritingDirectionInfos.empty() ) InitWritingDirections( nPara ); // i89825: Use CTL font for numbers embedded into an RTL run: WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; for (WritingDirectionInfo & rDirInfo : rDirInfos) { const sal_Int32 nStart = rDirInfo.nStartPos; const sal_Int32 nEnd = rDirInfo.nEndPos; const sal_uInt8 nCurrDirType = rDirInfo.nType; if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run ( nCurrDirType > UBIDI_LTR && !lcl_HasStrongLTR( aText, nStart, nEnd ) ) ) // non-strong text in embedded LTR run { size_t nIdx = 0; // Skip entries in ScriptArray which are not inside the RTL run: while ( nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart ) ++nIdx; // Remove any entries *inside* the current run: while ( nIdx < rTypes.size() && rTypes[nIdx].nEndPos <= nEnd ) rTypes.erase( rTypes.begin()+nIdx ); // special case: if(nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart && rTypes[nIdx].nEndPos > nEnd) { rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( rTypes[nIdx].nScriptType, nEnd, rTypes[nIdx].nEndPos ) ); rTypes[nIdx].nEndPos = nStart; } if( nIdx ) rTypes[nIdx - 1].nEndPos = nStart; rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( i18n::ScriptType::COMPLEX, nStart, nEnd) ); ++nIdx; if( nIdx < rTypes.size() ) rTypes[nIdx].nStartPos = nEnd; } } } } namespace { struct FindByPos { explicit FindByPos(sal_Int32 nPos) : mnPos(nPos) { } bool operator()(const ScriptTypePosInfos::value_type& rValue) { return rValue.nStartPos <= mnPos && rValue.nEndPos >= mnPos; } private: sal_Int32 mnPos; }; } sal_uInt16 ImpEditEngine::GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos ) const { sal_uInt16 nScriptType = 0; if ( pEndPos ) *pEndPos = rPaM.GetNode()->Len(); if ( rPaM.GetNode()->Len() ) { sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() ); const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); if ( pParaPortion->aScriptInfos.empty() ) const_cast(this)->InitScriptTypes( nPara ); const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; const sal_Int32 nPos = rPaM.GetIndex(); ScriptTypePosInfos::const_iterator itr = std::find_if(rTypes.begin(), rTypes.end(), FindByPos(nPos)); if(itr != rTypes.end()) { nScriptType = itr->nScriptType; if( pEndPos ) *pEndPos = itr->nEndPos; } } return nScriptType ? nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() ); } SvtScriptType ImpEditEngine::GetItemScriptType( const EditSelection& rSel ) const { EditSelection aSel( rSel ); aSel.Adjust( aEditDoc ); SvtScriptType nScriptType = SvtScriptType::NONE; sal_Int32 nStartPara = GetEditDoc().GetPos( aSel.Min().GetNode() ); sal_Int32 nEndPara = GetEditDoc().GetPos( aSel.Max().GetNode() ); for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) { const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); if ( pParaPortion->aScriptInfos.empty() ) const_cast(this)->InitScriptTypes( nPara ); const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; // 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 (bStartInRange || bEndInRange) { if ( rType.nScriptType != i18n::ScriptType::WEAK ) nScriptType |= SvtLanguageOptions::FromI18NToSvtScriptType( rType.nScriptType ); } } } return bool(nScriptType) ? nScriptType : SvtLanguageOptions::GetScriptTypeOfLanguage( GetDefaultLanguage() ); } bool ImpEditEngine::IsScriptChange( const EditPaM& rPaM ) const { bool bScriptChange = false; if ( rPaM.GetNode()->Len() ) { sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() ); const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); if ( pParaPortion->aScriptInfos.empty() ) const_cast(this)->InitScriptTypes( nPara ); const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; const sal_Int32 nPos = rPaM.GetIndex(); for (const ScriptTypePosInfo & rType : rTypes) { if ( rType.nStartPos == nPos ) { bScriptChange = true; break; } } } return bScriptChange; } bool ImpEditEngine::HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const { bool bTypeFound = false; const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); if ( pParaPortion->aScriptInfos.empty() ) const_cast(this)->InitScriptTypes( nPara ); const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; for ( size_t n = rTypes.size(); n && !bTypeFound; ) { if ( rTypes[--n].nScriptType == nType ) bTypeFound = true; } return bTypeFound; } void ImpEditEngine::InitWritingDirections( sal_Int32 nPara ) { ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); WritingDirectionInfos& rInfos = pParaPortion->aWritingDirectionInfos; rInfos.clear(); bool bCTL = false; ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; for (ScriptTypePosInfo & rType : rTypes) { if ( rType.nScriptType == i18n::ScriptType::COMPLEX ) { bCTL = true; break; } } const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/; if ( ( bCTL || ( nBidiLevel == 1 /*RTL*/ ) ) && pParaPortion->GetNode()->Len() ) { OUString aText = pParaPortion->GetNode()->GetString(); // Bidi functions from icu 2.0 UErrorCode nError = U_ZERO_ERROR; UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError ); nError = U_ZERO_ERROR; ubidi_setPara( pBidi, reinterpret_cast(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError ); nError = U_ZERO_ERROR; int32_t nCount = ubidi_countRuns( pBidi, &nError ); /* ubidi_countRuns can return -1 in case of error */ if (nCount > 0) { int32_t nStart = 0; int32_t nEnd; UBiDiLevel nCurrDir; for (int32_t nIdx = 0; nIdx < nCount; ++nIdx) { ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir ); rInfos.push_back( WritingDirectionInfo( nCurrDir, nStart, nEnd ) ); nStart = nEnd; } } ubidi_close( pBidi ); } // No infos mean no CTL and default dir is L2R... if ( rInfos.empty() ) rInfos.push_back( WritingDirectionInfo( 0, 0, pParaPortion->GetNode()->Len() ) ); } bool ImpEditEngine::IsRightToLeft( sal_Int32 nPara ) const { bool bR2L = false; const SvxFrameDirectionItem* pFrameDirItem = nullptr; if ( !IsVertical() ) { bR2L = GetDefaultHorizontalTextDirection() == EE_HTEXTDIR_R2L; pFrameDirItem = &static_cast(GetParaAttrib( nPara, EE_PARA_WRITINGDIR )); if ( pFrameDirItem->GetValue() == FRMDIR_ENVIRONMENT ) { // #103045# if DefaultHorizontalTextDirection is set, use that value, otherwise pool default. if ( GetDefaultHorizontalTextDirection() != EE_HTEXTDIR_DEFAULT ) { pFrameDirItem = nullptr; // bR2L already set to default horizontal text direction } else { // Use pool default pFrameDirItem = &static_cast(const_cast(this)->GetEmptyItemSet().Get( EE_PARA_WRITINGDIR )); } } } if ( pFrameDirItem ) bR2L = pFrameDirItem->GetValue() == FRMDIR_HORI_RIGHT_TOP; return bR2L; } bool ImpEditEngine::HasDifferentRTLLevels( const ContentNode* pNode ) { sal_Int32 nPara = GetEditDoc().GetPos( pNode ); ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); bool bHasDifferentRTLLevels = false; sal_uInt16 nRTLLevel = IsRightToLeft( nPara ) ? 1 : 0; for ( sal_Int32 n = 0; n < (sal_Int32)pParaPortion->GetTextPortions().Count(); n++ ) { const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n]; if ( rTextPortion.GetRightToLeftLevel() != nRTLLevel ) { bHasDifferentRTLLevels = true; break; } } return bHasDifferentRTLLevels; } sal_uInt8 ImpEditEngine::GetRightToLeft( sal_Int32 nPara, sal_Int32 nPos, sal_Int32* pStart, sal_Int32* pEnd ) { sal_uInt8 nRightToLeft = 0; ContentNode* pNode = aEditDoc.GetObject( nPara ); if ( pNode && pNode->Len() ) { ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); if ( pParaPortion->aWritingDirectionInfos.empty() ) InitWritingDirections( nPara ); WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; for (const WritingDirectionInfo & rDirInfo : rDirInfos) { if ( ( rDirInfo.nStartPos <= nPos ) && ( rDirInfo.nEndPos >= nPos ) ) { nRightToLeft = rDirInfo.nType; if ( pStart ) *pStart = rDirInfo.nStartPos; if ( pEnd ) *pEnd = rDirInfo.nEndPos; break; } } } return nRightToLeft; } SvxAdjust ImpEditEngine::GetJustification( sal_Int32 nPara ) const { SvxAdjust eJustification = SVX_ADJUST_LEFT; if ( !aStatus.IsOutliner() ) { eJustification = static_cast(GetParaAttrib( nPara, EE_PARA_JUST )).GetAdjust(); if ( IsRightToLeft( nPara ) ) { if ( eJustification == SVX_ADJUST_LEFT ) eJustification = SVX_ADJUST_RIGHT; else if ( eJustification == SVX_ADJUST_RIGHT ) eJustification = SVX_ADJUST_LEFT; } } return eJustification; } SvxCellJustifyMethod ImpEditEngine::GetJustifyMethod( sal_Int32 nPara ) const { const SvxJustifyMethodItem& rItem = static_cast( GetParaAttrib(nPara, EE_PARA_JUST_METHOD)); return static_cast(rItem.GetEnumValue()); } SvxCellVerJustify ImpEditEngine::GetVerJustification( sal_Int32 nPara ) const { const SvxVerJustifyItem& rItem = static_cast( GetParaAttrib(nPara, EE_PARA_VER_JUST)); return static_cast(rItem.GetEnumValue()); } // Text changes void ImpEditEngine::ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars ) { if ( IsUndoEnabled() && !IsInUndo() ) { OUString aStr( rPaM.GetNode()->Copy( rPaM.GetIndex(), nChars ) ); // Check whether attributes are deleted or changed: const sal_Int32 nStart = rPaM.GetIndex(); const sal_Int32 nEnd = nStart + nChars; const CharAttribList::AttribsType& rAttribs = rPaM.GetNode()->GetCharAttribs().GetAttribs(); for (const auto & rAttrib : rAttribs) { const EditCharAttrib& rAttr = *rAttrib.get(); if (rAttr.GetEnd() >= nStart && rAttr.GetStart() < nEnd) { EditSelection aSel( rPaM ); aSel.Max().SetIndex( aSel.Max().GetIndex() + nChars ); EditUndoSetAttribs* pAttrUndo = CreateAttribUndo( aSel, GetEmptyItemSet() ); InsertUndo( pAttrUndo ); break; // for } } InsertUndo(new EditUndoRemoveChars(pEditEngine, CreateEPaM(rPaM), aStr)); } aEditDoc.RemoveChars( rPaM, nChars ); } EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 nNewPos ) { aOldPositions.Justify(); bool bValidAction = ( (long)nNewPos < aOldPositions.Min() ) || ( (long)nNewPos > aOldPositions.Max() ); OSL_ENSURE( bValidAction, "Move in itself?" ); OSL_ENSURE( aOldPositions.Max() <= (long)GetParaPortions().Count(), "totally over it: MoveParagraphs" ); EditSelection aSelection; if ( !bValidAction ) { aSelection = aEditDoc.GetStartPaM(); return aSelection; } sal_Int32 nParaCount = GetParaPortions().Count(); 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 { pRecalc1 = GetParaPortions()[0]; pRecalc2 = GetParaPortions()[aOldPositions.Min()]; } else if ( nNewPos == nParaCount ) { pRecalc1 = GetParaPortions()[nParaCount-1]; pRecalc2 = GetParaPortions()[aOldPositions.Max()]; } if ( aOldPositions.Min() == 0 ) // Move from Start { pRecalc3 = GetParaPortions()[0]; pRecalc4 = GetParaPortions()[aOldPositions.Max()+1]; } else if ( aOldPositions.Max() == (nParaCount-1) ) { pRecalc3 = GetParaPortions()[aOldPositions.Max()]; pRecalc4 = GetParaPortions()[aOldPositions.Min()-1]; } MoveParagraphsInfo aMoveParagraphsInfo( aOldPositions.Min(), aOldPositions.Max(), nNewPos ); aBeginMovingParagraphsHdl.Call( aMoveParagraphsInfo ); if ( IsUndoEnabled() && !IsInUndo()) InsertUndo(new EditUndoMoveParagraphs(pEditEngine, aOldPositions, nNewPos)); // do not lose sight of the Position ! ParaPortion* pDestPortion = GetParaPortions().SafeGetObject( nNewPos ); ParaPortionList aTmpPortionList; for (long i = aOldPositions.Min(); i <= aOldPositions.Max(); i++ ) { // always aOldPositions.Min(), since Remove(). ParaPortion* pTmpPortion = GetParaPortions().Release(aOldPositions.Min()); aEditDoc.Release( aOldPositions.Min() ); aTmpPortionList.Append(pTmpPortion); } sal_Int32 nRealNewPos = pDestPortion ? GetParaPortions().GetPos( pDestPortion ) : GetParaPortions().Count(); OSL_ENSURE( nRealNewPos != EE_PARA_NOT_FOUND, "ImpMoveParagraphs: Invalid Position!" ); for (sal_Int32 i = 0; i < aTmpPortionList.Count(); ++i) { ParaPortion* pTmpPortion = aTmpPortionList[i]; if ( i == 0 ) aSelection.Min().SetNode( pTmpPortion->GetNode() ); aSelection.Max().SetNode( pTmpPortion->GetNode() ); aSelection.Max().SetIndex( pTmpPortion->GetNode()->Len() ); ContentNode* pN = pTmpPortion->GetNode(); aEditDoc.Insert(nRealNewPos+i, pN); GetParaPortions().Insert(nRealNewPos+i, pTmpPortion); } aEndMovingParagraphsHdl.Call( aMoveParagraphsInfo ); if ( GetNotifyHdl().IsSet() ) { EENotify aNotify( EE_NOTIFY_PARAGRAPHSMOVED ); aNotify.pEditEngine = GetEditEnginePtr(); aNotify.nParagraph = nNewPos; aNotify.nParam1 = aOldPositions.Min(); aNotify.nParam2 = aOldPositions.Max(); CallNotify( aNotify ); } aEditDoc.SetModified( true ); if ( pRecalc1 ) CalcHeight( pRecalc1 ); if ( pRecalc2 ) CalcHeight( pRecalc2 ); if ( pRecalc3 ) CalcHeight( pRecalc3 ); if ( pRecalc4 ) CalcHeight( pRecalc4 ); while( aTmpPortionList.Count() > 0 ) aTmpPortionList.Release( aTmpPortionList.Count() - 1 ); #if OSL_DEBUG_LEVEL > 0 ParaPortionList::DbgCheck(GetParaPortions(), aEditDoc); #endif return aSelection; } 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)" ); // #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 )) { OSL_ENSURE(false, "ImpConnectParagraphs wit wrong order of pLeft/pRight nodes (!)"); std::swap(pLeft, pRight); } sal_Int32 nParagraphTobeDeleted = aEditDoc.GetPos( pRight ); aDeletedNodes.push_back(o3tl::make_unique( pRight, nParagraphTobeDeleted )); GetEditEnginePtr()->ParagraphConnected( aEditDoc.GetPos( pLeft ), aEditDoc.GetPos( pRight ) ); if ( IsUndoEnabled() && !IsInUndo() ) { InsertUndo( new EditUndoConnectParas(pEditEngine, aEditDoc.GetPos( pLeft ), pLeft->Len(), pLeft->GetContentAttribs().GetItems(), pRight->GetContentAttribs().GetItems(), pLeft->GetStyleSheet(), pRight->GetStyleSheet(), bBackward ) ); } if ( bBackward ) { pLeft->SetStyleSheet( pRight->GetStyleSheet() ); pLeft->GetContentAttribs().GetItems().Set( pRight->GetContentAttribs().GetItems() ); pLeft->GetCharAttribs().GetDefFont() = pRight->GetCharAttribs().GetDefFont(); } ParaAttribsChanged( pLeft ); // First search for Portions since pRight is gone after ConnectParagraphs. ParaPortion* pLeftPortion = FindParaPortion( pLeft ); OSL_ENSURE( pLeftPortion, "Blind Portion in ImpConnectParagraphs(1)" ); if ( GetStatus().DoOnlineSpelling() ) { sal_Int32 nEnd = pLeft->Len(); sal_Int32 nInv = nEnd ? nEnd-1 : nEnd; pLeft->GetWrongList()->ClearWrongs( nInv, static_cast(-1), pLeft ); // Possibly remove one pLeft->GetWrongList()->SetInvalidRange(nInv, nEnd+1); // Take over misspelled words WrongList* pRWrongs = pRight->GetWrongList(); for (WrongList::iterator i = pRWrongs->begin(); i < pRWrongs->end(); ++i) { if (i->mnStart != 0) // Not a subsequent { i->mnStart = i->mnStart + nEnd; i->mnEnd = i->mnEnd + nEnd; pLeft->GetWrongList()->push_back(*i); } } } if ( IsCallParaInsertedOrDeleted() ) GetEditEnginePtr()->ParagraphDeleted( nParagraphTobeDeleted ); EditPaM aPaM = aEditDoc.ConnectParagraphs( pLeft, pRight ); GetParaPortions().Remove( nParagraphTobeDeleted ); pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex(), pLeft->Len() ); // 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 hight too late... for ( sal_Int32 n = nParagraphTobeDeleted; n < GetParaPortions().Count(); n++ ) { ParaPortion* pPP = GetParaPortions()[n]; pPP->MarkSelectionInvalid( 0, pPP->GetNode()->Len() ); pPP->GetLines().Reset(); } } TextModified(); return aPaM; } EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 nMode, DeleteMode nDelMode ) { OSL_ENSURE( !EditSelection( rSel ).DbgIsBuggy( aEditDoc ), "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 ) { aDelStart = CursorLeft( aCurPos, i18n::CharacterIteratorMode::SKIPCHARACTER ); } else if ( 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 ); } else if ( nDelMode == DeleteMode::RestOfWord ) { aDelEnd = EndOfWord( aCurPos ); if (aDelEnd.GetIndex() == aCurPos.GetIndex()) { const sal_Int32 nLen(aCurPos.GetNode()->Len()); // #i120020# when 0 == nLen, aDelStart needs to be adapted, not // aDelEnd. This would (and did) lead to a wrong order in the // ImpConnectParagraphs call later. if(nLen) { // end of para? if (aDelEnd.GetIndex() == nLen) { aDelEnd = WordLeft( aCurPos ); } else // there's still sth to delete on the right { aDelEnd = EndOfWord( WordRight( aCurPos ) ); // if there'n no next word... if (aDelEnd.GetIndex() == nLen ) { aDelEnd.SetIndex( nLen ); } } } else { aDelStart = WordLeft(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 ) ); // Decide now if to delete selection (RESTOFCONTENTS) bool bSpecialBackward = ( nMode == DEL_LEFT ) && ( nDelMode == DeleteMode::Simple ); if ( aStatus.IsAnyOutliner() ) bSpecialBackward = false; return ImpConnectParagraphs( aDelStart.GetNode(), aDelEnd.GetNode(), bSpecialBackward ); } EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) { if ( !rCurSel.HasRange() ) return rCurSel.Min(); EditSelection aCurSel(rCurSel); aCurSel.Adjust( aEditDoc ); 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... 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() ); OSL_ENSURE( nEndNode != EE_PARA_NOT_FOUND, "Start > End ?!" ); OSL_ENSURE( 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() ); OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(3)" ); pPortion->MarkSelectionInvalid( aStartPaM.GetIndex(), aStartPaM.GetNode()->Len() ); // The beginning of the EndNodes.... const sal_Int32 nChars = aEndPaM.GetIndex(); aEndPaM.SetIndex( 0 ); ImpRemoveChars( aEndPaM, nChars ); pPortion = FindParaPortion( aEndPaM.GetNode() ); OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(4)" ); pPortion->MarkSelectionInvalid( 0, aEndPaM.GetNode()->Len() ); // Join together.... aStartPaM = ImpConnectParagraphs( aStartPaM.GetNode(), aEndPaM.GetNode() ); } else { ImpRemoveChars( aStartPaM, aEndPaM.GetIndex() - aStartPaM.GetIndex() ); ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() ); OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(5)" ); pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() ); } UpdateSelections(); TextModified(); return aStartPaM; } void ImpEditEngine::ImpRemoveParagraph( sal_Int32 nPara ) { ContentNode* pNode = aEditDoc.GetObject( nPara ); ContentNode* pNextNode = aEditDoc.GetObject( nPara+1 ); OSL_ENSURE( pNode, "Blind Node in ImpRemoveParagraph" ); aDeletedNodes.push_back(o3tl::make_unique( pNode, nPara )); // The node is managed by the undo and possibly destroyed! aEditDoc.Release( nPara ); 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(new EditUndoDelContent(pEditEngine, pNode, nPara)); else { aEditDoc.RemoveItemsFromPool(*pNode); if ( pNode->GetStyleSheet() ) EndListening( *pNode->GetStyleSheet() ); delete pNode; } } EditPaM ImpEditEngine::AutoCorrect( const EditSelection& rCurSel, sal_Unicode c, bool bOverwrite, vcl::Window* 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 ); } EditSelection aSel( rCurSel ); SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect(); if ( pAutoCorrect ) { if ( aSel.HasRange() ) aSel = ImpDeleteSelection( rCurSel ); // #i78661 allow application to turn off capitalization of // start sentence explicitly. // (This is done by setting IsFirstWordCapitalization to sal_False.) bool bOldCapitalStartSentence = pAutoCorrect->IsAutoCorrFlag( CapitalStartSentence ); if (!IsFirstWordCapitalization()) { ESelection aESel( CreateESel(aSel) ); EditSelection aFirstWordSel; EditSelection aSecondWordSel; if (aESel.nEndPara == 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.nEndPara == 0 && aFirstWordSel.Max().GetIndex() <= aSel.Max().GetIndex() && aSel.Max().GetIndex() <= aSecondWordSel.Min().GetIndex(); if (bIsFirstWordInFirstPara) pAutoCorrect->SetAutoCorrFlag( CapitalStartSentence, IsFirstWordCapitalization() ); } ContentNode* pNode = aSel.Max().GetNode(); const sal_Int32 nIndex = aSel.Max().GetIndex(); EdtAutoCorrDoc aAuto(pEditEngine, 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, 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( 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 ?"); EditPaM aPaM( rCurSel.Min() ); bool bDoOverwrite = bOverwrite && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ); bool bUndoAction = ( rCurSel.HasRange() || bDoOverwrite ); if ( bUndoAction ) UndoActionStart( EDITUNDO_INSERT ); if ( rCurSel.HasRange() ) { aPaM = ImpDeleteSelection( rCurSel ); } else if ( bDoOverwrite ) { // 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! "); ImpDeleteSelection( aTmpSel ); } if ( aPaM.GetNode()->Len() < MAXCHARSINPARA ) { if (IsInputSequenceCheckingRequired( c, rCurSel )) { uno::Reference < i18n::XExtendedInputSequenceChecker > _xISC( ImplGetInputSequenceChecker() ); if (!pCTLOptions) pCTLOptions = new SvtCTLOptions; if (_xISC.is() || pCTLOptions) { const sal_Int32 nTmpPos = aPaM.GetIndex(); sal_Int16 nCheckMode = pCTLOptions->IsCTLSequenceCheckingRestricted() ? i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC; // the text that needs to be checked is only the one // before the current cursor position OUString aOldText( aPaM.GetNode()->Copy(0, nTmpPos) ); OUString aNewText( aOldText ); if (pCTLOptions->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; OUString aChgText( aNewText.copy( 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... } if ( IsUndoEnabled() && !IsInUndo() ) { EditUndoInsertChars* pNewUndo = new EditUndoInsertChars(pEditEngine, CreateEPaM(aPaM), OUString(c)); bool bTryMerge = !bDoOverwrite && ( c != ' ' ); InsertUndo( pNewUndo, bTryMerge ); } aEditDoc.InsertText( (const EditPaM&)aPaM, OUString(c) ); ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); OSL_ENSURE( pPortion, "Blind Portion in InsertText" ); pPortion->MarkInvalid( aPaM.GetIndex(), 1 ); aPaM.SetIndex( aPaM.GetIndex()+1 ); // does not do EditDoc-Method anymore } TextModified(); if ( bUndoAction ) UndoActionEnd( EDITUNDO_INSERT ); return aPaM; } EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUString& rStr) { UndoActionStart( EDITUNDO_INSERT ); EditPaM aPaM; if ( aCurSel.HasRange() ) aPaM = ImpDeleteSelection( aCurSel ); else aPaM = aCurSel.Max(); EditPaM aCurPaM( aPaM ); // for the Invalidate // 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( aCurPaM, i18n::WordType::DICTIONARY_WORD ); OUString aText(convertLineEnd(rStr, LINEEND_LF)); SfxVoidItem aTabItem( EE_FEATURE_TAB ); // Converts to linesep = \n // Token LINE_SEP query, // since the MAC-Compiler makes something else from \n ! sal_Int32 nStart = 0; while ( nStart < aText.getLength() ) { sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart ); if ( nEnd == -1 ) nEnd = aText.getLength(); // not dereference! // Start == End => empty line if ( nEnd > nStart ) { OUString aLine = aText.copy( nStart, nEnd-nStart ); sal_Int32 nChars = aPaM.GetNode()->Len() + aLine.getLength(); if ( nChars > MAXCHARSINPARA ) { sal_Int32 nMaxNewChars = MAXCHARSINPARA-aPaM.GetNode()->Len(); nEnd -= ( aLine.getLength() - nMaxNewChars ); // Then the characters end up in the next paragraph. aLine = aLine.copy( 0, nMaxNewChars ); // Delete the Rest... } if ( IsUndoEnabled() && !IsInUndo() ) InsertUndo(new EditUndoInsertChars(pEditEngine, CreateEPaM(aPaM), aLine)); // Tabs ? if ( aLine.indexOf( '\t' ) == -1 ) aPaM = aEditDoc.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 ( nEnd2 > nStart2 ) aPaM = aEditDoc.InsertText( aPaM, aLine.copy( nStart2, nEnd2-nStart2 ) ); if ( nEnd2 < aLine.getLength() ) { aPaM = aEditDoc.InsertFeature( aPaM, aTabItem ); } nStart2 = nEnd2+1; } } ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); OSL_ENSURE( pPortion, "Blind Portion in InsertText" ); pPortion->SetMustRepaint(true); 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 ); nStart = nEnd+1; } UndoActionEnd( EDITUNDO_INSERT ); TextModified(); return aPaM; } EditPaM ImpEditEngine::ImpFastInsertText( EditPaM aPaM, const OUString& rStr ) { OSL_ENSURE( rStr.indexOf( 0x0A ) == -1, "FastInsertText: Newline not allowed! "); OSL_ENSURE( rStr.indexOf( 0x0D ) == -1, "FastInsertText: Newline not allowed! "); OSL_ENSURE( rStr.indexOf( '\t' ) == -1, "FastInsertText: Newline not allowed! "); if ( ( aPaM.GetNode()->Len() + rStr.getLength() ) < MAXCHARSINPARA ) { if ( IsUndoEnabled() && !IsInUndo() ) InsertUndo(new EditUndoInsertChars(pEditEngine, CreateEPaM(aPaM), rStr)); aPaM = aEditDoc.InsertText( aPaM, rStr ); TextModified(); } else { aPaM = ImpInsertText( aPaM, rStr ); } return aPaM; } EditPaM ImpEditEngine::ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem) { EditPaM aPaM; if ( rCurSel.HasRange() ) aPaM = ImpDeleteSelection( rCurSel ); else aPaM = rCurSel.Max(); if ( aPaM.GetIndex() >= SAL_MAX_INT32-1 ) return aPaM; if ( IsUndoEnabled() && !IsInUndo() ) InsertUndo(new EditUndoInsertFeature(pEditEngine, CreateEPaM(aPaM), rItem)); aPaM = aEditDoc.InsertFeature( aPaM, rItem ); ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); OSL_ENSURE( pPortion, "Blind Portion in InsertFeature" ); pPortion->MarkInvalid( aPaM.GetIndex()-1, 1 ); TextModified(); return aPaM; } EditPaM ImpEditEngine::ImpInsertParaBreak( const EditSelection& rCurSel ) { EditPaM aPaM; if ( rCurSel.HasRange() ) aPaM = ImpDeleteSelection( rCurSel ); else aPaM = rCurSel.Max(); return ImpInsertParaBreak( aPaM ); } EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs ) { if ( aEditDoc.Count() >= EE_PARA_MAX_COUNT ) { SAL_WARN( "editeng", "ImpEditEngine::ImpInsertParaBreak - can't process more than " << EE_PARA_MAX_COUNT << " paragraphs!"); return rPaM; } if ( IsUndoEnabled() && !IsInUndo() ) InsertUndo(new EditUndoSplitPara(pEditEngine, aEditDoc.GetPos(rPaM.GetNode()), rPaM.GetIndex())); EditPaM aPaM( aEditDoc.InsertParaBreak( rPaM, bKeepEndingAttribs ) ); if ( GetStatus().DoOnlineSpelling() ) { sal_Int32 nEnd = rPaM.GetNode()->Len(); aPaM.GetNode()->CreateWrongList(); WrongList* pLWrongs = rPaM.GetNode()->GetWrongList(); WrongList* pRWrongs = aPaM.GetNode()->GetWrongList(); // take over misspelled words: for(WrongList::iterator i = pLWrongs->begin(); i < pLWrongs->end(); ++i) { // Correct only if really a word gets overlapped in the process of // Spell checking if (i->mnStart > (size_t)nEnd) { pRWrongs->push_back(*i); editeng::MisspellRange& rRWrong = pRWrongs->back(); rRWrong.mnStart = rRWrong.mnStart - nEnd; rRWrong.mnEnd = rRWrong.mnEnd - nEnd; } else if (i->mnStart < (size_t)nEnd && i->mnEnd > (size_t)nEnd) i->mnEnd = nEnd; } sal_Int32 nInv = nEnd ? nEnd-1 : nEnd; if ( nEnd ) pLWrongs->SetInvalidRange(nInv, nEnd); else pLWrongs->SetValid(); pRWrongs->SetValid(); pRWrongs->SetInvalidRange(0, 1); // Only test the first word } ParaPortion* pPortion = FindParaPortion( rPaM.GetNode() ); OSL_ENSURE( pPortion, "Blind Portion in ImpInsertParaBreak" ); pPortion->MarkInvalid( rPaM.GetIndex(), 0 ); // 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 ); ParaPortion* pNewPortion = new ParaPortion( aPaM.GetNode() ); GetParaPortions().Insert(nPos+1, pNewPortion); ParaAttribsChanged( pNewPortion->GetNode() ); if ( IsCallParaInsertedOrDeleted() ) GetEditEnginePtr()->ParagraphInserted( nPos+1 ); CursorMoved( rPaM.GetNode() ); // if empty Attributes have emerged. TextModified(); return aPaM; } EditPaM ImpEditEngine::ImpFastInsertParagraph( sal_Int32 nPara ) { if ( IsUndoEnabled() && !IsInUndo() ) { if ( nPara ) { OSL_ENSURE( aEditDoc.GetObject( nPara-1 ), "FastInsertParagraph: Prev does not exist" ); InsertUndo(new EditUndoSplitPara(pEditEngine, nPara-1, aEditDoc.GetObject( nPara-1 )->Len())); } else InsertUndo(new EditUndoSplitPara(pEditEngine, 0, 0)); } ContentNode* pNode = new ContentNode( aEditDoc.GetItemPool() ); // If flat mode, then later no Font is set: pNode->GetCharAttribs().GetDefFont() = aEditDoc.GetDefFont(); if ( GetStatus().DoOnlineSpelling() ) pNode->CreateWrongList(); aEditDoc.Insert(nPara, pNode); ParaPortion* pNewPortion = new ParaPortion( pNode ); GetParaPortions().Insert(nPara, pNewPortion); if ( IsCallParaInsertedOrDeleted() ) GetEditEnginePtr()->ParagraphInserted( nPara ); return EditPaM( pNode, 0 ); } EditPaM ImpEditEngine::InsertParaBreak(const EditSelection& rCurSel) { EditPaM aPaM(ImpInsertParaBreak(rCurSel)); if ( aStatus.DoAutoIndenting() ) { sal_Int32 nPara = aEditDoc.GetPos( aPaM.GetNode() ); OSL_ENSURE( nPara > 0, "AutoIndenting: Error!" ); OUString aPrevParaText( GetEditDoc().GetParaAsString( nPara-1 ) ); sal_Int32 n = 0; while ( ( n < aPrevParaText.getLength() ) && ( ( aPrevParaText[n] == ' ' ) || ( aPrevParaText[n] == '\t' ) ) ) { if ( aPrevParaText[n] == '\t' ) aPaM = ImpInsertFeature( aPaM, SfxVoidItem( EE_FEATURE_TAB ) ); else aPaM = ImpInsertText( aPaM, OUString(aPrevParaText[n]) ); n++; } } return aPaM; } EditPaM ImpEditEngine::InsertTab(const EditSelection& rCurSel) { EditPaM aPaM( ImpInsertFeature(rCurSel, SfxVoidItem(EE_FEATURE_TAB ))); return aPaM; } EditPaM ImpEditEngine::InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld) { return ImpInsertFeature(rCurSel, rFld); } bool ImpEditEngine::UpdateFields() { bool bChanges = false; sal_Int32 nParas = GetEditDoc().Count(); for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) { bool bChangesInPara = false; ContentNode* pNode = GetEditDoc().GetObject( nPara ); OSL_ENSURE( pNode, "NULL-Pointer in Doc" ); CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); for (std::unique_ptr & rAttrib : rAttribs) { EditCharAttrib& rAttr = *rAttrib.get(); if (rAttr.Which() == EE_FEATURE_FIELD) { EditCharAttribField& rField = static_cast(rAttr); std::unique_ptr pCurrent(new EditCharAttribField(rField)); rField.Reset(); if ( aStatus.MarkFields() ) rField.GetFieldColor() = new Color( GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor ); OUString aFldValue = GetEditEnginePtr()->CalcFieldValue( static_cast(*rField.GetItem()), nPara, rField.GetStart(), rField.GetTextColor(), rField.GetFieldColor()); rField.SetFieldValue(aFldValue); if (rField != *pCurrent) { bChanges = true; bChangesInPara = true; } } } if ( bChangesInPara ) { // If possible be more precise when invalidate. ParaPortion* pPortion = GetParaPortions()[nPara]; OSL_ENSURE( pPortion, "NULL-Pointer in Doc" ); pPortion->MarkSelectionInvalid( 0, pNode->Len() ); } } return bChanges; } EditPaM ImpEditEngine::InsertLineBreak(const EditSelection& aCurSel) { EditPaM aPaM( ImpInsertFeature( aCurSel, SfxVoidItem( EE_FEATURE_LINEBR ) ) ); return aPaM; } // Helper functions Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags ) { OSL_ENSURE( GetUpdateMode(), "Must not be reached when Update=FALSE: PaMtoEditCursor" ); Rectangle aEditCursor; long nY = 0; for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) { ParaPortion* pPortion = GetParaPortions()[nPortion]; ContentNode* pNode = pPortion->GetNode(); OSL_ENSURE( pNode, "Invalid Node in Portion!" ); if ( pNode != aPaM.GetNode() ) { nY += pPortion->GetHeight(); } else { aEditCursor = GetEditCursor( pPortion, aPaM.GetIndex(), nFlags ); aEditCursor.Top() += nY; aEditCursor.Bottom() += nY; return aEditCursor; } } OSL_FAIL( "Portion not found!" ); return aEditCursor; } EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart ) { OSL_ENSURE( GetUpdateMode(), "Must not be reached when Update=FALSE: GetPaM" ); long nY = 0; EditPaM aPaM; sal_Int32 nPortion; for ( nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) { ParaPortion* pPortion = GetParaPortions()[nPortion]; const long nTmpHeight = pPortion->GetHeight(); // should also be correct for !bVisible! nY += nTmpHeight; if ( nY > aDocPos.Y() ) { nY -= nTmpHeight; aDocPos.Y() -= nY; // Skip invisible Portions: while ( pPortion && !pPortion->IsVisible() ) { nPortion++; pPortion = GetParaPortions().SafeGetObject( nPortion ); } SAL_WARN_IF(!pPortion, "editeng", "worrying lack of any visible paragraph"); if (!pPortion) return aPaM; return GetPaM(pPortion, aDocPos, bSmart); } } // 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; } sal_uInt32 ImpEditEngine::GetTextHeight() const { OSL_ENSURE( GetUpdateMode(), "Should not be used for Update=FALSE: GetTextHeight" ); OSL_ENSURE( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); return nCurTextHeight; } 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(); long nMaxWidth = 0; long nCurWidth = 0; // Over all the paragraphs ... sal_Int32 nParas = GetParaPortions().Count(); for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) { ParaPortion* pPortion = GetParaPortions()[nPara]; if ( pPortion->IsVisible() ) { const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pPortion->GetNode() ); sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pPortion->GetNode() ); // On the lines of the paragraph ... sal_Int32 nLines = (sal_Int32)pPortion->GetLines().Count(); for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ ) { EditLine& 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! nCurWidth = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth ); if ( nLine == 0 ) { long nFI = GetXValue( rLRItem.GetTextFirstLineOfst() ); nCurWidth -= nFI; if ( pPortion->GetBulletX() > nCurWidth ) { nCurWidth += nFI; // LI? if ( pPortion->GetBulletX() > nCurWidth ) nCurWidth = pPortion->GetBulletX(); } } nCurWidth += GetXValue( rLRItem.GetRight() ); nCurWidth += CalcLineWidth( pPortion, &rLine, bIgnoreExtraSpace ); if ( nCurWidth > nMaxWidth ) { nMaxWidth = nCurWidth; } } } } nMaxWidth++; // widen it, because in CreateLines for >= is wrapped. return (sal_uInt32)nMaxWidth; } sal_uInt32 ImpEditEngine::CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace ) { sal_Int32 nPara = GetEditDoc().GetPos( pPortion->GetNode() ); // #114278# Saving both layout mode and language (since I'm // potentially changing both) GetRefDevice()->Push( PushFlags::TEXTLAYOUTMODE|PushFlags::TEXTLANGUAGE ); 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++ ) { const TextPortion& rTextPortion = pPortion->GetTextPortions()[nTP]; switch ( rTextPortion.GetKind() ) { case PortionKind::FIELD: case PortionKind::HYPHENATOR: case PortionKind::TAB: { nWidth += rTextPortion.GetSize().Width(); } break; case PortionKind::TEXT: { if ( ( eJustification != SVX_ADJUST_BLOCK ) || ( !bIgnoreExtraSpace ) ) { nWidth += rTextPortion.GetSize().Width(); } 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(); } } break; case PortionKind::LINEBREAK: break; } nPos = nPos + rTextPortion.GetLen(); } GetRefDevice()->Pop(); return nWidth; } sal_uInt32 ImpEditEngine::GetTextHeightNTP() const { DBG_ASSERT( GetUpdateMode(), "Should not be used for Update=FALSE: GetTextHeight" ); DBG_ASSERT( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); return nCurTextHeightNTP; } sal_uInt32 ImpEditEngine::CalcTextHeight( sal_uInt32* 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; } } if ( pHeightNTP ) *pHeightNTP = nY - nEmptyHeight; return nY; } 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( pPPortion, "Paragraph not found: GetLineCount" ); if ( pPPortion ) return pPPortion->GetLines().Count(); return -1; } 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" ); 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 ) const { OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "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 ) const { sal_Int32 nLineNo = -1; const ContentNode* pNode = GetEditDoc().GetObject( nPara ); OSL_ENSURE( pNode, "GetLineNumberAtIndex: invalid paragraph index" ); if (pNode) { // we explicitly allow for the index to point at the character right behind the text const bool bValidIndex = /*0 <= nIndex &&*/ nIndex <= pNode->Len(); OSL_ENSURE( bValidIndex, "GetLineNumberAtIndex: invalid index" ); const sal_Int32 nLineCount = GetLineCount( nPara ); if (nIndex == pNode->Len()) nLineNo = nLineCount > 0 ? nLineCount - 1 : 0; else if (bValidIndex) // nIndex < pNode->Len() { sal_Int32 nStart = -1, nEnd = -1; for (sal_Int32 i = 0; i < nLineCount && nLineNo == -1; ++i) { GetLineBoundaries( nStart, nEnd, nPara, i ); if (nStart >= 0 && nStart <= nIndex && nEnd >= 0 && nIndex < nEnd) nLineNo = i; } } } return nLineNo; } sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine ) { OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "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; } sal_uInt32 ImpEditEngine::GetParaHeight( sal_Int32 nParagraph ) { sal_uInt32 nHeight = 0; ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); OSL_ENSURE( pPPortion, "Paragraph not found: GetParaHeight" ); if ( pPPortion ) nHeight = pPPortion->GetHeight(); return nHeight; } 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) { EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); bool bChanged = false; for (std::unique_ptr & aDeletedNode : aDeletedNodes) { const DeletedNodeInfo& rInf = *aDeletedNode.get(); if ( ( aCurSel.Min().GetNode() == rInf.GetNode() ) || ( aCurSel.Max().GetNode() == rInf.GetNode() ) ) { // Use ParaPortions, as now also hidden paragraphs have to be // taken into account! sal_Int32 nPara = rInf.GetPosition(); if (!GetParaPortions().SafeGetObject(nPara)) // Last paragraph { nPara = GetParaPortions().Count()-1; } assert(GetParaPortions()[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() ) nPara++; if ( nPara > nLastPara ) // then also backwards ... { nPara = nCurPara; while ( nPara && !GetParaPortions()[nPara]->IsVisible() ) nPara--; } OSL_ENSURE( GetParaPortions()[nPara]->IsVisible(), "No visible paragraph found: UpdateSelections" ); ParaPortion* pParaPortion = GetParaPortions()[nPara]; EditSelection aTmpSelection( EditPaM( pParaPortion->GetNode(), 0 ) ); pView->pImpEditView->SetEditSelection( aTmpSelection ); bChanged=true; break; // for loop } } if ( !bChanged ) { // Check Index if node shrunk. if ( aCurSel.Min().GetIndex() > aCurSel.Min().GetNode()->Len() ) { aCurSel.Min().SetIndex( aCurSel.Min().GetNode()->Len() ); pView->pImpEditView->SetEditSelection( aCurSel ); } if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() ) { aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() ); pView->pImpEditView->SetEditSelection( aCurSel ); } } } aDeletedNodes.clear(); } EditSelection ImpEditEngine::ConvertSelection( sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos ) { EditSelection aNewSelection; // Start... ContentNode* pNode = aEditDoc.GetObject( nStartPara ); sal_Int32 nIndex = nStartPos; if ( !pNode ) { pNode = aEditDoc[ aEditDoc.Count()-1 ]; nIndex = pNode->Len(); } else if ( nIndex > pNode->Len() ) nIndex = pNode->Len(); aNewSelection.Min().SetNode( pNode ); aNewSelection.Min().SetIndex( nIndex ); // End... pNode = aEditDoc.GetObject( nEndPara ); nIndex = nEndPos; if ( !pNode ) { pNode = aEditDoc[ aEditDoc.Count()-1 ]; nIndex = pNode->Len(); } else if ( nIndex > pNode->Len() ) nIndex = pNode->Len(); aNewSelection.Max().SetNode( pNode ); aNewSelection.Max().SetIndex( nIndex ); return aNewSelection; } void ImpEditEngine::SetActiveView( EditView* pView ) { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Actually, now bHasVisSel and HideSelection would be necessary !!! if ( pView == pActiveView ) return; if ( pActiveView && pActiveView->HasSelection() ) pActiveView->pImpEditView->DrawSelection(); pActiveView = pView; if ( pActiveView && pActiveView->HasSelection() ) pActiveView->pImpEditView->DrawSelection(); // 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 ) { delete mpIMEInfos; mpIMEInfos = nullptr; } } uno::Reference< datatransfer::XTransferable > ImpEditEngine::CreateTransferable( const EditSelection& rSelection ) { EditSelection aSelection( rSelection ); aSelection.Adjust( GetEditDoc() ); EditDataObject* pDataObj = new EditDataObject; uno::Reference< datatransfer::XTransferable > xDataObj; xDataObj = pDataObj; OUString aText(convertLineEnd(GetSelected(aSelection), GetSystemLineEnd())); // System specific pDataObj->GetString() = aText; SvxFontItem::EnableStoreUnicodeNames( true ); WriteBin( pDataObj->GetStream(), aSelection, true ); pDataObj->GetStream().Seek( 0 ); SvxFontItem::EnableStoreUnicodeNames( false ); WriteRTF( pDataObj->GetRTFStream(), aSelection ); pDataObj->GetRTFStream().Seek( 0 ); if ( ( aSelection.Min().GetNode() == aSelection.Max().GetNode() ) && ( aSelection.Max().GetIndex() == (aSelection.Min().GetIndex()+1) ) ) { const EditCharAttrib* pAttr = aSelection.Min().GetNode()->GetCharAttribs(). FindFeature( aSelection.Min().GetIndex() ); if ( pAttr && ( pAttr->GetStart() == aSelection.Min().GetIndex() ) && ( pAttr->Which() == EE_FEATURE_FIELD ) ) { const SvxFieldItem* pField = static_cast(pAttr->GetItem()); const SvxFieldData* pFld = pField->GetField(); if ( dynamic_cast(pFld) != nullptr ) { // Office-Bookmark OUString aURL( static_cast(pFld)->GetURL() ); pDataObj->GetURL() = aURL; } } } return xDataObj; } EditSelection ImpEditEngine::InsertText( uno::Reference< datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial ) { EditSelection aNewSelection( rPaM ); if ( !rxDataObj.is() ) return aNewSelection; datatransfer::DataFlavor aFlavor; bool bDone = false; if ( bUseSpecial ) { // BIN SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE, aFlavor ); if ( rxDataObj->isDataFlavorSupported( aFlavor ) ) { try { uno::Any aData = rxDataObj->getTransferData( aFlavor ); uno::Sequence< sal_Int8 > aSeq; aData >>= aSeq; { SvMemoryStream aBinStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ ); aNewSelection = Read( aBinStream, rBaseURL, EE_FORMAT_BIN, rPaM ); } bDone = true; } catch( const css::uno::Exception& ) { } } if ( !bDone ) { // RTF SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aFlavor ); // RICHTEXT datatransfer::DataFlavor aFlavorRichtext; SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aFlavorRichtext ); bool bRtfSupported = rxDataObj->isDataFlavorSupported( aFlavor ); bool bRichtextSupported = rxDataObj->isDataFlavorSupported( aFlavorRichtext ); if ( bRtfSupported || bRichtextSupported ) { if(bRichtextSupported) { aFlavor = aFlavorRichtext; } try { uno::Any aData = rxDataObj->getTransferData( aFlavor ); uno::Sequence< sal_Int8 > aSeq; aData >>= aSeq; { SvMemoryStream aRTFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ ); aNewSelection = Read( aRTFStream, rBaseURL, EE_FORMAT_RTF, rPaM ); } bDone = true; } catch( const css::uno::Exception& ) { } } } if ( !bDone ) { // XML ? // Currently, there is nothing like "The" XML format, StarOffice doesn't offer plain XML in Clipboard... } } if ( !bDone ) { SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); if ( rxDataObj->isDataFlavorSupported( aFlavor ) ) { try { uno::Any aData = rxDataObj->getTransferData( aFlavor ); OUString aText; aData >>= aText; aNewSelection = ImpInsertText( rPaM, aText ); } catch( ... ) { ; // #i9286# can happen, even if isDataFlavorSupported returns true... } } } return aNewSelection; } Range ImpEditEngine::GetInvalidYOffsets( ParaPortion* pPortion ) { Range aRange( 0, 0 ); if ( pPortion->IsVisible() ) { const SvxULSpaceItem& rULSpace = static_cast(pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE )); const SvxLineSpacingItem& rLSItem = static_cast(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(); } if( ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) && rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() < 100 ) ) { const EditLine& rL = pPortion->GetLines()[nFirstInvalid]; long n = rL.GetTxtHeight() * ( 100L - rLSItem.GetPropLineSpace() ); 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 = static_cast(pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL )); sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; 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 = static_cast(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, long nXPos, bool bSmart) { OSL_ENSURE( pLine, "No line received: GetChar" ); sal_Int32 nChar = -1; sal_Int32 nCurIndex = pLine->GetStart(); // Search best matching portion with GetPortionXOffset() for ( sal_Int32 i = pLine->GetStartPortion(); i <= pLine->GetEndPortion(); i++ ) { const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; long nXLeft = GetPortionXOffset( pParaPortion, pLine, i ); long nXRight = nXLeft + rPortion.GetSize().Width(); if ( ( nXLeft <= nXPos ) && ( nXRight >= nXPos ) ) { nChar = nCurIndex; // Search within Portion... // Don't search within special portions... if ( rPortion.GetKind() != PortionKind::TEXT ) { // ...but check on which side if ( bSmart ) { long nLeftDiff = nXPos-nXLeft; long nRightDiff = nXRight-nXPos; if ( nRightDiff < nLeftDiff ) nChar++; } } else { sal_Int32 nMax = rPortion.GetLen(); sal_Int32 nOffset = -1; sal_Int32 nTmpCurIndex = nChar - pLine->GetStart(); long nXInPortion = nXPos - nXLeft; if ( rPortion.IsRightToLeft() ) nXInPortion = nXRight - nXPos; // Search in Array... for ( sal_Int32 x = 0; x < nMax; x++ ) { long nTmpPosMax = pLine->GetCharPosArray()[nTmpCurIndex+x]; if ( nTmpPosMax > nXInPortion ) { // Check whether this or the previous... long nTmpPosMin = x ? pLine->GetCharPosArray()[nTmpCurIndex+x-1] : 0; long nDiffLeft = nXInPortion - nTmpPosMin; 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 ) { const long nX = pLine->GetCharPosArray()[nOffset]; while ( ( (nOffset+1) < nMax ) && ( pLine->GetCharPosArray()[nOffset+1] == nX ) ) nOffset++; } 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; OSL_ENSURE( nOffset <= nMax, "nOffset > nMax" ); nChar = nChar + nOffset; // Check if index is within a cell: if ( nChar && ( nChar < pParaPortion->GetNode()->Len() ) ) { EditPaM aPaM( pParaPortion->GetNode(), nChar+1 ); sal_uInt16 nScriptType = GetI18NScriptType( aPaM ); if ( nScriptType == i18n::ScriptType::COMPLEX ) { uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); 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 ); sal_Int32 nLeft = _xBI->previousCharacters( pParaPortion->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; } } } } } nCurIndex = nCurIndex + rPortion.GetLen(); } if ( nChar == -1 ) { nChar = ( nXPos <= pLine->GetStartPosX() ) ? pLine->GetStart() : pLine->GetEnd(); } return nChar; } Range ImpEditEngine::GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const { Range aLineXPosStartEnd; sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); if ( !IsRightToLeft( nPara ) ) { aLineXPosStartEnd.Min() = pLine->GetStartPosX(); aLineXPosStartEnd.Max() = pLine->GetStartPosX() + pLine->GetTextWidth(); } else { aLineXPosStartEnd.Min() = GetPaperSize().Width() - ( pLine->GetStartPosX() + pLine->GetTextWidth() ); aLineXPosStartEnd.Max() = GetPaperSize().Width() - pLine->GetStartPosX(); } return aLineXPosStartEnd; } long ImpEditEngine::GetPortionXOffset( const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const { long nX = pLine->GetStartPosX(); for ( sal_Int32 i = pLine->GetStartPortion(); i < nTextPortion; i++ ) { const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; switch ( rPortion.GetKind() ) { case PortionKind::FIELD: case PortionKind::TEXT: case PortionKind::HYPHENATOR: case PortionKind::TAB: { nX += rPortion.GetSize().Width(); } break; case PortionKind::LINEBREAK: break; } } sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); bool bR2LPara = IsRightToLeft( nPara ); const TextPortion& rDestPortion = pParaPortion->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() ) { const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; if ( rNextTextPortion.GetRightToLeftLevel() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) nX += rNextTextPortion.GetSize().Width(); else break; nTmpPortion++; } // Portions before must be removed, visual behind this portion nTmpPortion = nTextPortion; while ( nTmpPortion > pLine->GetStartPortion() ) { --nTmpPortion; const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; if ( rPrevTextPortion.GetRightToLeftLevel() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) nX -= rPrevTextPortion.GetSize().Width(); else break; } } else if ( bR2LPara && !rDestPortion.IsRightToLeft() ) { // Portions behind must be removed, visual behind this portion sal_Int32 nTmpPortion = nTextPortion+1; while ( nTmpPortion <= pLine->GetEndPortion() ) { const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) nX += rNextTextPortion.GetSize().Width(); else break; nTmpPortion++; } // Portions before must be added, visual before this portion nTmpPortion = nTextPortion; while ( nTmpPortion > pLine->GetStartPortion() ) { --nTmpPortion; const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) nX -= rPrevTextPortion.GetSize().Width(); else break; } } } if ( bR2LPara ) { // Switch X positions... OSL_ENSURE( GetTextRanger() || GetPaperSize().Width(), "GetPortionXOffset - paper size?!" ); OSL_ENSURE( GetTextRanger() || (nX <= GetPaperSize().Width()), "GetPortionXOffset - position out of paper size!" ); nX = GetPaperSize().Width() - nX; nX -= rDestPortion.GetSize().Width(); } return nX; } long ImpEditEngine::GetXPos( const ParaPortion* pParaPortion, const EditLine* pLine, 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!" ); bool bDoPreferPortionStart = bPreferPortionStart; // Assure that the portion belongs to this line: if ( nIndex == pLine->GetStart() ) bDoPreferPortionStart = true; else if ( nIndex == pLine->GetEnd() ) bDoPreferPortionStart = false; sal_Int32 nTextPortionStart = 0; sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart ); OSL_ENSURE( ( nTextPortion >= pLine->GetStartPortion() ) && ( nTextPortion <= pLine->GetEndPortion() ), "GetXPos: Portion not in current line! " ); const TextPortion& rPortion = pParaPortion->GetTextPortions()[nTextPortion]; long nX = GetPortionXOffset( pParaPortion, pLine, 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()... long nPortionTextWidth = rPortion.GetSize().Width(); if ( ( rPortion.GetKind() == PortionKind::TEXT ) && rPortion.GetLen() && !GetTextRanger() ) nPortionTextWidth = pLine->GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - pLine->GetStart()]; if ( nTextPortionStart != nIndex ) { // Search within portion... if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) ) { // End of Portion if ( rPortion.GetKind() == PortionKind::TAB ) { if ( nTextPortion+1 < pParaPortion->GetTextPortions().Count() ) { const TextPortion& rNextPortion = pParaPortion->GetTextPortions()[nTextPortion+1]; if ( rNextPortion.GetKind() != PortionKind::TAB ) { if ( !bPreferPortionStart ) nX = GetXPos( pParaPortion, pLine, nIndex, true ); else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) nX += nPortionTextWidth; } } else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) { nX += nPortionTextWidth; } } else if ( !rPortion.IsRightToLeft() ) { nX += nPortionTextWidth; } } else if ( rPortion.GetKind() == PortionKind::TEXT ) { OSL_ENSURE( nIndex != pLine->GetStart(), "Strange behavior in new GetXPos()" ); OSL_ENSURE( pLine && pLine->GetCharPosArray().size(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" ); if( pLine->GetCharPosArray().size() ) { sal_Int32 nPos = nIndex - 1 - pLine->GetStart(); if (nPos < 0 || nPos >= (sal_Int32)pLine->GetCharPosArray().size()) { nPos = pLine->GetCharPosArray().size()-1; OSL_FAIL("svx::ImpEditEngine::GetXPos(), index out of range!"); } // old code restored see #i112788 (which leaves #i74188 unfixed again) long nPosInPortion = pLine->GetCharPosArray()[nPos]; if ( !rPortion.IsRightToLeft() ) { nX += nPosInPortion; } else { nX += nPortionTextWidth - nPosInPortion; } if ( rPortion.GetExtraInfos() && rPortion.GetExtraInfos()->bCompressed ) { nX += rPortion.GetExtraInfos()->nPortionOffsetX; if ( rPortion.GetExtraInfos()->nAsianCompressionTypes & AsianCompressionFlags::PunctuationRight ) { AsianCompressionFlags nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex ) ); if ( nType == AsianCompressionFlags::PunctuationRight && !pLine->GetCharPosArray().empty() ) { sal_Int32 n = nIndex - nTextPortionStart; const long* pDXArray = &pLine->GetCharPosArray()[0]+( nTextPortionStart-pLine->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 ) ); if ( nType == AsianCompressionFlags::PunctuationRight ) { sal_Int32 nNextCharWidth = ( ( (n+2) < rPortion.GetLen() ) ? pDXArray[n+1] : rPortion.GetSize().Width() ) - pDXArray[n]; sal_Int32 nCompressed = nNextCharWidth/2; nCompressed *= rPortion.GetExtraInfos()->nMaxCompression100thPercent; nCompressed /= 10000; nCharWidth += nCompressed; } } else { nCharWidth *= 2; // last char pos to portion end is only compressed size } nX += nCharWidth/2; // 50% compression } } } } } } else // if ( nIndex == pLine->GetStart() ) { if ( rPortion.IsRightToLeft() ) { nX += nPortionTextWidth; } } return nX; } void ImpEditEngine::CalcHeight( ParaPortion* pPortion ) { pPortion->nHeight = 0; pPortion->nFirstLineOffset = 0; if ( pPortion->IsVisible() ) { 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(); if ( !aStatus.IsOutliner() ) { const SvxULSpaceItem& rULItem = static_cast(pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE )); const SvxLineSpacingItem& rLSItem = static_cast(pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL )); sal_Int32 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; if ( nSBL ) { if ( pPortion->GetLines().Count() > 1 ) pPortion->nHeight += ( pPortion->GetLines().Count() - 1 ) * nSBL; if ( aStatus.ULSpaceSummation() ) pPortion->nHeight += nSBL; } sal_Int32 nPortion = GetParaPortions().GetPos( pPortion ); if ( nPortion || aStatus.ULSpaceFirstParagraph() ) { sal_uInt16 nUpper = GetYValue( rULItem.GetUpper() ); pPortion->nHeight += nUpper; pPortion->nFirstLineOffset = nUpper; } if ( ( nPortion != (GetParaPortions().Count()-1) ) ) { pPortion->nHeight += GetYValue( rULItem.GetLower() ); // not in the last } if ( nPortion && !aStatus.ULSpaceSummation() ) { ParaPortion* pPrev = GetParaPortions().SafeGetObject( nPortion-1 ); const SvxULSpaceItem& rPrevULItem = static_cast(pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE )); const SvxLineSpacingItem& rPrevLSItem = static_cast(pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL )); // 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 = GetYValue( lcl_CalcExtraSpace( pPortion, rLSItem ) ); if ( nExtraSpace > pPortion->nFirstLineOffset ) { // Paragraph becomes 'bigger': pPortion->nHeight += ( nExtraSpace - pPortion->nFirstLineOffset ); pPortion->nFirstLineOffset = nExtraSpace; } // Determine nFirstLineOffset now f(pNode) => now f(pNode, pPrev): sal_uInt16 nPrevLower = GetYValue( rPrevULItem.GetLower() ); // This PrevLower is still in the height of PrevPortion ... if ( nPrevLower > pPortion->nFirstLineOffset ) { // Paragraph is 'small': pPortion->nHeight -= pPortion->nFirstLineOffset; pPortion->nFirstLineOffset = 0; } else if ( nPrevLower ) { // Paragraph becomes 'somewhat smaller': pPortion->nHeight -= nPrevLower; pPortion->nFirstLineOffset = pPortion->nFirstLineOffset - 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() ) { nExtraSpace = GetYValue( lcl_CalcExtraSpace( pPrev, rPrevLSItem ) ); if ( nExtraSpace > nPrevLower ) { sal_uInt16 nMoreLower = nExtraSpace - nPrevLower; // Paragraph becomes 'bigger', 'grows' downwards: if ( nMoreLower > pPortion->nFirstLineOffset ) { pPortion->nHeight += ( nMoreLower - pPortion->nFirstLineOffset ); pPortion->nFirstLineOffset = nMoreLower; } } } } } } } 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.... */ long nY = pPortion->GetFirstLineOffset(); const SvxLineSpacingItem& rLSItem = static_cast(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 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; } Rectangle aEditCursor; aEditCursor.Top() = nY; nY += pLine->GetHeight(); aEditCursor.Bottom() = nY-1; // Search within the line... 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.Left() = aEditCursor.Right() = nX; if ( nFlags & GetCursorFlags::TextOnly ) aEditCursor.Top() = aEditCursor.Bottom() - pLine->GetTxtHeight() + 1; else aEditCursor.Top() = aEditCursor.Bottom() - std::min( pLine->GetTxtHeight(), pLine->GetHeight() ) + 1; return aEditCursor; } void ImpEditEngine::SetValidPaperSize( const Size& rNewSz ) { aPaperSize = rNewSz; long nMinWidth = aStatus.AutoPageWidth() ? aMinAutoPaperSize.Width() : 0; long nMaxWidth = aStatus.AutoPageWidth() ? aMaxAutoPaperSize.Width() : 0x7FFFFFFF; long nMinHeight = aStatus.AutoPageHeight() ? aMinAutoPaperSize.Height() : 0; long nMaxHeight = aStatus.AutoPageHeight() ? aMaxAutoPaperSize.Height() : 0x7FFFFFFF; // Minimum/Maximum width: if ( aPaperSize.Width() < nMinWidth ) aPaperSize.Width() = nMinWidth; else if ( aPaperSize.Width() > nMaxWidth ) aPaperSize.Width() = nMaxWidth; // Minimum/Maximum height: if ( aPaperSize.Height() < nMinHeight ) aPaperSize.Height() = nMinHeight; else if ( aPaperSize.Height() > nMaxHeight ) aPaperSize.Height() = nMaxHeight; } void ImpEditEngine::IndentBlock( EditView* pEditView, bool bRight ) { ESelection aESel( CreateESel( pEditView->pImpEditView->GetEditSelection() ) ); aESel.Adjust(); // Only if more selected Paragraphs ... if ( aESel.nEndPara > aESel.nStartPara ) { ESelection aNewSel = aESel; aNewSel.nStartPos = 0; aNewSel.nEndPos = EE_TEXTPOS_ALL; if ( aESel.nEndPos == 0 ) { aESel.nEndPara--; // then not this paragraph ... aNewSel.nEndPos = 0; } pEditView->pImpEditView->DrawSelection(); pEditView->pImpEditView->SetEditSelection( pEditView->pImpEditView->GetEditSelection().Max() ); UndoActionStart( bRight ? EDITUNDO_INDENTBLOCK : EDITUNDO_UNINDENTBLOCK ); for ( sal_Int32 nPara = aESel.nStartPara; nPara <= aESel.nEndPara; nPara++ ) { ContentNode* pNode = GetEditDoc().GetObject( nPara ); if ( bRight ) { // Insert Tabs EditPaM aPaM( pNode, 0 ); InsertTab( aPaM ); } else { // Remove Tabs const EditCharAttrib* pFeature = pNode->GetCharAttribs().FindFeature( 0 ); if ( pFeature && ( pFeature->GetStart() == 0 ) && ( pFeature->GetItem()->Which() == EE_FEATURE_TAB ) ) { EditPaM aStartPaM( pNode, 0 ); EditPaM aEndPaM( pNode, 1 ); ImpDeleteSelection( EditSelection( aStartPaM, aEndPaM ) ); } } } UndoActionEnd( bRight ? EDITUNDO_INDENTBLOCK : EDITUNDO_UNINDENTBLOCK ); UpdateSelections(); FormatAndUpdate( pEditView ); ContentNode* pLastNode = GetEditDoc().GetObject( aNewSel.nEndPara ); if ( pLastNode->Len() < aNewSel.nEndPos ) aNewSel.nEndPos = pLastNode->Len(); pEditView->pImpEditView->SetEditSelection( CreateSel( aNewSel ) ); pEditView->pImpEditView->DrawSelection(); pEditView->pImpEditView->ShowCursor( false, true ); } } rtl::Reference ImpEditEngine::GetForbiddenCharsTable() const { rtl::Reference xF = xForbiddenCharsTable; if ( !xF.is() ) xF = EE_DLL().GetGlobalData()->GetForbiddenCharsTable(); return xF; } void ImpEditEngine::SetForbiddenCharsTable( const rtl::Reference& xForbiddenChars ) { EE_DLL().GetGlobalData()->SetForbiddenCharsTable( xForbiddenChars ); } svtools::ColorConfig& ImpEditEngine::GetColorConfig() { if ( !pColorConfig ) pColorConfig = new svtools::ColorConfig; return *pColorConfig; } bool ImpEditEngine::IsVisualCursorTravelingEnabled() { bool bVisualCursorTravaling = false; if( !pCTLOptions ) pCTLOptions = new SvtCTLOptions; if ( pCTLOptions->IsCTLFontEnabled() && ( pCTLOptions->GetCTLCursorMovement() == SvtCTLOptions::MOVEMENT_VISUAL ) ) { bVisualCursorTravaling = true; } return bVisualCursorTravaling; } bool ImpEditEngine::DoVisualCursorTraveling( const ContentNode* ) { // Don't check if it's necessary, because we also need it when leaving the paragraph return IsVisualCursorTravelingEnabled(); } void ImpEditEngine::CallNotify( EENotify& rNotify ) { if ( !nBlockNotifications ) GetNotifyHdl().Call( rNotify ); else aNotifyCache.push_back(rNotify); } void ImpEditEngine::EnterBlockNotifications() { if( !nBlockNotifications ) { // #109864# Send out START notification immediately, to allow // external, non-queued events to be captured as well from // client side EENotify aNotify( EE_NOTIFY_BLOCKNOTIFICATION_START ); aNotify.pEditEngine = GetEditEnginePtr(); GetNotifyHdl().Call( aNotify ); } nBlockNotifications++; } void ImpEditEngine::LeaveBlockNotifications() { OSL_ENSURE( nBlockNotifications, "LeaveBlockNotifications - Why?" ); nBlockNotifications--; if ( !nBlockNotifications ) { // Call blocked notify events... while(!aNotifyCache.empty()) { EENotify aNotify(aNotifyCache[0]); // Remove from list before calling, maybe we enter LeaveBlockNotifications while calling the handler... aNotifyCache.erase(aNotifyCache.begin()); GetNotifyHdl().Call( aNotify ); } EENotify aNotify( EE_NOTIFY_BLOCKNOTIFICATION_END ); aNotify.pEditEngine = GetEditEnginePtr(); GetNotifyHdl().Call( aNotify ); } } IMPL_LINK_NOARG(ImpEditEngine, DocModified, LinkParamNone*, void) { aModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */