/* -*- 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 <memory>
#include <i18nutil/searchopt.hxx>
#include <o3tl/string_view.hxx>
#include <utility>
#include <vcl/dndlistenercontainer.hxx>
#include <vcl/textview.hxx>
#include <vcl/texteng.hxx>
#include <vcl/settings.hxx>
#include "textdoc.hxx"
#include <vcl/textdata.hxx>
#include <vcl/transfer.hxx>
#include <vcl/xtextedt.hxx>
#include "textdat2.hxx"
#include <vcl/commandevent.hxx>
#include <vcl/inputctx.hxx>

#include <svl/undo.hxx>
#include <vcl/weld.hxx>
#include <vcl/window.hxx>
#include <vcl/svapp.hxx>
#include <tools/stream.hxx>

#include <sal/log.hxx>
#include <sot/formats.hxx>

#include <cppuhelper/weak.hxx>
#include <cppuhelper/queryinterface.hxx>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
#include <com/sun/star/datatransfer/XTransferable.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
#include <com/sun/star/util/SearchFlags.hpp>

#include <vcl/toolkit/edit.hxx>

#include <sot/exchange.hxx>

#include "TextLine.hxx"

#include <algorithm>
#include <cstddef>

TETextDataObject::TETextDataObject( OUString aText ) : maText(std::move( aText ))
{
}

// css::uno::XInterface
css::uno::Any TETextDataObject::queryInterface( const css::uno::Type & rType )
{
    css::uno::Any aRet = ::cppu::queryInterface( rType, static_cast< css::datatransfer::XTransferable* >(this) );
    return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ));
}

// css::datatransfer::XTransferable
css::uno::Any TETextDataObject::getTransferData( const css::datatransfer::DataFlavor& rFlavor )
{
    css::uno::Any aAny;

    SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
    if ( nT == SotClipboardFormatId::STRING )
    {
        aAny <<= maText;
    }
    else if ( nT == SotClipboardFormatId::HTML )
    {
        sal_uInt64 nLen = GetHTMLStream().TellEnd();
        GetHTMLStream().Seek(0);

        css::uno::Sequence< sal_Int8 > aSeq( nLen );
        memcpy( aSeq.getArray(), GetHTMLStream().GetData(), nLen );
        aAny <<= aSeq;
    }
    else
    {
        throw css::datatransfer::UnsupportedFlavorException();
    }
    return aAny;
}

css::uno::Sequence< css::datatransfer::DataFlavor > TETextDataObject::getTransferDataFlavors(  )
{
    GetHTMLStream().Seek( STREAM_SEEK_TO_END );
    bool bHTML = GetHTMLStream().Tell() > 0;
    css::uno::Sequence< css::datatransfer::DataFlavor > aDataFlavors( bHTML ? 2 : 1 );
    SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[0] );
    if ( bHTML )
        SotExchange::GetFormatDataFlavor( SotClipboardFormatId::HTML, aDataFlavors.getArray()[1] );
    return aDataFlavors;
}

sal_Bool TETextDataObject::isDataFlavorSupported( const css::datatransfer::DataFlavor& rFlavor )
{
    SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
    return ( nT == SotClipboardFormatId::STRING );
}

TextView::TextView(ExtTextEngine* pEng, vcl::Window* pWindow)
{
    pWindow->EnableRTL( false );

    mpWindow = pWindow;
    mpTextEngine = pEng;

    mbPaintSelection = true;
    mbAutoScroll = true;
    mbInsertMode = true;
    mbReadOnly = false;
    mbAutoIndent = false;
    mbCursorEnabled = true;
    mbClickedInSelection = false;
    mbCursorAtEndOfLine = false;
//  mbInSelection = false;

    mnTravelXPos = TRAVEL_X_DONTKNOW;

    mpSelFuncSet = std::make_unique<TextSelFunctionSet>(this);
    mpSelEngine = std::make_unique<SelectionEngine>(mpWindow, mpSelFuncSet.get());
    mpSelEngine->SetSelectionMode(SelectionMode::Range);
    mpSelEngine->EnableDrag(true);

    mpCursor.reset(new vcl::Cursor);
    mpCursor->Show();
    pWindow->SetCursor(mpCursor.get());
    pWindow->SetInputContext( InputContext( pEng->GetFont(), InputContextFlags::Text|InputContextFlags::ExtText ) );

    pWindow->GetOutDev()->SetLineColor();

    if (pWindow->GetDropTarget().is())
    {
        mxDnDListener = new vcl::unohelper::DragAndDropWrapper(this);

        pWindow->GetDropTarget()->addDragGestureListener(mxDnDListener);
        pWindow->GetDropTarget()->addDropTargetListener(mxDnDListener);
        pWindow->GetDropTarget()->setActive( true );
        pWindow->GetDropTarget()->setDefaultActions( css::datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
    }
}

TextView::~TextView()
{
    mpSelEngine.reset();
    mpSelFuncSet.reset();

    if (mpWindow->GetCursor() == mpCursor.get())
        mpWindow->SetCursor(nullptr);

    mpCursor.reset();
    mpDDInfo.reset();
}

void TextView::Invalidate()
{
    mpWindow->Invalidate();
}

void TextView::SetSelection( const TextSelection& rTextSel, bool bGotoCursor )
{
    // if someone left an empty attribute and then the Outliner manipulated the selection
    if (!maSelection.HasRange())
        mpTextEngine->CursorMoved(maSelection.GetStart().GetPara());

    // if the selection is manipulated after a KeyInput
    mpTextEngine->CheckIdleFormatter();

    HideSelection();
    TextSelection aNewSel( rTextSel );
    mpTextEngine->ValidateSelection(aNewSel);
    ImpSetSelection( aNewSel );
    ShowSelection();
    ShowCursor( bGotoCursor );
}

void TextView::SetSelection( const TextSelection& rTextSel )
{
    SetSelection(rTextSel, mbAutoScroll);
}

const TextSelection& TextView::GetSelection() const
{
    return maSelection;
}

TextSelection& TextView::GetSelection()
{
    return maSelection;
}

void TextView::DeleteSelected()
{
//  HideSelection();

    mpTextEngine->UndoActionStart();
    TextPaM aPaM = mpTextEngine->ImpDeleteText(maSelection);
    mpTextEngine->UndoActionEnd();

    ImpSetSelection( aPaM );
    mpTextEngine->FormatAndUpdate(this);
    ShowCursor();
}

void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection)
{
    if (!mbPaintSelection)
    {
        pSelection = nullptr;
    }
    else
    {
        // set correct background color;
        // unfortunately we cannot detect if it has changed
        vcl::Font aFont = mpTextEngine->GetFont();
        Color aColor = rRenderContext.GetBackground().GetColor();
        aColor.SetAlpha(255);
        if (aColor != aFont.GetFillColor())
        {
            if (aFont.IsTransparent())
                aColor = COL_TRANSPARENT;
            aFont.SetFillColor(aColor);
            mpTextEngine->maFont = std::move(aFont);
        }
    }

    mpTextEngine->ImpPaint(&rRenderContext, rStartPos, pPaintArea, pSelection);
}

void TextView::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    ImpPaint(rRenderContext, rRect);
}

void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    if (!mpTextEngine->GetUpdateMode() || mpTextEngine->IsInUndo())
        return;

    TextSelection *pDrawSelection = nullptr;
    if (maSelection.HasRange())
        pDrawSelection = &maSelection;

    Point aStartPos = ImpGetOutputStartPos(maStartDocPos);
    ImpPaint(rRenderContext, aStartPos, &rRect, pDrawSelection);
}

void TextView::ImpSetSelection( const TextSelection& rSelection )
{
    if (rSelection == maSelection)
        return;

    bool bCaret = false, bSelection = false;
    const TextPaM &rEnd = rSelection.GetEnd();
    const TextPaM& rOldEnd = maSelection.GetEnd();
    bool bGap = rSelection.HasRange(), bOldGap = maSelection.HasRange();
    if (rEnd != rOldEnd)
        bCaret = true;
    if (bGap || bOldGap)
        bSelection = true;

    maSelection = rSelection;

    if (bSelection)
        mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewSelectionChanged));

    if (bCaret)
        mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewCaretChanged));
}

void TextView::ShowSelection()
{
    ImpShowHideSelection();
}

void TextView::HideSelection()
{
    ImpShowHideSelection();
}

void TextView::ShowSelection( const TextSelection& rRange )
{
    ImpShowHideSelection( &rRange );
}

void TextView::ImpShowHideSelection(const TextSelection* pRange)
{
    const TextSelection* pRangeOrSelection = pRange ? pRange : &maSelection;

    if ( !pRangeOrSelection->HasRange() )
        return;

    if (mpWindow->IsPaintTransparent())
        mpWindow->Invalidate();
    else
    {
        TextSelection aRange( *pRangeOrSelection );
        aRange.Justify();
        bool bVisCursor = mpCursor->IsVisible();
        mpCursor->Hide();
        Invalidate();
        if (bVisCursor)
            mpCursor->Show();
    }
}

bool TextView::KeyInput( const KeyEvent& rKeyEvent )
{
    bool bDone      = true;
    bool bModified  = false;
    bool bMoved     = false;
    bool bEndKey    = false;    // special CursorPosition
    bool bAllowIdle = true;

    // check mModified;
    // the local bModified is not set e.g. by Cut/Paste, as here
    // the update happens somewhere else
    bool bWasModified = mpTextEngine->IsModified();
    mpTextEngine->SetModified(false);

    TextSelection aCurSel(maSelection);
    TextSelection aOldSel( aCurSel );

    sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode();
    KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
    if ( eFunc != KeyFuncType::DONTKNOW )
    {
        switch ( eFunc )
        {
            case KeyFuncType::CUT:
            {
                if (!mbReadOnly)
                    Cut();
            }
            break;
            case KeyFuncType::COPY:
            {
                Copy();
            }
            break;
            case KeyFuncType::PASTE:
            {
                if (!mbReadOnly)
                    Paste();
            }
            break;
            case KeyFuncType::UNDO:
            {
                if (!mbReadOnly)
                    Undo();
            }
            break;
            case KeyFuncType::REDO:
            {
                if (!mbReadOnly)
                    Redo();
            }
            break;

            default:    // might get processed below
                        eFunc = KeyFuncType::DONTKNOW;
        }
    }
    if ( eFunc == KeyFuncType::DONTKNOW )
    {
        switch ( nCode )
        {
            case KEY_UP:
            case KEY_DOWN:
            case KEY_LEFT:
            case KEY_RIGHT:
            case KEY_HOME:
            case KEY_END:
            case KEY_PAGEUP:
            case KEY_PAGEDOWN:
            case css::awt::Key::MOVE_WORD_FORWARD:
            case css::awt::Key::SELECT_WORD_FORWARD:
            case css::awt::Key::MOVE_WORD_BACKWARD:
            case css::awt::Key::SELECT_WORD_BACKWARD:
            case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
            case css::awt::Key::MOVE_TO_END_OF_LINE:
            case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
            case css::awt::Key::SELECT_TO_END_OF_LINE:
            case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
            case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
            case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
            case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
            case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
            case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
            case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
            case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
            {
                if ( ( !rKeyEvent.GetKeyCode().IsMod2() || ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) )
                      && !( rKeyEvent.GetKeyCode().IsMod1() && ( nCode == KEY_PAGEUP || nCode == KEY_PAGEDOWN ) ) )
                {
                    aCurSel = ImpMoveCursor( rKeyEvent );
                    if ( aCurSel.HasRange() ) {
                        css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
                        Copy( aSelection );
                    }
                    bMoved = true;
                    if ( nCode == KEY_END )
                        bEndKey = true;
                }
                else
                    bDone = false;
            }
            break;
            case KEY_BACKSPACE:
            case KEY_DELETE:
            case css::awt::Key::DELETE_WORD_BACKWARD:
            case css::awt::Key::DELETE_WORD_FORWARD:
            case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
            case css::awt::Key::DELETE_TO_END_OF_LINE:
            {
                if (!mbReadOnly && !rKeyEvent.GetKeyCode().IsMod2())
                {
                    sal_uInt8 nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT;
                    sal_uInt8 nMode = rKeyEvent.GetKeyCode().IsMod1() ? DELMODE_RESTOFWORD : DELMODE_SIMPLE;
                    if ( ( nMode == DELMODE_RESTOFWORD ) && rKeyEvent.GetKeyCode().IsShift() )
                        nMode = DELMODE_RESTOFCONTENT;

                    switch( nCode )
                    {
                    case css::awt::Key::DELETE_WORD_BACKWARD:
                        nDel = DEL_LEFT;
                        nMode = DELMODE_RESTOFWORD;
                        break;
                    case css::awt::Key::DELETE_WORD_FORWARD:
                        nDel = DEL_RIGHT;
                        nMode = DELMODE_RESTOFWORD;
                        break;
                    case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
                        nDel = DEL_LEFT;
                        nMode = DELMODE_RESTOFCONTENT;
                        break;
                    case css::awt::Key::DELETE_TO_END_OF_LINE:
                        nDel = DEL_RIGHT;
                        nMode = DELMODE_RESTOFCONTENT;
                        break;
                    default: break;
                    }

                    mpTextEngine->UndoActionStart();
                    aCurSel = ImpDelete( nDel, nMode );
                    mpTextEngine->UndoActionEnd();
                    bModified = true;
                    bAllowIdle = false;
                }
                else
                    bDone = false;
            }
            break;
            case KEY_TAB:
            {
                if ( !mbReadOnly && !rKeyEvent.GetKeyCode().IsShift() &&
                        !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() &&
                        ImplCheckTextLen( u"x" ) )
                {
                    aCurSel = mpTextEngine->ImpInsertText(aCurSel, '\t', !IsInsertMode());
                    bModified = true;
                }
                else
                    bDone = false;
            }
            break;
            case KEY_RETURN:
            {
                // do not swallow Shift-RETURN, as this would disable multi-line entries
                // in dialogs & property editors
                if ( !mbReadOnly && !rKeyEvent.GetKeyCode().IsMod1() &&
                        !rKeyEvent.GetKeyCode().IsMod2() && ImplCheckTextLen( u"x" ) )
                {
                    mpTextEngine->UndoActionStart();
                    aCurSel = mpTextEngine->ImpInsertParaBreak(aCurSel);
                    if (mbAutoIndent)
                    {
                        TextNode* pPrev = mpTextEngine->mpDoc->GetNodes()[aCurSel.GetEnd().GetPara() - 1].get();
                        sal_Int32 n = 0;
                        while ( ( n < pPrev->GetText().getLength() ) && (
                                    ( pPrev->GetText()[ n ] == ' ' ) ||
                                    ( pPrev->GetText()[ n ] == '\t' ) ) )
                        {
                            n++;
                        }
                        if ( n )
                            aCurSel
                                = mpTextEngine->ImpInsertText(aCurSel, pPrev->GetText().copy(0, n));
                    }
                    mpTextEngine->UndoActionEnd();
                    bModified = true;
                }
                else
                    bDone = false;
            }
            break;
            case KEY_INSERT:
            {
                if (!mbReadOnly)
                    SetInsertMode( !IsInsertMode() );
            }
            break;
            default:
            {
                if ( TextEngine::IsSimpleCharInput( rKeyEvent ) )
                {
                    sal_Unicode nCharCode = rKeyEvent.GetCharCode();
                    if (!mbReadOnly && ImplCheckTextLen(OUStringChar(nCharCode)))    // otherwise swallow the character anyway
                    {
                        aCurSel = mpTextEngine->ImpInsertText(nCharCode, aCurSel, !IsInsertMode(), true);
                        bModified = true;
                    }
                }
                else
                    bDone = false;
            }
        }
    }

    if ( aCurSel != aOldSel )   // Check if changed, maybe other method already changed maSelection, don't overwrite that!
        ImpSetSelection( aCurSel );

    if ( ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) )
        mnTravelXPos = TRAVEL_X_DONTKNOW;

    if ( bModified )
    {
        // Idle-Formatter only if AnyInput
        if ( bAllowIdle && Application::AnyInput( VclInputFlags::KEYBOARD) )
            mpTextEngine->IdleFormatAndUpdate(this);
        else
            mpTextEngine->FormatAndUpdate(this);
    }
    else if ( bMoved )
    {
        // selection is painted now in ImpMoveCursor
        ImpShowCursor(mbAutoScroll, true, bEndKey);
    }

    if (mpTextEngine->IsModified())
        mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified));
    else if ( bWasModified )
        mpTextEngine->SetModified(true);

    return bDone;
}

void TextView::MouseButtonUp( const MouseEvent& rMouseEvent )
{
    mbClickedInSelection = false;
    mnTravelXPos = TRAVEL_X_DONTKNOW;
    mpSelEngine->SelMouseButtonUp(rMouseEvent);
    if ( rMouseEvent.IsMiddle() && !IsReadOnly() &&
         ( GetWindow()->GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
    {
        css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
        Paste( aSelection );
        if (mpTextEngine->IsModified())
            mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified));
    }
    else if ( rMouseEvent.IsLeft() && GetSelection().HasRange() )
    {
        css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
        Copy( aSelection );
    }
}

void TextView::MouseButtonDown( const MouseEvent& rMouseEvent )
{
    mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
    mnTravelXPos = TRAVEL_X_DONTKNOW;
    mbClickedInSelection = IsSelectionAtPoint(rMouseEvent.GetPosPixel());

    mpTextEngine->SetActiveView(this);

    mpSelEngine->SelMouseButtonDown(rMouseEvent);

    // mbu 20.01.2005 - SelMouseButtonDown() possibly triggers a 'selection changed'
    // notification. The appropriate handler could change the current selection,
    // which is the case in the MailMerge address block control. To enable select'n'drag
    // we need to reevaluate the selection after the notification has been fired.
    mbClickedInSelection = IsSelectionAtPoint(rMouseEvent.GetPosPixel());

    // special cases
    if ( rMouseEvent.IsShift() || ( rMouseEvent.GetClicks() < 2 ))
        return;

    if ( rMouseEvent.IsMod2() )
    {
        HideSelection();
        ImpSetSelection(maSelection.GetEnd());
        SetCursorAtPoint( rMouseEvent.GetPosPixel() );  // not set by SelectionEngine for MOD2
    }

    if ( rMouseEvent.GetClicks() == 2 )
    {
        // select word
        if (maSelection.GetEnd().GetIndex() < mpTextEngine->GetTextLen( maSelection.GetEnd().GetPara()))
        {
            HideSelection();
            // tdf#57879 - expand selection to include connector punctuations
            TextSelection aNewSel;
            mpTextEngine->GetWord(maSelection.GetEnd(), &aNewSel.GetStart(), &aNewSel.GetEnd());
            ImpSetSelection( aNewSel );
            ShowSelection();
            ShowCursor();
        }
    }
    else if ( rMouseEvent.GetClicks() == 3 )
    {
        // select paragraph
        if (maSelection.GetStart().GetIndex() || (maSelection.GetEnd().GetIndex() < mpTextEngine->GetTextLen(maSelection.GetEnd().GetPara())))
        {
            HideSelection();
            TextSelection aNewSel(maSelection);
            aNewSel.GetStart().GetIndex() = 0;
            aNewSel.GetEnd().GetIndex() = mpTextEngine->mpDoc->GetNodes()[maSelection.GetEnd().GetPara()]->GetText().getLength();
            ImpSetSelection( aNewSel );
            ShowSelection();
            ShowCursor();
        }
    }
}

void TextView::MouseMove( const MouseEvent& rMouseEvent )
{
    mnTravelXPos = TRAVEL_X_DONTKNOW;
    mpSelEngine->SelMouseMove(rMouseEvent);
}

void TextView::Command( const CommandEvent& rCEvt )
{
    mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown
    mpTextEngine->SetActiveView(this);

    if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
    {
        DeleteSelected();
        TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[GetSelection().GetEnd().GetPara()].get();
        mpTextEngine->mpIMEInfos = std::make_unique<TEIMEInfos>(
            GetSelection().GetEnd(), pNode->GetText().copy(GetSelection().GetEnd().GetIndex()));
        mpTextEngine->mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
    }
    else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
    {
        SAL_WARN_IF(!mpTextEngine->mpIMEInfos, "vcl",
                    "CommandEventId::EndExtTextInput => No Start ?");
        if (mpTextEngine->mpIMEInfos)
        {
            TEParaPortion* pPortion = mpTextEngine->mpTEParaPortions->GetObject(
                mpTextEngine->mpIMEInfos->aPos.GetPara());
            pPortion->MarkSelectionInvalid(mpTextEngine->mpIMEInfos->aPos.GetIndex());

            bool bInsertMode = !mpTextEngine->mpIMEInfos->bWasCursorOverwrite;

            mpTextEngine->mpIMEInfos.reset();

            mpTextEngine->TextModified();
            mpTextEngine->FormatAndUpdate(this);

            SetInsertMode( bInsertMode );

            if (mpTextEngine->IsModified())
                mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified));
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
    {
        SAL_WARN_IF(!mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::ExtTextInput => No Start ?");
        if (mpTextEngine->mpIMEInfos)
        {
            const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();

            if ( !pData->IsOnlyCursorChanged() )
            {
                TextSelection aSelect(mpTextEngine->mpIMEInfos->aPos);
                aSelect.GetEnd().GetIndex() += mpTextEngine->mpIMEInfos->nLen;
                aSelect = mpTextEngine->ImpDeleteText(aSelect);
                aSelect = mpTextEngine->ImpInsertText(aSelect, pData->GetText());

                if (mpTextEngine->mpIMEInfos->bWasCursorOverwrite)
                {
                    const sal_Int32 nOldIMETextLen = mpTextEngine->mpIMEInfos->nLen;
                    const sal_Int32 nNewIMETextLen = pData->GetText().getLength();

                    if ((nOldIMETextLen > nNewIMETextLen) &&
                         ( nNewIMETextLen < mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
                    {
                        // restore old characters
                        sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
                        TextPaM aPaM(mpTextEngine->mpIMEInfos->aPos);
                        aPaM.GetIndex() += nNewIMETextLen;
                        mpTextEngine->ImpInsertText(aPaM, mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.copy(nNewIMETextLen, nRestore));
                    }
                    else if ((nOldIMETextLen < nNewIMETextLen) &&
                              ( nOldIMETextLen < mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
                    {
                        // overwrite
                        const sal_Int32 nOverwrite = std::min(nNewIMETextLen, mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength()) - nOldIMETextLen;
                        SAL_WARN_IF( !nOverwrite || (nOverwrite >= 0xFF00), "vcl", "IME Overwrite?!" );
                        TextPaM aPaM(mpTextEngine->mpIMEInfos->aPos);
                        aPaM.GetIndex() += nNewIMETextLen;
                        TextSelection aSel( aPaM );
                        aSel.GetEnd().GetIndex() += nOverwrite;
                        mpTextEngine->ImpDeleteText(aSel);
                    }
                }

                if ( pData->GetTextAttr() )
                {
                    mpTextEngine->mpIMEInfos->CopyAttribs(pData->GetTextAttr(), pData->GetText().getLength());
                }
                else
                {
                    mpTextEngine->mpIMEInfos->DestroyAttribs();
                }

                TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(
                    mpTextEngine->mpIMEInfos->aPos.GetPara());
                pPPortion->MarkSelectionInvalid(mpTextEngine->mpIMEInfos->aPos.GetIndex());
                mpTextEngine->FormatAndUpdate(this);
            }

            TextSelection aNewSel
                = TextPaM(mpTextEngine->mpIMEInfos->aPos.GetPara(),
                          mpTextEngine->mpIMEInfos->aPos.GetIndex() + pData->GetCursorPos());
            SetSelection( aNewSel );
            SetInsertMode( !pData->IsCursorOverwrite() );

            if ( pData->IsCursorVisible() )
                ShowCursor();
            else
                HideCursor();
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
    {
        if (mpTextEngine->mpIMEInfos && mpTextEngine->mpIMEInfos->nLen)
        {
            TextPaM aPaM( GetSelection().GetEnd() );
            tools::Rectangle aR1 = mpTextEngine->PaMtoEditCursor(aPaM);

            sal_Int32 nInputEnd
                = mpTextEngine->mpIMEInfos->aPos.GetIndex() + mpTextEngine->mpIMEInfos->nLen;

            if (!mpTextEngine->IsFormatted())
                mpTextEngine->FormatDoc();

            TEParaPortion* pParaPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
            std::vector<TextLine>::size_type nLine = pParaPortion->GetLineNumber( aPaM.GetIndex(), true );
            TextLine& rLine = pParaPortion->GetLines()[ nLine ];
            if ( nInputEnd > rLine.GetEnd() )
                nInputEnd = rLine.GetEnd();
            tools::Rectangle aR2
                = mpTextEngine->PaMtoEditCursor(TextPaM(aPaM.GetPara(), nInputEnd));

            tools::Long nWidth = aR2.Left()-aR1.Right();
            aR1.Move( -GetStartDocPos().X(), -GetStartDocPos().Y() );
            GetWindow()->SetCursorRect( &aR1, nWidth );
        }
        else
        {
            GetWindow()->SetCursorRect();
        }
    }
    else
    {
        mpSelEngine->Command(rCEvt);
    }
}

void TextView::ShowCursor( bool bGotoCursor, bool bForceVisCursor )
{
    // this setting has more weight
    if (!mbAutoScroll)
        bGotoCursor = false;
    ImpShowCursor( bGotoCursor, bForceVisCursor, false );
}

void TextView::HideCursor()
{
    mpCursor->Hide();
}

void TextView::Scroll( tools::Long ndX, tools::Long ndY )
{
    SAL_WARN_IF(!mpTextEngine->IsFormatted(), "vcl", "Scroll: Not formatted!");

    if ( !ndX && !ndY )
        return;

    Point aNewStartPos(maStartDocPos);

    // Vertical:
    aNewStartPos.AdjustY( -ndY );
    if ( aNewStartPos.Y() < 0 )
        aNewStartPos.setY( 0 );

    // Horizontal:
    aNewStartPos.AdjustX( -ndX );
    if ( aNewStartPos.X() < 0 )
        aNewStartPos.setX( 0 );

    tools::Long nDiffX = maStartDocPos.X() - aNewStartPos.X();
    tools::Long nDiffY = maStartDocPos.Y() - aNewStartPos.Y();

    if ( nDiffX || nDiffY )
    {
        bool bVisCursor = mpCursor->IsVisible();
        mpCursor->Hide();
        mpWindow->PaintImmediately();
        maStartDocPos = aNewStartPos;

        if (mpTextEngine->IsRightToLeft())
            nDiffX = -nDiffX;
        mpWindow->Scroll(nDiffX, nDiffY);
        mpWindow->PaintImmediately();
        mpCursor->SetPos(mpCursor->GetPos() + Point(nDiffX, nDiffY));
        if (bVisCursor && !mbReadOnly)
            mpCursor->Show();
    }

    mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewScrolled));
}

void TextView::Undo()
{
    mpTextEngine->SetActiveView(this);
    mpTextEngine->GetUndoManager().Undo();
}

void TextView::Redo()
{
    mpTextEngine->SetActiveView(this);
    mpTextEngine->GetUndoManager().Redo();
}

void TextView::Cut()
{
    mpTextEngine->UndoActionStart();
    Copy();
    DeleteSelected();
    mpTextEngine->UndoActionEnd();
}

void TextView::Copy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
{
    if ( !rxClipboard.is() )
        return;

    rtl::Reference<TETextDataObject> pDataObj = new TETextDataObject( GetSelected() );

    SolarMutexReleaser aReleaser;

    try
    {
        rxClipboard->setContents( pDataObj, nullptr );

        css::uno::Reference< css::datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, css::uno::UNO_QUERY );
        if( xFlushableClipboard.is() )
            xFlushableClipboard->flushClipboard();
    }
    catch( const css::uno::Exception& )
    {
    }
}

void TextView::Copy()
{
    css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
    Copy( aClipboard );
}

void TextView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard )
{
    if ( !rxClipboard.is() )
        return;

    css::uno::Reference< css::datatransfer::XTransferable > xDataObj;

    try
        {
            SolarMutexReleaser aReleaser;
            xDataObj = rxClipboard->getContents();
        }
    catch( const css::uno::Exception& )
        {
        }

    if ( !xDataObj.is() )
        return;

    css::datatransfer::DataFlavor aFlavor;
    SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
    if ( !xDataObj->isDataFlavorSupported( aFlavor ) )
        return;

    try
    {
        css::uno::Any aData = xDataObj->getTransferData( aFlavor );
        OUString aText;
        aData >>= aText;
        bool bWasTruncated = false;
        if (mpTextEngine->GetMaxTextLen() != 0)
            bWasTruncated = ImplTruncateNewText( aText );
        InsertText( aText );
        mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified));

        if( bWasTruncated )
            Edit::ShowTruncationWarning(mpWindow->GetFrameWeld());
    }
    catch( const css::datatransfer::UnsupportedFlavorException& )
    {
    }
}

void TextView::Paste()
{
    css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard());
    Paste( aClipboard );
}

OUString TextView::GetSelected() const
{
    return GetSelected( GetSystemLineEnd() );
}

OUString TextView::GetSelected( LineEnd aSeparator ) const
{
    return mpTextEngine->GetText(maSelection, aSeparator);
}

void TextView::SetInsertMode( bool bInsert )
{
    if (mbInsertMode != bInsert)
    {
        mbInsertMode = bInsert;
        ShowCursor(mbAutoScroll, false);
    }
}

void TextView::SetReadOnly( bool bReadOnly )
{
    if (mbReadOnly != bReadOnly)
    {
        mbReadOnly = bReadOnly;
        if (!mbReadOnly)
            ShowCursor(mbAutoScroll, false);
        else
            HideCursor();

        GetWindow()->SetInputContext(
            InputContext(mpTextEngine->GetFont(),
                         bReadOnly ? InputContextFlags::Text | InputContextFlags::ExtText
                                   : InputContextFlags::NONE));
    }
}

TextSelection const & TextView::ImpMoveCursor( const KeyEvent& rKeyEvent )
{
    // normally only needed for Up/Down; but who cares
    mpTextEngine->CheckIdleFormatter();

    TextPaM aPaM(maSelection.GetEnd());
    TextPaM aOldEnd( aPaM );

    TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
    if (mpTextEngine->IsRightToLeft())
        eTextDirection = TextDirectionality::RightToLeft_TopToBottom;

    KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection );

    bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1();
    sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode();

    bool bSelect = aTranslatedKeyEvent.GetKeyCode().IsShift();
    switch ( nCode )
    {
        case KEY_UP:        aPaM = CursorUp( aPaM );
                            break;
        case KEY_DOWN:      aPaM = CursorDown( aPaM );
                            break;
        case KEY_HOME:
            if (bCtrl)
            {
                aPaM = CursorStartOfDoc();
            }
            else
            {
                // tdf#145764 - move cursor to the beginning or the first non-space character in the same line
                const TextPaM aFirstWordPaM = CursorFirstWord(aPaM);
                aPaM = aPaM.GetIndex() == aFirstWordPaM.GetIndex() ? CursorStartOfLine(aPaM) : aFirstWordPaM;
            }
                            break;
        case KEY_END:       aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM );
                            break;
        case KEY_PAGEUP:    aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM );
                            break;
        case KEY_PAGEDOWN:  aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM );
                            break;
        case KEY_LEFT:      aPaM = bCtrl ? CursorWordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
                            break;
        case KEY_RIGHT:     aPaM = bCtrl ? CursorWordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
                            break;
        case css::awt::Key::SELECT_WORD_FORWARD:
                            bSelect = true;
                            [[fallthrough]];
        case css::awt::Key::MOVE_WORD_FORWARD:
                            aPaM = CursorWordRight( aPaM );
                            break;
        case css::awt::Key::SELECT_WORD_BACKWARD:
                            bSelect = true;
                            [[fallthrough]];
        case css::awt::Key::MOVE_WORD_BACKWARD:
                            aPaM = CursorWordLeft( aPaM );
                            break;
        case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
                            bSelect = true;
                            [[fallthrough]];
        case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
                            aPaM = CursorStartOfLine( aPaM );
                            break;
        case css::awt::Key::SELECT_TO_END_OF_LINE:
                            bSelect = true;
                            [[fallthrough]];
        case css::awt::Key::MOVE_TO_END_OF_LINE:
                            aPaM = CursorEndOfLine( aPaM );
                            break;
        case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
                            bSelect = true;
                            [[fallthrough]];
        case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
                            aPaM = CursorStartOfParagraph( aPaM );
                            break;
        case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
                            bSelect = true;
                            [[fallthrough]];
        case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
                            aPaM = CursorEndOfParagraph( aPaM );
                            break;
        case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
                            bSelect = true;
                            [[fallthrough]];
        case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
                            aPaM = CursorStartOfDoc();
                            break;
        case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
                            bSelect = true;
                            [[fallthrough]];
        case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
                            aPaM = CursorEndOfDoc();
                            break;
    }

    // might cause a CreateAnchor or Deselection all
    mpSelEngine->CursorPosChanging(bSelect, aTranslatedKeyEvent.GetKeyCode().IsMod1());

    if ( aOldEnd != aPaM )
    {
        mpTextEngine->CursorMoved(aOldEnd.GetPara());

        TextSelection aNewSelection(maSelection);
        aNewSelection.GetEnd() = aPaM;
        if ( bSelect )
        {
            // extend the selection
            ImpSetSelection( aNewSelection );
            ShowSelection( TextSelection( aOldEnd, aPaM ) );
        }
        else
        {
            aNewSelection.GetStart() = aPaM;
            ImpSetSelection( aNewSelection );
        }
    }

    return maSelection;
}

void TextView::InsertText( const OUString& rStr )
{
    mpTextEngine->UndoActionStart();

    TextSelection aNewSel = mpTextEngine->ImpInsertText(maSelection, rStr);

    ImpSetSelection( aNewSel );

    mpTextEngine->UndoActionEnd();

    mpTextEngine->FormatAndUpdate(this);
}

TextPaM TextView::CursorLeft( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
{
    TextPaM aPaM( rPaM );

    if ( aPaM.GetIndex() )
    {
        TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
        css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
        sal_Int32 nCount = 1;
        aPaM.GetIndex()
            = xBI->previousCharacters(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(),
                                      nCharacterIteratorMode, nCount, nCount);
    }
    else if ( aPaM.GetPara() )
    {
        aPaM.GetPara()--;
        TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
        aPaM.GetIndex() = pNode->GetText().getLength();
    }
    return aPaM;
}

TextPaM TextView::CursorRight( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
{
    TextPaM aPaM( rPaM );

    TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
    if ( aPaM.GetIndex() < pNode->GetText().getLength() )
    {
        css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
        sal_Int32 nCount = 1;
        aPaM.GetIndex()
            = xBI->nextCharacters(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(),
                                  nCharacterIteratorMode, nCount, nCount);
    }
    else if (aPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1))
    {
        aPaM.GetPara()++;
        aPaM.GetIndex() = 0;
    }

    return aPaM;
}

TextPaM TextView::CursorFirstWord( const TextPaM& rPaM )
{
    TextPaM aPaM(rPaM);
    TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();

    css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
    aPaM.GetIndex() = xBI->beginOfSentence(pNode->GetText(), 0, mpTextEngine->GetLocale());

    return aPaM;
}

TextPaM TextView::CursorWordLeft( const TextPaM& rPaM )
{
    TextPaM aPaM( rPaM );

    if ( aPaM.GetIndex() )
    {
        // tdf#57879 - expand selection to the left to include connector punctuations
        mpTextEngine->GetWord(rPaM, &aPaM);
        if ( aPaM.GetIndex() >= rPaM.GetIndex() )
        {
            TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
            css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
            aPaM.GetIndex()
                = xBI->previousWord(pNode->GetText(), rPaM.GetIndex(), mpTextEngine->GetLocale(),
                                    css::i18n::WordType::ANYWORD_IGNOREWHITESPACES).startPos;
            if ( aPaM.GetIndex() > 0 )
                mpTextEngine->GetWord(aPaM, &aPaM);
            else
                aPaM.GetIndex() = 0;
        }
    }
    else if ( aPaM.GetPara() )
    {
        aPaM.GetPara()--;
        TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
        aPaM.GetIndex() = pNode->GetText().getLength();
    }
    return aPaM;
}

TextPaM TextView::CursorWordRight( const TextPaM& rPaM )
{
    TextPaM aPaM( rPaM );

    TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
    if ( aPaM.GetIndex() < pNode->GetText().getLength() )
    {
        css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
        // tdf#160202 - NextWord unexpectedly skips two words at the start of any word
        const auto aWordBoundary
            = xBI->getWordBoundary(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(),
                                   css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);

        // Check if the current index is inside the word boundary
        if (aWordBoundary.startPos <= aPaM.GetIndex() && aPaM.GetIndex() < aWordBoundary.endPos)
            aPaM.GetIndex() = aWordBoundary.startPos;
        else
            aPaM.GetIndex()
                = xBI->nextWord(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(),
                                css::i18n::WordType::ANYWORD_IGNOREWHITESPACES)
                      .endPos;
        mpTextEngine->GetWord(aPaM, nullptr, &aPaM);
    }
    else if (aPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1))
    {
        aPaM.GetPara()++;
        aPaM.GetIndex() = 0;
    }

    return aPaM;
}

TextPaM TextView::ImpDelete( sal_uInt8 nMode, sal_uInt8 nDelMode )
{
    if (maSelection.HasRange()) // only delete selection
        return mpTextEngine->ImpDeleteText(maSelection);

    TextPaM aStartPaM = maSelection.GetStart();
    TextPaM aEndPaM = aStartPaM;
    if ( nMode == DEL_LEFT )
    {
        if ( nDelMode == DELMODE_SIMPLE )
        {
            aEndPaM = CursorLeft( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) );
        }
        else if ( nDelMode == DELMODE_RESTOFWORD )
        {
            TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get();
            css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
            css::i18n::Boundary aBoundary = xBI->getWordBoundary(
                pNode->GetText(), maSelection.GetEnd().GetIndex(), mpTextEngine->GetLocale(),
                css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
            if (aBoundary.startPos == maSelection.GetEnd().GetIndex())
                aBoundary = xBI->previousWord(pNode->GetText(), maSelection.GetEnd().GetIndex(),
                                              mpTextEngine->GetLocale(),
                                              css::i18n::WordType::ANYWORD_IGNOREWHITESPACES);
            // #i63506# startPos is -1 when the paragraph starts with a tab
            aEndPaM.GetIndex() = std::max<sal_Int32>(aBoundary.startPos, 0);
        }
        else    // DELMODE_RESTOFCONTENT
        {
            if ( aEndPaM.GetIndex() != 0 )
                aEndPaM.GetIndex() = 0;
            else if ( aEndPaM.GetPara() )
            {
                // previous paragraph
                aEndPaM.GetPara()--;
                aEndPaM.GetIndex() = 0;
            }
        }
    }
    else
    {
        if ( nDelMode == DELMODE_SIMPLE )
        {
            aEndPaM = CursorRight( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
        }
        else if ( nDelMode == DELMODE_RESTOFWORD )
        {
            TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get();
            css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator();
            css::i18n::Boundary aBoundary = xBI->nextWord(
                pNode->GetText(), maSelection.GetEnd().GetIndex(), mpTextEngine->GetLocale(),
                css::i18n::WordType::ANYWORD_IGNOREWHITESPACES);
            aEndPaM.GetIndex() = aBoundary.startPos;
        }
        else    // DELMODE_RESTOFCONTENT
        {
            TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get();
            if ( aEndPaM.GetIndex() < pNode->GetText().getLength() )
                aEndPaM.GetIndex() = pNode->GetText().getLength();
            else if (aEndPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1))
            {
                // next paragraph
                aEndPaM.GetPara()++;
                TextNode* pNextNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get();
                aEndPaM.GetIndex() = pNextNode->GetText().getLength();
            }
        }
    }

    return mpTextEngine->ImpDeleteText(TextSelection(aStartPaM, aEndPaM));
}

TextPaM TextView::CursorUp( const TextPaM& rPaM )
{
    TextPaM aPaM( rPaM );

    tools::Long nX;
    if (mnTravelXPos == TRAVEL_X_DONTKNOW)
    {
        nX = mpTextEngine->GetEditCursor(rPaM, false).Left();
        mnTravelXPos = static_cast<sal_uInt16>(nX) + 1;
    }
    else
        nX = mnTravelXPos;

    TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara());
    std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
    if ( nLine )    // same paragraph
    {
        aPaM.GetIndex() = mpTextEngine->GetCharPos(rPaM.GetPara(), nLine - 1, nX);
        // If we need to go to the end of a line that was wrapped automatically,
        // the cursor ends up at the beginning of the 2nd line
        // Problem: Last character of an automatically wrapped line = Cursor
        TextLine& rLine = pPPortion->GetLines()[ nLine - 1 ];
        if ( aPaM.GetIndex() && ( aPaM.GetIndex() == rLine.GetEnd() ) )
            --aPaM.GetIndex();
    }
    else if ( rPaM.GetPara() )  // previous paragraph
    {
        aPaM.GetPara()--;
        pPPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
        std::vector<TextLine>::size_type nL = pPPortion->GetLines().size() - 1;
        aPaM.GetIndex() = mpTextEngine->GetCharPos(aPaM.GetPara(), nL, nX + 1);
    }

    return aPaM;
}

TextPaM TextView::CursorDown( const TextPaM& rPaM )
{
    TextPaM aPaM( rPaM );

    tools::Long nX;
    if (mnTravelXPos == TRAVEL_X_DONTKNOW)
    {
        nX = mpTextEngine->GetEditCursor(rPaM, false).Left();
        mnTravelXPos = static_cast<sal_uInt16>(nX) + 1;
    }
    else
        nX = mnTravelXPos;

    TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara());
    std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false );
    if ( nLine < ( pPPortion->GetLines().size() - 1 ) )
    {
        aPaM.GetIndex() = mpTextEngine->GetCharPos(rPaM.GetPara(), nLine + 1, nX);

        // special case CursorUp
        TextLine& rLine = pPPortion->GetLines()[ nLine + 1 ];
        if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && aPaM.GetIndex() < pPPortion->GetNode()->GetText().getLength() )
            --aPaM.GetIndex();
    }
    else if (rPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1)) // next paragraph
    {
        aPaM.GetPara()++;
        pPPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
        aPaM.GetIndex() = mpTextEngine->GetCharPos(aPaM.GetPara(), 0, nX + 1);
        TextLine& rLine = pPPortion->GetLines().front();
        if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && ( pPPortion->GetLines().size() > 1 ) )
            --aPaM.GetIndex();
    }

    return aPaM;
}

TextPaM TextView::CursorStartOfLine( const TextPaM& rPaM )
{
    TextPaM aPaM( rPaM );

    TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara());
    std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
    TextLine& rLine = pPPortion->GetLines()[ nLine ];
    aPaM.GetIndex() = rLine.GetStart();

    return aPaM;
}

TextPaM TextView::CursorEndOfLine( const TextPaM& rPaM )
{
    TextPaM aPaM( rPaM );

    TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara());
    std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
    TextLine& rLine = pPPortion->GetLines()[ nLine ];
    aPaM.GetIndex() = rLine.GetEnd();

    if ( rLine.GetEnd() > rLine.GetStart() )  // empty line
    {
        sal_Unicode cLastChar = pPPortion->GetNode()->GetText()[ aPaM.GetIndex()-1 ];
        if ( ( cLastChar == ' ' ) && ( aPaM.GetIndex() != pPPortion->GetNode()->GetText().getLength() ) )
        {
            // for a blank in an automatically-wrapped line it is better to stand before it,
            // as the user will intend to stand behind the prior word.
            // If there is a change, special case for Pos1 after End!
            --aPaM.GetIndex();
        }
    }
    return aPaM;
}

TextPaM TextView::CursorStartOfParagraph( const TextPaM& rPaM )
{
    TextPaM aPaM( rPaM );
    aPaM.GetIndex() = 0;
    return aPaM;
}

TextPaM TextView::CursorEndOfParagraph( const TextPaM& rPaM )
{
    TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[rPaM.GetPara()].get();
    TextPaM aPaM( rPaM );
    aPaM.GetIndex() = pNode->GetText().getLength();
    return aPaM;
}

TextPaM TextView::CursorStartOfDoc()
{
    TextPaM aPaM( 0, 0 );
    return aPaM;
}

TextPaM TextView::CursorEndOfDoc()
{
    const sal_uInt32 nNode = static_cast<sal_uInt32>(mpTextEngine->mpDoc->GetNodes().size() - 1);
    TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[nNode].get();
    TextPaM aPaM( nNode, pNode->GetText().getLength() );
    return aPaM;
}

TextPaM TextView::PageUp( const TextPaM& rPaM )
{
    tools::Rectangle aRect = mpTextEngine->PaMtoEditCursor(rPaM);
    Point aTopLeft = aRect.TopLeft();
    aTopLeft.AdjustY(-(mpWindow->GetOutputSizePixel().Height() * 9 / 10));
    aTopLeft.AdjustX(1 );
    if ( aTopLeft.Y() < 0 )
        aTopLeft.setY( 0 );

    TextPaM aPaM = mpTextEngine->GetPaM(aTopLeft);
    return aPaM;
}

TextPaM TextView::PageDown( const TextPaM& rPaM )
{
    tools::Rectangle aRect = mpTextEngine->PaMtoEditCursor(rPaM);
    Point aBottomRight = aRect.BottomRight();
    aBottomRight.AdjustY(mpWindow->GetOutputSizePixel().Height() * 9 / 10);
    aBottomRight.AdjustX(1 );
    tools::Long nHeight = mpTextEngine->GetTextHeight();
    if ( aBottomRight.Y() > nHeight )
        aBottomRight.setY( nHeight-1 );

    TextPaM aPaM = mpTextEngine->GetPaM(aBottomRight);
    return aPaM;
}

void TextView::ImpShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bSpecial )
{
    if (mpTextEngine->IsFormatting())
        return;
    if (!mpTextEngine->GetUpdateMode())
        return;
    if (mpTextEngine->IsInUndo())
        return;

    mpTextEngine->CheckIdleFormatter();
    if (!mpTextEngine->IsFormatted())
        mpTextEngine->FormatAndUpdate(this);

    TextPaM aPaM(maSelection.GetEnd());
    tools::Rectangle aEditCursor = mpTextEngine->PaMtoEditCursor(aPaM, bSpecial);

    // Remember that we placed the cursor behind the last character of a line
    mbCursorAtEndOfLine = false;
    if( bSpecial )
    {
        TEParaPortion* pParaPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
        mbCursorAtEndOfLine = pParaPortion->GetLineNumber(aPaM.GetIndex(), true)
                              != pParaPortion->GetLineNumber(aPaM.GetIndex(), false);
    }

    if (!IsInsertMode() && !maSelection.HasRange())
    {
        TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get();
        if ( !pNode->GetText().isEmpty() && ( aPaM.GetIndex() < pNode->GetText().getLength() ) )
        {
            // If we are behind a portion, and the next portion has other direction, we must change position...
            aEditCursor.SetLeft(mpTextEngine->GetEditCursor(aPaM, false, true).Left());
            aEditCursor.SetRight( aEditCursor.Left() );

            TEParaPortion* pParaPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());

            sal_Int32 nTextPortionStart = 0;
            std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true );
            TETextPortion& rTextPortion = pParaPortion->GetTextPortions()[ nTextPortion ];
            if ( rTextPortion.GetKind() == PORTIONKIND_TAB )
            {
                aEditCursor.AdjustRight(rTextPortion.GetWidth() );
            }
            else
            {
                TextPaM aNext = CursorRight( TextPaM( aPaM.GetPara(), aPaM.GetIndex() ), sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) );
                aEditCursor.SetRight(mpTextEngine->GetEditCursor(aNext, true).Left());
            }
        }
    }

    Size aOutSz = mpWindow->GetOutputSizePixel();
    if ( aEditCursor.GetHeight() > aOutSz.Height() )
        aEditCursor.SetBottom( aEditCursor.Top() + aOutSz.Height() - 1 );

    aEditCursor.AdjustLeft( -1 );

    if ( bGotoCursor
        // #i81283# protect maStartDocPos against initialization problems
        && aOutSz.Width() && aOutSz.Height()
    )
    {
        tools::Long nVisStartY = maStartDocPos.Y();
        tools::Long nVisEndY = maStartDocPos.Y() + aOutSz.Height();
        tools::Long nVisStartX = maStartDocPos.X();
        tools::Long nVisEndX = maStartDocPos.X() + aOutSz.Width();
        tools::Long nMoreX = aOutSz.Width() / 4;

        Point aNewStartPos(maStartDocPos);

        if ( aEditCursor.Bottom() > nVisEndY )
        {
            aNewStartPos.AdjustY( aEditCursor.Bottom() - nVisEndY);
        }
        else if ( aEditCursor.Top() < nVisStartY )
        {
            aNewStartPos.AdjustY( -( nVisStartY - aEditCursor.Top() ) );
        }

        if ( aEditCursor.Right() >= nVisEndX )
        {
            aNewStartPos.AdjustX( aEditCursor.Right() - nVisEndX );

            // do you want some more?
            aNewStartPos.AdjustX(nMoreX );
        }
        else if ( aEditCursor.Left() <= nVisStartX )
        {
            aNewStartPos.AdjustX( -( nVisStartX - aEditCursor.Left() ) );

            // do you want some more?
            aNewStartPos.AdjustX( -nMoreX );
        }

        // X can be wrong for the 'some more' above:
//      sal_uLong nMaxTextWidth = mpTextEngine->GetMaxTextWidth();
//      if ( !nMaxTextWidth || ( nMaxTextWidth > 0x7FFFFFFF ) )
//          nMaxTextWidth = 0x7FFFFFFF;
//      long nMaxX = (long)nMaxTextWidth - aOutSz.Width();
        tools::Long nMaxX = mpTextEngine->CalcTextWidth() - aOutSz.Width();
        if ( nMaxX < 0 )
            nMaxX = 0;

        if ( aNewStartPos.X() < 0 )
            aNewStartPos.setX( 0 );
        else if ( aNewStartPos.X() > nMaxX )
            aNewStartPos.setX( nMaxX );

        // Y should not be further down than needed
        tools::Long nYMax = mpTextEngine->GetTextHeight() - aOutSz.Height();
        if ( nYMax < 0 )
            nYMax = 0;
        if ( aNewStartPos.Y() > nYMax )
            aNewStartPos.setY( nYMax );

        if (aNewStartPos != maStartDocPos)
            Scroll(-(aNewStartPos.X() - maStartDocPos.X()), -(aNewStartPos.Y() - maStartDocPos.Y()));
    }

    if ( aEditCursor.Right() < aEditCursor.Left() )
    {
        tools::Long n = aEditCursor.Left();
        aEditCursor.SetLeft( aEditCursor.Right() );
        aEditCursor.SetRight( n );
    }

    Point aPoint(GetWindowPos(!mpTextEngine->IsRightToLeft() ? aEditCursor.TopLeft()
                                                             : aEditCursor.TopRight()));
    mpCursor->SetPos(aPoint);
    mpCursor->SetSize(aEditCursor.GetSize());
    if (bForceVisCursor && mbCursorEnabled)
        mpCursor->Show();
}

void TextView::SetCursorAtPoint( const Point& rPosPixel )
{
    mpTextEngine->CheckIdleFormatter();

    Point aDocPos = GetDocPos( rPosPixel );

    TextPaM aPaM = mpTextEngine->GetPaM(aDocPos);

    // aTmpNewSel: Diff between old and new; not the new selection
    TextSelection aTmpNewSel(maSelection.GetEnd(), aPaM);
    TextSelection aNewSel(maSelection);
    aNewSel.GetEnd() = aPaM;

    if (!mpSelEngine->HasAnchor())
    {
        if (maSelection.GetStart() != aPaM)
            mpTextEngine->CursorMoved(maSelection.GetStart().GetPara());
        aNewSel.GetStart() = aPaM;
        ImpSetSelection( aNewSel );
    }
    else
    {
        ImpSetSelection( aNewSel );
        ShowSelection( aTmpNewSel );
    }

    bool bForceCursor = !mpDDInfo; // && !mbInSelection
    ImpShowCursor(mbAutoScroll, bForceCursor, false);
}

bool TextView::IsSelectionAtPoint( const Point& rPosPixel )
{
    Point aDocPos = GetDocPos( rPosPixel );
    TextPaM aPaM = mpTextEngine->GetPaM(aDocPos);
    // BeginDrag is only called, however, if IsSelectionAtPoint()
    // Problem: IsSelectionAtPoint is not called by Command()
    // if before MBDown returned false.
    return IsInSelection( aPaM );
}

bool TextView::IsInSelection( const TextPaM& rPaM ) const
{
    TextSelection aSel = maSelection;
    aSel.Justify();

    const sal_uInt32 nStartNode = aSel.GetStart().GetPara();
    const sal_uInt32 nEndNode = aSel.GetEnd().GetPara();
    const sal_uInt32 nCurNode = rPaM.GetPara();

    if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) )
        return true;

    if ( nStartNode == nEndNode )
    {
        if ( nCurNode == nStartNode )
            if ( ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
                return true;
    }
    else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) )
        return true;
    else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) )
        return true;

    return false;
}

void TextView::ImpHideDDCursor()
{
    if (mpDDInfo && mpDDInfo->mbVisCursor)
    {
        mpDDInfo->maCursor.Hide();
        mpDDInfo->mbVisCursor = false;
    }
}

void TextView::ImpShowDDCursor()
{
    if (!mpDDInfo->mbVisCursor)
    {
        tools::Rectangle aCursor = mpTextEngine->PaMtoEditCursor(mpDDInfo->maDropPos, true);
        aCursor.AdjustRight( 1 );
        aCursor.SetPos( GetWindowPos( aCursor.TopLeft() ) );

        mpDDInfo->maCursor.SetWindow(mpWindow);
        mpDDInfo->maCursor.SetPos(aCursor.TopLeft());
        mpDDInfo->maCursor.SetSize(aCursor.GetSize());
        mpDDInfo->maCursor.Show();
        mpDDInfo->mbVisCursor = true;
    }
}

void TextView::SetPaintSelection( bool bPaint )
{
    if (bPaint != mbPaintSelection)
    {
        mbPaintSelection = bPaint;
        ShowSelection(maSelection);
    }
}

void TextView::Read( SvStream& rInput )
{
    mpTextEngine->Read(rInput, &maSelection);
    ShowCursor();
}

bool TextView::ImplTruncateNewText( OUString& rNewText ) const
{
    bool bTruncated = false;

    const sal_Int32 nMaxLen = mpTextEngine->GetMaxTextLen();
    // 0 means unlimited
    if( nMaxLen != 0 )
    {
        const sal_Int32 nCurLen = mpTextEngine->GetTextLen();

        const sal_Int32 nNewLen = rNewText.getLength();
        if ( nCurLen + nNewLen > nMaxLen )
        {
            // see how much text will be replaced
            const sal_Int32 nSelLen = mpTextEngine->GetTextLen(maSelection);
            if ( nCurLen + nNewLen - nSelLen > nMaxLen )
            {
                const sal_Int32 nTruncatedLen = nMaxLen - (nCurLen - nSelLen);
                rNewText = rNewText.copy( 0, nTruncatedLen );
                bTruncated = true;
            }
        }
    }
    return bTruncated;
}

bool TextView::ImplCheckTextLen( std::u16string_view rNewText ) const
{
    bool bOK = true;
    if (mpTextEngine->GetMaxTextLen())
    {
        sal_Int32 n = mpTextEngine->GetTextLen() + rNewText.size();
        if (n > mpTextEngine->GetMaxTextLen())
        {
            // calculate how much text is being deleted
            n -= mpTextEngine->GetTextLen(maSelection);
            if (n > mpTextEngine->GetMaxTextLen())
                bOK = false;
        }
    }
    return bOK;
}

void TextView::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
{
    if (!mbClickedInSelection)
        return;

    SolarMutexGuard aVclGuard;

    SAL_WARN_IF(!maSelection.HasRange(), "vcl",
                "TextView::dragGestureRecognized: mbClickedInSelection, but no selection?");

    mpDDInfo.reset(new TextDDInfo);
    mpDDInfo->mbStarterOfDD = true;

    rtl::Reference<TETextDataObject> pDataObj = new TETextDataObject( GetSelected() );

    mpCursor->Hide();

    sal_Int8 nActions = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
    if ( !IsReadOnly() )
        nActions |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
    rDGE.DragSource->startDrag(rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mxDnDListener);
}

void TextView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& )
{
    ImpHideDDCursor();
    mpDDInfo.reset();
}

void TextView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
{
    SolarMutexGuard aVclGuard;

    if (!mbReadOnly && mpDDInfo)
    {
        ImpHideDDCursor();

        // Data for deleting after DROP_MOVE:
        TextSelection aPrevSel(maSelection);
        aPrevSel.Justify();
        const sal_uInt32 nPrevParaCount = mpTextEngine->GetParagraphCount();
        const sal_Int32 nPrevStartParaLen = mpTextEngine->GetTextLen(aPrevSel.GetStart().GetPara());

        bool bStarterOfDD = false;
        for (sal_uInt16 nView = mpTextEngine->GetViewCount(); nView && !bStarterOfDD; )
            bStarterOfDD = mpTextEngine->GetView( --nView )->mpDDInfo && mpTextEngine->GetView( nView )->mpDDInfo->mbStarterOfDD;

        HideSelection();
        ImpSetSelection(mpDDInfo->maDropPos);

        mpTextEngine->UndoActionStart();

        OUString aText;
        css::uno::Reference< css::datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
        if ( xDataObj.is() )
        {
            css::datatransfer::DataFlavor aFlavor;
            SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
            if ( xDataObj->isDataFlavorSupported( aFlavor ) )
            {
                css::uno::Any aData = xDataObj->getTransferData( aFlavor );
                OUString aOUString;
                aData >>= aOUString;
                aText = convertLineEnd(aOUString, LINEEND_LF);
            }
        }

        if ( !aText.isEmpty() && ( aText[ aText.getLength()-1 ] == LINE_SEP ) )
            aText = aText.copy(0, aText.getLength()-1);

        if ( ImplCheckTextLen( aText ) )
            ImpSetSelection(mpTextEngine->ImpInsertText(mpDDInfo->maDropPos, aText));

        if ( aPrevSel.HasRange() &&
                (( rDTDE.DropAction & css::datatransfer::dnd::DNDConstants::ACTION_MOVE ) || !bStarterOfDD) )
        {
            // adjust selection if necessary
            if ((mpDDInfo->maDropPos.GetPara() < aPrevSel.GetStart().GetPara()) ||
                 ((mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara())
                        && (mpDDInfo->maDropPos.GetIndex() < aPrevSel.GetStart().GetIndex())))
            {
                const sal_uInt32 nNewParasBeforeSelection
                    = mpTextEngine->GetParagraphCount() - nPrevParaCount;

                aPrevSel.GetStart().GetPara() += nNewParasBeforeSelection;
                aPrevSel.GetEnd().GetPara() += nNewParasBeforeSelection;

                if (mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara())
                {
                    const sal_Int32 nNewChars
                        = mpTextEngine->GetTextLen(aPrevSel.GetStart().GetPara()) - nPrevStartParaLen;

                    aPrevSel.GetStart().GetIndex() += nNewChars;
                    if ( aPrevSel.GetStart().GetPara() == aPrevSel.GetEnd().GetPara() )
                        aPrevSel.GetEnd().GetIndex() += nNewChars;
                }
            }
            else
            {
                // adjust current selection
                TextPaM aPaM = maSelection.GetStart();
                aPaM.GetPara() -= ( aPrevSel.GetEnd().GetPara() - aPrevSel.GetStart().GetPara() );
                if (aPrevSel.GetEnd().GetPara() == mpDDInfo->maDropPos.GetPara())
                {
                    aPaM.GetIndex() -= aPrevSel.GetEnd().GetIndex();
                    if (aPrevSel.GetStart().GetPara() == mpDDInfo->maDropPos.GetPara())
                        aPaM.GetIndex() += aPrevSel.GetStart().GetIndex();
                }
                ImpSetSelection( aPaM );

            }
            mpTextEngine->ImpDeleteText(aPrevSel);
        }

        mpTextEngine->UndoActionEnd();

        mpDDInfo.reset();

        mpTextEngine->FormatAndUpdate(this);

        mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified));
    }
    rDTDE.Context->dropComplete( false/*bChanges*/ );
}

void TextView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& )
{
}

void TextView::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
{
    SolarMutexGuard aVclGuard;
    ImpHideDDCursor();
}

void TextView::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
{
    SolarMutexGuard aVclGuard;

    if (!mpDDInfo)
        mpDDInfo.reset(new TextDDInfo);

    TextPaM aPrevDropPos = mpDDInfo->maDropPos;
    Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
    Point aDocPos = GetDocPos( aMousePos );
    mpDDInfo->maDropPos = mpTextEngine->GetPaM(aDocPos);

    // Don't drop in selection or in read only engine
    if (IsReadOnly() || IsInSelection(mpDDInfo->maDropPos))
    {
        ImpHideDDCursor();
        rDTDE.Context->rejectDrag();
    }
    else
    {
        // delete old Cursor
        if (!mpDDInfo->mbVisCursor || (aPrevDropPos != mpDDInfo->maDropPos))
        {
            ImpHideDDCursor();
            ImpShowDDCursor();
        }
        rDTDE.Context->acceptDrag( rDTDE.DropAction );
    }
}

Point TextView::ImpGetOutputStartPos( const Point& rStartDocPos ) const
{
    Point aStartPos( -rStartDocPos.X(), -rStartDocPos.Y() );
    if (mpTextEngine->IsRightToLeft())
    {
        Size aSz = mpWindow->GetOutputSizePixel();
        aStartPos.setX( rStartDocPos.X() + aSz.Width() - 1 ); // -1: Start is 0
    }
    return aStartPos;
}

Point TextView::GetDocPos( const Point& rWindowPos ) const
{
    // Window Position => Document Position

    Point aPoint;

    aPoint.setY(rWindowPos.Y() + maStartDocPos.Y());

    if (!mpTextEngine->IsRightToLeft())
    {
        aPoint.setX(rWindowPos.X() + maStartDocPos.X());
    }
    else
    {
        Size aSz = mpWindow->GetOutputSizePixel();
        aPoint.setX((aSz.Width() - 1) - rWindowPos.X() + maStartDocPos.X());
    }

    return aPoint;
}

Point TextView::GetWindowPos( const Point& rDocPos ) const
{
    // Document Position => Window Position

    Point aPoint;

    aPoint.setY(rDocPos.Y() - maStartDocPos.Y());

    if (!mpTextEngine->IsRightToLeft())
    {
        aPoint.setX(rDocPos.X() - maStartDocPos.X());
    }
    else
    {
        Size aSz = mpWindow->GetOutputSizePixel();
        aPoint.setX((aSz.Width() - 1) - (rDocPos.X() - maStartDocPos.X()));
    }

    return aPoint;
}

sal_Int32 TextView::GetLineNumberOfCursorInSelection() const
{
 // PROGRESS
    sal_Int32 nLineNo = -1;
    if (mbCursorEnabled)
    {
        TextPaM aPaM = GetSelection().GetEnd();
        TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara());
        nLineNo = pPPortion->GetLineNumber( aPaM.GetIndex(), false );
            //TODO: std::vector<TextLine>::size_type -> sal_Int32!
        if (mbCursorAtEndOfLine)
            --nLineNo;
    }
    return nLineNo;
}

// (+) class TextSelFunctionSet

TextSelFunctionSet::TextSelFunctionSet( TextView* pView )
{
    mpView = pView;
}

void TextSelFunctionSet::BeginDrag()
{
}

void TextSelFunctionSet::CreateAnchor()
{
//  TextSelection aSel( mpView->GetSelection() );
//  aSel.GetStart() = aSel.GetEnd();
//  mpView->SetSelection( aSel );

    // may not be followed by ShowCursor
    mpView->HideSelection();
    mpView->ImpSetSelection(mpView->maSelection.GetEnd());
}

void TextSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool )
{
    mpView->SetCursorAtPoint( rPointPixel );
}

bool TextSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel )
{
    return mpView->IsSelectionAtPoint( rPointPixel );
}

void TextSelFunctionSet::DeselectAll()
{
    CreateAnchor();
}

void TextSelFunctionSet::DeselectAtPoint( const Point& )
{
    // only for multiple selection
}

void TextSelFunctionSet::DestroyAnchor()
{
    // only for multiple selection
}
TextEngine* TextView::GetTextEngine() const { return mpTextEngine; }

vcl::Window* TextView::GetWindow() const { return mpWindow; }

void TextView::EnableCursor(bool bEnable) { mbCursorEnabled = bEnable; }

bool TextView::IsCursorEnabled() const { return mbCursorEnabled; }

void TextView::SetStartDocPos(const Point& rPos) { maStartDocPos = rPos; }

const Point& TextView::GetStartDocPos() const { return maStartDocPos; }

void TextView::SetAutoIndentMode(bool bAutoIndent) { mbAutoIndent = bAutoIndent; }

bool TextView::IsReadOnly() const { return mbReadOnly; }

void TextView::SetAutoScroll(bool bAutoScroll) { mbAutoScroll = bAutoScroll; }

bool TextView::IsAutoScroll() const { return mbAutoScroll; }

bool TextView::HasSelection() const { return maSelection.HasRange(); }

bool TextView::IsInsertMode() const { return mbInsertMode; }

void TextView::MatchGroup()
{
    TextSelection aTmpSel( GetSelection() );
    aTmpSel.Justify();
    if ( ( aTmpSel.GetStart().GetPara() != aTmpSel.GetEnd().GetPara() ) ||
         ( ( aTmpSel.GetEnd().GetIndex() - aTmpSel.GetStart().GetIndex() ) > 1 ) )
    {
        return;
    }

    TextSelection aMatchSel = static_cast<ExtTextEngine*>(GetTextEngine())->MatchGroup( aTmpSel.GetStart() );
    if ( aMatchSel.HasRange() )
        SetSelection( aMatchSel );
}

void TextView::CenterPaM( const TextPaM& rPaM )
{
    // Get textview size and the corresponding y-coordinates
    Size aOutSz = mpWindow->GetOutputSizePixel();
    tools::Long nVisStartY = maStartDocPos.Y();
    tools::Long nVisEndY = maStartDocPos.Y() + aOutSz.Height();

    // Retrieve the coordinates of the PaM
    tools::Rectangle aRect = mpTextEngine->PaMtoEditCursor(rPaM);

    // Recalculate the offset of the center y-coordinates and scroll
    Scroll(0, (nVisStartY + nVisEndY) / 2 - aRect.TopLeft().getY());
}

bool TextView::Search( const i18nutil::SearchOptions2& rSearchOptions, bool bForward )
{
    bool bFound = false;
    TextSelection aSel( GetSelection() );
    if ( static_cast<ExtTextEngine*>(GetTextEngine())->Search( aSel, rSearchOptions, bForward ) )
    {
        bFound = true;
        // First add the beginning of the word to the selection,
        // so that the whole word is in the visible region.
        SetSelection( aSel.GetStart() );
        ShowCursor( true, false );
    }
    else
    {
        aSel = GetSelection().GetEnd();
    }

    SetSelection( aSel );
    // tdf#49482: Move the start of the selection to the center of the textview
    if (bFound)
    {
        CenterPaM( aSel.GetStart() );
    }
    ShowCursor();

    return bFound;
}

sal_uInt16 TextView::Replace( const i18nutil::SearchOptions2& rSearchOptions, bool bAll, bool bForward )
{
    sal_uInt16 nFound = 0;

    if ( !bAll )
    {
        if ( GetSelection().HasRange() )
        {
            InsertText( rSearchOptions.replaceString );
            nFound = 1;
            Search( rSearchOptions, bForward ); // right away to the next
        }
        else
        {
            if( Search( rSearchOptions, bForward ) )
                nFound = 1;
        }
    }
    else
    {
        // the writer replaces all, from beginning to end

        ExtTextEngine* pTextEngine = static_cast<ExtTextEngine*>(GetTextEngine());

        // HideSelection();
        TextSelection aSel;

        bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & css::util::SearchFlags::REG_NOT_BEGINOFLINE) );
        if ( bSearchInSelection )
        {
            aSel = GetSelection();
            aSel.Justify();
        }

        TextSelection aSearchSel( aSel );

        bool bFound = pTextEngine->Search( aSel, rSearchOptions );
        if ( bFound )
            pTextEngine->UndoActionStart();
        while ( bFound )
        {
            nFound++;

            TextPaM aNewStart = pTextEngine->ImpInsertText( aSel, rSearchOptions.replaceString );
            // tdf#64690 - extend selection to include inserted text portions
            if ( aSel.GetEnd().GetPara() == aSearchSel.GetEnd().GetPara() )
            {
                aSearchSel.GetEnd().GetIndex() += rSearchOptions.replaceString.getLength() - 1;
            }
            aSel = aSearchSel;
            aSel.GetStart() = aNewStart;
            bFound = pTextEngine->Search( aSel, rSearchOptions );
        }
        if ( nFound )
        {
            SetSelection( aSel.GetStart() );
            pTextEngine->FormatAndUpdate( this );
            pTextEngine->UndoActionEnd();
        }
    }
    return nFound;
}

bool TextView::ImpIndentBlock( bool bRight )
{
    bool bDone = false;

    TextSelection aSel = GetSelection();
    aSel.Justify();

    HideSelection();
    GetTextEngine()->UndoActionStart();

    const sal_uInt32 nStartPara = aSel.GetStart().GetPara();
    sal_uInt32 nEndPara = aSel.GetEnd().GetPara();
    if ( aSel.HasRange() && !aSel.GetEnd().GetIndex() )
    {
        nEndPara--; // do not indent
    }

    for ( sal_uInt32 nPara = nStartPara; nPara <= nEndPara; ++nPara )
    {
        if ( bRight )
        {
            // add tabs
            GetTextEngine()->ImpInsertText( TextPaM( nPara, 0 ), '\t' );
            bDone = true;
        }
        else
        {
            // remove Tabs/Blanks
            OUString aText = GetTextEngine()->GetText( nPara );
            if ( !aText.isEmpty() && (
                    ( aText[ 0 ] == '\t' ) ||
                    ( aText[ 0 ] == ' ' ) ) )
            {
                GetTextEngine()->ImpDeleteText( TextSelection( TextPaM( nPara, 0 ), TextPaM( nPara, 1 ) ) );
                bDone = true;
            }
        }
    }

    GetTextEngine()->UndoActionEnd();

    bool bRange = aSel.HasRange();
    if ( bRight )
    {
        ++aSel.GetStart().GetIndex();
        if ( bRange && ( aSel.GetEnd().GetPara() == nEndPara ) )
            ++aSel.GetEnd().GetIndex();
    }
    else
    {
        if ( aSel.GetStart().GetIndex() )
            --aSel.GetStart().GetIndex();
        if ( bRange && aSel.GetEnd().GetIndex() )
            --aSel.GetEnd().GetIndex();
    }

    ImpSetSelection( aSel );
    GetTextEngine()->FormatAndUpdate( this );

    return bDone;
}

bool TextView::IndentBlock()
{
    return ImpIndentBlock( true );
}

bool TextView::UnindentBlock()
{
    return ImpIndentBlock( false );
}

void TextView::ToggleComment()
{
    /* To determines whether to add or remove comment markers, the rule is:
     * - If any of the lines in the selection does not start with a comment character "'"
     *   or "REM" then the selection is commented
     * - Otherwise, the selection is uncommented (i.e. if all of the lines start with a
     *   comment marker "'" or "REM")
     * - Empty lines, or lines with only blank spaces or tabs are ignored
     */

    TextEngine* pEngine = GetTextEngine();
    TextSelection aSel = GetSelection();
    sal_uInt32 nStartPara = aSel.GetStart().GetPara();
    sal_uInt32 nEndPara = aSel.GetEnd().GetPara();

    // True = Comment character will be added; False = Comment marker will be removed
    bool bAddCommentChar = false;

    // Indicates whether any change has been made
    bool bChanged = false;

    // Indicates whether the selection is downwards (normal) or upwards (reversed)
    bool bSelReversed = false;

    if (nEndPara < nStartPara)
    {
        std::swap(nStartPara, nEndPara);
        bSelReversed = true;
    }

    for (sal_uInt32 n = nStartPara; n <= nEndPara; n++)
    {
        OUString sText = pEngine->GetText(n).trim();

        // Empty lines or lines with only blank spaces and tabs are ignored
        if (sText.isEmpty())
            continue;

        if (!sText.startsWith("'") && !sText.startsWithIgnoreAsciiCase("REM"))
        {
            bAddCommentChar = true;
            break;
        }

        // Notice that a REM comment is only actually a comment if:
        // a) There is no subsequent character or
        // b) The subsequent character is a blank space or a tab
        std::u16string_view sRest;
        if (sText.startsWithIgnoreAsciiCase("REM", &sRest))
        {
            if (sRest.size() > 0 && !o3tl::starts_with(sRest, u" ") && !o3tl::starts_with(sRest, u"\t"))
            {
                bAddCommentChar = true;
                break;
            }
        }
    }

    if (bAddCommentChar)
    {
        // For each line, determine the first position where there is a character that is not
        // a blank space or a tab; the comment marker will be the smallest such position
        size_t nCommentPos = std::string::npos;

        for (sal_uInt32 n = nStartPara; n <= nEndPara; n++)
        {
            OUString sText = pEngine->GetText(n);
            std::u16string_view sLine(sText);
            sal_uInt32 nCharPos = sLine.find_first_not_of(u" \t");

            // Update the position where to place the comment marker
            if (nCharPos < nCommentPos)
                nCommentPos = nCharPos;

            // If the comment position is zero, then there's no more need to keep searching
            if (nCommentPos == 0)
                break;
        }

        // Insert the comment marker in all lines (except empty lines)
        for (sal_uInt32 n = nStartPara; n <= nEndPara; n++)
        {
            OUString sText = pEngine->GetText(n);
            std::u16string_view sLine(sText);
            if (o3tl::trim(sLine).length() > 0)
            {
                pEngine->ImpInsertText(TextPaM(n, nCommentPos), u"' "_ustr);
                bChanged = true;
            }
        }
    }
    else
    {
        // For each line, find the first comment marker and remove it
        for (sal_uInt32 nPara = nStartPara; nPara <= nEndPara; nPara++)
        {
            OUString sText = pEngine->GetText(nPara);
            if (!sText.isEmpty())
            {
                // Determine the position of the comment marker and check whether it's
                // a single quote "'" or a "REM" comment
                sal_Int32 nQuotePos = sText.indexOf("'");
                sal_Int32 nRemPos = sText.toAsciiUpperCase().indexOf("REM");

                // An empty line or a line with only blank spaces or tabs needs to be skipped
                if (nQuotePos == -1 && nRemPos == -1)
                    continue;

                // nRemPos only refers to a comment if the subsequent character is a blank space or tab
                const sal_Int32 nRemSub = nRemPos + 3;
                if (nRemPos != -1 && nRemPos < sText.getLength() - 3 &&
                    sText.indexOf(" ", nRemSub) != nRemSub &&
                    sText.indexOf("\t", nRemSub) != nRemSub)
                {
                    nRemPos = -1;
                }

                // True = comment uses single quote; False = comment uses REM
                bool bQuoteComment = true;

                // Start and end positions to be removed
                sal_Int32 nStartPos = nQuotePos;
                sal_Int32 nEndPos = nStartPos + 1;

                if (nQuotePos == -1)
                    bQuoteComment = false;
                else if (nRemPos != -1 && nRemPos < nQuotePos)
                    bQuoteComment = false;

                if (!bQuoteComment)
                {
                    nStartPos = nRemPos;
                    nEndPos = nStartPos + 3;
                }

                // Check if the next character is a blank space or a tab
                if (sText.indexOf(" ", nEndPos) == nEndPos || sText.indexOf("\t", nEndPos) == nEndPos)
                    nEndPos++;

                // Remove the comment marker
                pEngine->ImpDeleteText(TextSelection(TextPaM(nPara, nStartPos), TextPaM(nPara, nEndPos)));
                bChanged = true;
            }
        }
    }

    // Update selection if there was a selection in the first place
    if (bChanged)
    {
        TextPaM aNewStart;
        if (!bSelReversed)
            aNewStart = TextPaM(nStartPara, std::min(aSel.GetStart().GetIndex(),
                                                     pEngine->GetText(nStartPara).getLength()));
        else
            aNewStart = TextPaM(nStartPara, std::min(aSel.GetEnd().GetIndex(),
                                                     pEngine->GetText(nEndPara).getLength()));

        if (HasSelection())
        {
            TextPaM aNewEnd;
            if (!bSelReversed)
                aNewEnd = TextPaM(nEndPara, pEngine->GetText(nEndPara).getLength());
            else
                aNewEnd = TextPaM(nEndPara, pEngine->GetText(nStartPara).getLength());

            TextSelection aNewSel(aNewStart, aNewEnd);
            ImpSetSelection(aNewSel);
        }
        else
        {
            TextSelection aNewSel(aNewStart, aNewStart);
            ImpSetSelection(aNewSel);
        }
    }
}


/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
