/* -*- 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 <utility>
#include <vcl/builder.hxx>
#include <vcl/event.hxx>
#include <vcl/cursor.hxx>
#include <vcl/menu.hxx>
#include <vcl/toolkit/edit.hxx>
#include <vcl/weld.hxx>
#include <vcl/specialchars.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/transfer.hxx>
#include <vcl/uitest/uiobject.hxx>
#include <vcl/ptrstyle.hxx>
 
#include <window.h>
#include <svdata.hxx>
#include <strings.hrc>
 
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/datatransfer/XTransferable.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboard.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/i18n/InputSequenceChecker.hpp>
#include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
 
#include <com/sun/star/uno/Any.hxx>
 
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
 
#include <sot/exchange.hxx>
#include <sot/formats.hxx>
#include <sal/macros.h>
#include <sal/log.hxx>
 
#include <i18nlangtag/languagetag.hxx>
#include <vcl/unohelp2.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <officecfg/Office/Common.hxx>
#include <tools/json_writer.hxx>
 
#include <algorithm>
#include <memory>
#include <string_view>
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
 
// - Redo
// - if Tracking-Cancel recreate DefaultSelection
 
static FncGetSpecialChars pImplFncGetSpecialChars = nullptr;
 
#define EDIT_ALIGN_LEFT             1
#define EDIT_ALIGN_CENTER           2
#define EDIT_ALIGN_RIGHT            3
 
#define EDIT_DEL_LEFT               1
#define EDIT_DEL_RIGHT              2
 
#define EDIT_DELMODE_SIMPLE         11
#define EDIT_DELMODE_RESTOFWORD     12
#define EDIT_DELMODE_RESTOFCONTENT  13
 
struct DDInfo
{
    vcl::Cursor     aCursor;
    Selection       aDndStartSel;
    sal_Int32       nDropPos;
    bool            bStarterOfDD;
    bool            bDroppedInMe;
    bool            bVisCursor;
    bool            bIsStringSupported;
 
    DDInfo()
    {
        aCursor.SetStyle( CURSOR_SHADOW );
        nDropPos = 0;
        bStarterOfDD = false;
        bDroppedInMe = false;
        bVisCursor = false;
        bIsStringSupported = false;
    }
};
 
struct Impl_IMEInfos
{
    OUString      aOldTextAfterStartPos;
    std::unique_ptr<ExtTextInputAttr[]>
                  pAttribs;
    sal_Int32     nPos;
    sal_Int32     nLen;
    bool          bCursor;
    bool          bWasCursorOverwrite;
 
    Impl_IMEInfos(sal_Int32 nPos, OUString aOldTextAfterStartPos);
 
    void        CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL);
    void        DestroyAttribs();
};
 
Impl_IMEInfos::Impl_IMEInfos(sal_Int32 nP, OUString _aOldTextAfterStartPos)
    : aOldTextAfterStartPos(std::move(_aOldTextAfterStartPos)),
    nPos(nP),
    nLen(0),
    bCursor(true),
    bWasCursorOverwrite(false)
{
}
 
void Impl_IMEInfos::CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL)
{
    nLen = nL;
    pAttribs.reset(new ExtTextInputAttr[ nL ]);
    memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) );
}
 
void Impl_IMEInfos::DestroyAttribs()
{
    pAttribs.reset();
    nLen = 0;
}
 
Edit::Edit( WindowType nType )
    : Control( nType )
{
    ImplInitEditData();
}
 
Edit::Edit( vcl::Window* pParent, WinBits nStyle )
    : Control( WindowType::EDIT )
{
    ImplInitEditData();
    ImplInit( pParent, nStyle );
}
 
void Edit::SetWidthInChars(sal_Int32 nWidthInChars)
{
    if (mnWidthInChars != nWidthInChars)
    {
        mnWidthInChars = nWidthInChars;
        queue_resize();
    }
}
 
void Edit::setMaxWidthChars(sal_Int32 nWidth)
{
    if (nWidth != mnMaxWidthChars)
    {
        mnMaxWidthChars = nWidth;
        queue_resize();
    }
}
 
bool Edit::set_property(const OUString &rKey, const OUString &rValue)
{
    if (rKey == "width-chars")
        SetWidthInChars(rValue.toInt32());
    else if (rKey == "max-width-chars")
        setMaxWidthChars(rValue.toInt32());
    else if (rKey == "max-length")
    {
        sal_Int32 nTextLen = rValue.toInt32();
        SetMaxTextLen(nTextLen == 0 ? EDIT_NOLIMIT : nTextLen);
    }
    else if (rKey == "editable")
    {
        SetReadOnly(!toBool(rValue));
    }
    else if (rKey == "overwrite-mode")
    {
        SetInsertMode(!toBool(rValue));
    }
    else if (rKey == "visibility")
    {
        mbPassword = false;
        if (!toBool(rValue))
            mbPassword = true;
    }
    else if (rKey == "placeholder-text")
        SetPlaceholderText(rValue);
    else if (rKey == "shadow-type")
    {
        if (GetStyle() & WB_BORDER)
            SetBorderStyle(rValue == "none" ? WindowBorderStyle::MONO : WindowBorderStyle::NORMAL);
    }
    else
        return Control::set_property(rKey, rValue);
    return true;
}
 
Edit::~Edit()
{
    disposeOnce();
}
 
void Edit::dispose()
{
    mpUIBuilder.reset();
    mpDDInfo.reset();
 
    vcl::Cursor* pCursor = GetCursor();
    if ( pCursor )
    {
        SetCursor( nullptr );
        delete pCursor;
    }
 
    mpIMEInfos.reset();
 
    if ( mxDnDListener.is() )
    {
        if ( GetDragGestureRecognizer().is() )
        {
            GetDragGestureRecognizer()->removeDragGestureListener( mxDnDListener );
        }
        if ( GetDropTarget().is() )
        {
            GetDropTarget()->removeDropTargetListener( mxDnDListener );
        }
 
        mxDnDListener->disposing( lang::EventObject() );  // #95154# #96585# Empty Source means it's the Client
        mxDnDListener.clear();
    }
 
    SetType(WindowType::WINDOW);
 
    mpSubEdit.disposeAndClear();
    Control::dispose();
}
 
void Edit::ImplInitEditData()
{
    mpSubEdit               = VclPtr<Edit>();
    mpFilterText            = nullptr;
    mnXOffset               = 0;
    mnAlign                 = EDIT_ALIGN_LEFT;
    mnMaxTextLen            = EDIT_NOLIMIT;
    mnWidthInChars          = -1;
    mnMaxWidthChars         = -1;
    mbInternModified        = false;
    mbReadOnly              = false;
    mbInsertMode            = true;
    mbClickedInSelection    = false;
    mbActivePopup           = false;
    mbIsSubEdit             = false;
    mbForceControlBackground = false;
    mbPassword              = false;
    mpDDInfo                = nullptr;
    mpIMEInfos              = nullptr;
    mcEchoChar              = 0;
 
    // no default mirroring for Edit controls
    // note: controls that use a subedit will revert this (SpinField, ComboBox)
    EnableRTL( false );
 
    mxDnDListener = new vcl::unohelper::DragAndDropWrapper( this );
}
 
bool Edit::ImplUseNativeBorder(vcl::RenderContext const & rRenderContext, WinBits nStyle) const
{
    bool bRet = rRenderContext.IsNativeControlSupported(ImplGetNativeControlType(),
                                                        ControlPart::HasBackgroundTexture)
                                 && ((nStyle & WB_BORDER) && !(nStyle & WB_NOBORDER));
    if (!bRet && mbIsSubEdit)
    {
        vcl::Window* pWindow = GetParent();
        nStyle = pWindow->GetStyle();
        bRet = pWindow->IsNativeControlSupported(ImplGetNativeControlType(),
                                                 ControlPart::HasBackgroundTexture)
               && ((nStyle & WB_BORDER) && !(nStyle & WB_NOBORDER));
    }
    return bRet;
}
 
void Edit::ImplInit(vcl::Window* pParent, WinBits nStyle)
{
    nStyle = ImplInitStyle(nStyle);
 
    if (!(nStyle & (WB_CENTER | WB_RIGHT)))
        nStyle |= WB_LEFT;
 
    Control::ImplInit(pParent, nStyle, nullptr);
 
    mbReadOnly = (nStyle & WB_READONLY) != 0;
 
    mnAlign = EDIT_ALIGN_LEFT;
 
    // hack: right align until keyinput and cursor travelling works
    if( IsRTLEnabled() )
        mnAlign = EDIT_ALIGN_RIGHT;
 
    if ( nStyle & WB_RIGHT )
        mnAlign = EDIT_ALIGN_RIGHT;
    else if ( nStyle & WB_CENTER )
        mnAlign = EDIT_ALIGN_CENTER;
 
    SetCursor( new vcl::Cursor );
 
    SetPointer( PointerStyle::Text );
    ApplySettings(*GetOutDev());
 
    uno::Reference< datatransfer::dnd::XDragGestureRecognizer > xDGR = GetDragGestureRecognizer();
    if ( xDGR.is() )
    {
        xDGR->addDragGestureListener( mxDnDListener );
        GetDropTarget()->addDropTargetListener( mxDnDListener );
        GetDropTarget()->setActive( true );
        GetDropTarget()->setDefaultActions( datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
    }
}
 
WinBits Edit::ImplInitStyle( WinBits nStyle )
{
    if ( !(nStyle & WB_NOTABSTOP) )
        nStyle |= WB_TABSTOP;
    if ( !(nStyle & WB_NOGROUP) )
        nStyle |= WB_GROUP;
 
    return nStyle;
}
 
bool Edit::IsCharInput( const KeyEvent& rKeyEvent )
{
    // In the future we must use new Unicode functions for this
    sal_Unicode cCharCode = rKeyEvent.GetCharCode();
    return ((cCharCode >= 32) && (cCharCode != 127) &&
            !rKeyEvent.GetKeyCode().IsMod3() &&
            !rKeyEvent.GetKeyCode().IsMod2() &&
            !rKeyEvent.GetKeyCode().IsMod1() );
}
 
void Edit::ApplySettings(vcl::RenderContext& rRenderContext)
{
    Control::ApplySettings(rRenderContext);
 
    const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
 
    const vcl::Font& aFont = rStyleSettings.GetFieldFont();
    ApplyControlFont(rRenderContext, aFont);
 
    ImplClearLayoutData();
 
    Color aTextColor = rStyleSettings.GetFieldTextColor();
    ApplyControlForeground(rRenderContext, aTextColor);
 
    if (IsControlBackground())
    {
        rRenderContext.SetBackground(GetControlBackground());
        rRenderContext.SetFillColor(GetControlBackground());
 
        if (ImplUseNativeBorder(rRenderContext, GetStyle()))
        {
            // indicates that no non-native drawing of background should take place
            mpWindowImpl->mnNativeBackground = ControlPart::Entire;
        }
    }
    else if (ImplUseNativeBorder(rRenderContext, GetStyle()))
    {
        // Transparent background
        rRenderContext.SetBackground();
        rRenderContext.SetFillColor();
    }
    else
    {
        rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
        rRenderContext.SetFillColor(rStyleSettings.GetFieldColor());
    }
}
 
tools::Long Edit::ImplGetExtraXOffset() const
{
    // MT 09/2002: nExtraOffsetX should become a member, instead of checking every time,
    // but I need an incompatible update for this...
    // #94095# Use extra offset only when edit has a border
    tools::Long nExtraOffset = 0;
    if( ( GetStyle() & WB_BORDER ) || ( mbIsSubEdit && ( GetParent()->GetStyle() & WB_BORDER ) ) )
        nExtraOffset = 2;
 
    return nExtraOffset;
}
 
tools::Long Edit::ImplGetExtraYOffset() const
{
    tools::Long nExtraOffset = 0;
    ControlType eCtrlType = ImplGetNativeControlType();
    if (eCtrlType != ControlType::EditboxNoBorder)
    {
        // add some space between text entry and border
        nExtraOffset = 2;
    }
    return nExtraOffset;
}
 
OUString Edit::ImplGetText() const
{
    if ( mcEchoChar || mbPassword )
    {
        sal_Unicode cEchoChar;
        if ( mcEchoChar )
            cEchoChar = mcEchoChar;
        else
            cEchoChar = u'\x2022';
        OUStringBuffer aText(maText.getLength());
        comphelper::string::padToLength(aText, maText.getLength(), cEchoChar);
        return aText.makeStringAndClear();
    }
    else
        return maText.toString();
}
 
void Edit::ImplInvalidateOrRepaint()
{
    if( IsPaintTransparent() )
    {
        Invalidate();
        // FIXME: this is currently only on macOS
        if( ImplGetSVData()->maNWFData.mbNoFocusRects )
            PaintImmediately();
    }
    else
        Invalidate();
}
 
tools::Long Edit::ImplGetTextYPosition() const
{
    if ( GetStyle() & WB_TOP )
        return ImplGetExtraXOffset();
    else if ( GetStyle() & WB_BOTTOM )
        return GetOutputSizePixel().Height() - GetTextHeight() - ImplGetExtraXOffset();
    return ( GetOutputSizePixel().Height() - GetTextHeight() ) / 2;
}
 
void Edit::ImplRepaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle)
{
    if (!IsReallyVisible())
        return;
 
    ApplySettings(rRenderContext);
 
    const OUString aText = ImplGetText();
    const sal_Int32 nLen = aText.getLength();
 
    KernArray aDX;
    if (nLen)
        GetOutDev()->GetCaretPositions(aText, aDX, 0, nLen);
 
    tools::Long nTH = GetTextHeight();
    Point aPos(mnXOffset, ImplGetTextYPosition());
 
    vcl::Cursor* pCursor = GetCursor();
    bool bVisCursor = pCursor && pCursor->IsVisible();
    if (pCursor)
        pCursor->Hide();
 
    ImplClearBackground(rRenderContext, rRectangle, 0, GetOutputSizePixel().Width()-1);
 
    bool bPaintPlaceholderText = aText.isEmpty() && !maPlaceholderText.isEmpty();
 
    const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
 
    if (!IsEnabled() || bPaintPlaceholderText)
        rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
 
    // Set background color of the normal text
    if (mbForceControlBackground && IsControlBackground())
    {
        // check if we need to set ControlBackground even in NWF case
        rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
        rRenderContext.SetLineColor();
        rRenderContext.SetFillColor(GetControlBackground());
        rRenderContext.DrawRect(tools::Rectangle(aPos, Size(GetOutputSizePixel().Width() - 2 * mnXOffset, GetOutputSizePixel().Height())));
        rRenderContext.Pop();
 
        rRenderContext.SetTextFillColor(GetControlBackground());
    }
    else if (IsPaintTransparent() || ImplUseNativeBorder(rRenderContext, GetStyle()))
        rRenderContext.SetTextFillColor();
    else
        rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
 
    ImplPaintBorder(rRenderContext);
 
    bool bDrawSelection = maSelection.Len() && (HasFocus() || (GetStyle() & WB_NOHIDESELECTION) || mbActivePopup);
 
    aPos.setX( mnXOffset + ImplGetExtraXOffset() );
    if (bPaintPlaceholderText)
    {
        rRenderContext.DrawText(aPos, maPlaceholderText);
    }
    else if (!bDrawSelection && !mpIMEInfos)
    {
        rRenderContext.DrawText(aPos, aText, 0, nLen);
    }
    else
    {
        // save graphics state
        rRenderContext.Push();
        // first calculate highlighted and non highlighted clip regions
        vcl::Region aHighlightClipRegion;
        vcl::Region aNormalClipRegion;
        Selection aTmpSel(maSelection);
        aTmpSel.Normalize();
        // selection is highlighted
        for(sal_Int32 i = 0; i < nLen; ++i)
        {
            tools::Rectangle aRect(aPos, Size(10, nTH));
            aRect.SetLeft( aDX[2 * i] + mnXOffset + ImplGetExtraXOffset() );
            aRect.SetRight( aDX[2 * i + 1] + mnXOffset + ImplGetExtraXOffset() );
            aRect.Normalize();
            bool bHighlight = false;
            if (i >= aTmpSel.Min() && i < aTmpSel.Max())
                bHighlight = true;
 
            if (mpIMEInfos && mpIMEInfos->pAttribs &&
                i >= mpIMEInfos->nPos && i < (mpIMEInfos->nPos+mpIMEInfos->nLen) &&
                (mpIMEInfos->pAttribs[i - mpIMEInfos->nPos] & ExtTextInputAttr::Highlight))
            {
                bHighlight = true;
            }
 
            if (bHighlight)
                aHighlightClipRegion.Union(aRect);
            else
                aNormalClipRegion.Union(aRect);
        }
        // draw normal text
        Color aNormalTextColor = rRenderContext.GetTextColor();
        rRenderContext.SetClipRegion(aNormalClipRegion);
 
        if (IsPaintTransparent())
            rRenderContext.SetTextFillColor();
        else
        {
            // Set background color when part of the text is selected
            if (ImplUseNativeBorder(rRenderContext, GetStyle()))
            {
                if( mbForceControlBackground && IsControlBackground() )
                    rRenderContext.SetTextFillColor(GetControlBackground());
                else
                    rRenderContext.SetTextFillColor();
            }
            else
            {
                rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
            }
        }
        rRenderContext.DrawText(aPos, aText, 0, nLen);
 
        // draw highlighted text
        rRenderContext.SetClipRegion(aHighlightClipRegion);
        rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
        rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
        rRenderContext.DrawText(aPos, aText, 0, nLen);
 
        // if IME info exists loop over portions and output different font attributes
        if (mpIMEInfos && mpIMEInfos->pAttribs)
        {
            for(int n = 0; n < 2; n++)
            {
                vcl::Region aRegion;
                if (n == 0)
                {
                    rRenderContext.SetTextColor(aNormalTextColor);
                    if (IsPaintTransparent())
                        rRenderContext.SetTextFillColor();
                    else
                        rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
                    aRegion = aNormalClipRegion;
                }
                else
                {
                    rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
                    rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
                    aRegion = aHighlightClipRegion;
                }
 
                for(int i = 0; i < mpIMEInfos->nLen; )
                {
                    ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[i];
                    vcl::Region aClip;
                    int nIndex = i;
                    while (nIndex < mpIMEInfos->nLen && mpIMEInfos->pAttribs[nIndex] == nAttr)  // #112631# check nIndex before using it
                    {
                        tools::Rectangle aRect( aPos, Size( 10, nTH ) );
                        aRect.SetLeft( aDX[2 * (nIndex + mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
                        aRect.SetRight( aDX[2 * (nIndex + mpIMEInfos->nPos) + 1] + mnXOffset + ImplGetExtraXOffset() );
                        aRect.Normalize();
                        aClip.Union(aRect);
                        nIndex++;
                    }
                    i = nIndex;
                    aClip.Intersect(aRegion);
                    if (!aClip.IsEmpty() && nAttr != ExtTextInputAttr::NONE)
                    {
                        vcl::Font aFont = rRenderContext.GetFont();
                        if (nAttr & ExtTextInputAttr::Underline)
                            aFont.SetUnderline(LINESTYLE_SINGLE);
                        else if (nAttr & ExtTextInputAttr::DoubleUnderline)
                            aFont.SetUnderline(LINESTYLE_DOUBLE);
                        else if (nAttr & ExtTextInputAttr::BoldUnderline)
                            aFont.SetUnderline( LINESTYLE_BOLD);
                        else if (nAttr & ExtTextInputAttr::DottedUnderline)
                            aFont.SetUnderline( LINESTYLE_DOTTED);
                        else if (nAttr & ExtTextInputAttr::DashDotUnderline)
                            aFont.SetUnderline( LINESTYLE_DASHDOT);
                        else if (nAttr & ExtTextInputAttr::GrayWaveline)
                        {
                            aFont.SetUnderline(LINESTYLE_WAVE);
                            rRenderContext.SetTextLineColor(COL_LIGHTGRAY);
                        }
                        rRenderContext.SetFont(aFont);
 
                        if (nAttr & ExtTextInputAttr::RedText)
                            rRenderContext.SetTextColor(COL_RED);
                        else if (nAttr & ExtTextInputAttr::HalfToneText)
                            rRenderContext.SetTextColor(COL_LIGHTGRAY);
 
                        rRenderContext.SetClipRegion(aClip);
                        rRenderContext.DrawText(aPos, aText, 0, nLen);
                    }
                }
            }
        }
 
        // restore graphics state
        rRenderContext.Pop();
    }
 
    if (bVisCursor && (!mpIMEInfos || mpIMEInfos->bCursor))
        pCursor->Show();
}
 
void Edit::ImplDelete( const Selection& rSelection, sal_uInt8 nDirection, sal_uInt8 nMode )
{
    const sal_Int32 nTextLen = ImplGetText().getLength();
 
    // deleting possible?
    if ( !rSelection.Len() &&
         (((rSelection.Min() == 0) && (nDirection == EDIT_DEL_LEFT)) ||
          ((rSelection.Max() == nTextLen) && (nDirection == EDIT_DEL_RIGHT))) )
        return;
 
    ImplClearLayoutData();
 
    Selection aSelection( rSelection );
    aSelection.Normalize();
 
    if ( !aSelection.Len() )
    {
        uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
        if ( nDirection == EDIT_DEL_LEFT )
        {
            if ( nMode == EDIT_DELMODE_RESTOFWORD )
            {
                const OUString sText = maText.toString();
                i18n::Boundary aBoundary = xBI->getWordBoundary( sText, aSelection.Min(),
                        GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
                auto startPos = aBoundary.startPos;
                if ( startPos == aSelection.Min() )
                {
                    aBoundary = xBI->previousWord( sText, aSelection.Min(),
                            GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
                    startPos = std::max(aBoundary.startPos, sal_Int32(0));
                }
                aSelection.Min() = startPos;
            }
            else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
               {
                aSelection.Min() = 0;
            }
            else
            {
                sal_Int32 nCount = 1;
                aSelection.Min() = xBI->previousCharacters( maText.toString(), aSelection.Min(),
                        GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
            }
        }
        else
        {
            if ( nMode == EDIT_DELMODE_RESTOFWORD )
            {
                i18n::Boundary aBoundary = xBI->nextWord( maText.toString(), aSelection.Max(),
                        GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
                aSelection.Max() = aBoundary.startPos;
            }
            else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
            {
                aSelection.Max() = nTextLen;
            }
            else
            {
                sal_Int32 nCount = 1;
                aSelection.Max() = xBI->nextCharacters( maText.toString(), aSelection.Max(),
                        GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
            }
        }
    }
 
    const auto nSelectionMin = aSelection.Min();
    maText.remove( nSelectionMin, aSelection.Len() );
    maSelection.Min() = nSelectionMin;
    maSelection.Max() = nSelectionMin;
    ImplAlignAndPaint();
    mbInternModified = true;
}
 
OUString Edit::ImplGetValidString( const OUString& rString )
{
    OUString aValidString = rString.replaceAll("\n", "").replaceAll("\r", "");
    aValidString = aValidString.replace('\t', ' ');
    return aValidString;
}
 
uno::Reference <i18n::XBreakIterator> const& Edit::ImplGetBreakIterator()
{
    if (!mxBreakIterator)
        mxBreakIterator = i18n::BreakIterator::create(::comphelper::getProcessComponentContext());
    return mxBreakIterator;
}
 
uno::Reference <i18n::XExtendedInputSequenceChecker> const& Edit::ImplGetInputSequenceChecker()
{
    if (!mxISC.is())
        mxISC = i18n::InputSequenceChecker::create(::comphelper::getProcessComponentContext());
    return mxISC;
}
 
void Edit::ShowTruncationWarning(weld::Widget* pParent)
{
    std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, VclMessageType::Warning,
                                              VclButtonsType::Ok, VclResId(SV_EDIT_WARNING_STR)));
    xBox->run();
}
 
bool Edit::ImplTruncateToMaxLen( OUString& rStr, sal_Int32 nSelectionLen ) const
{
    bool bWasTruncated = false;
    if (maText.getLength() - nSelectionLen > mnMaxTextLen - rStr.getLength())
    {
        sal_Int32 nErasePos = mnMaxTextLen - maText.getLength() + nSelectionLen;
        rStr = rStr.copy( 0, nErasePos );
        bWasTruncated = true;
    }
    return bWasTruncated;
}
 
void Edit::ImplInsertText( const OUString& rStr, const Selection* pNewSel, bool bIsUserInput )
{
    Selection aSelection( maSelection );
    aSelection.Normalize();
 
    OUString aNewText( ImplGetValidString( rStr ) );
 
    // as below, if there's no selection, but we're in overwrite mode and not beyond
    // the end of the existing text then that's like a selection of 1
    auto nSelectionLen = aSelection.Len();
    if (!nSelectionLen && !mbInsertMode && aSelection.Max() < maText.getLength())
        nSelectionLen = 1;
    ImplTruncateToMaxLen( aNewText, nSelectionLen );
 
    ImplClearLayoutData();
 
    if ( aSelection.Len() )
        maText.remove( aSelection.Min(), aSelection.Len() );
    else if (!mbInsertMode && aSelection.Max() < maText.getLength())
        maText.remove( aSelection.Max(), 1 );
 
    // take care of input-sequence-checking now
    if (bIsUserInput && !rStr.isEmpty())
    {
        SAL_WARN_IF( rStr.getLength() != 1, "vcl", "unexpected string length. User input is expected to provide 1 char only!" );
 
        // determine if input-sequence-checking should be applied or not
 
        uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
        bool bIsInputSequenceChecking = rStr.getLength() == 1 &&
                officecfg::Office::Common::I18N::CTL::CTLFont::get() &&
                officecfg::Office::Common::I18N::CTL::CTLSequenceChecking::get() &&
                aSelection.Min() > 0 && /* first char needs not to be checked */
                xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( rStr, 0 );
 
        if (bIsInputSequenceChecking)
        {
            uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = ImplGetInputSequenceChecker();
            if (xISC.is())
            {
                sal_Unicode cChar = rStr[0];
                sal_Int32 nTmpPos = aSelection.Min();
                sal_Int16 nCheckMode = officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingRestricted::get()?
                        i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
 
                // the text that needs to be checked is only the one
                // before the current cursor position
                const OUString aOldText( maText.subView(0, nTmpPos) );
                OUString aTmpText( aOldText );
                if (officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingTypeAndReplace::get())
                {
                    xISC->correctInputSequence( aTmpText, nTmpPos - 1, cChar, nCheckMode );
 
                    // find position of first character that has changed
                    sal_Int32 nOldLen = aOldText.getLength();
                    sal_Int32 nTmpLen = aTmpText.getLength();
                    const sal_Unicode *pOldTxt = aOldText.getStr();
                    const sal_Unicode *pTmpTxt = aTmpText.getStr();
                    sal_Int32 nChgPos = 0;
                    while ( nChgPos < nOldLen && nChgPos < nTmpLen &&
                            pOldTxt[nChgPos] == pTmpTxt[nChgPos] )
                        ++nChgPos;
 
                    const OUString aChgText( aTmpText.copy( nChgPos ) );
 
                    // remove text from first pos to be changed to current pos
                    maText.remove( nChgPos, nTmpPos - nChgPos );
 
                    if (!aChgText.isEmpty())
                    {
                        aNewText = aChgText;
                        aSelection.Min() = nChgPos; // position for new text to be inserted
                    }
                    else
                        aNewText.clear();
                }
                else
                {
                    // should the character be ignored (i.e. not get inserted) ?
                    if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, cChar, nCheckMode ))
                        aNewText.clear();
                }
            }
        }
 
        // at this point now we will insert the non-empty text 'normally' some lines below...
    }
 
    if ( !aNewText.isEmpty() )
        maText.insert( aSelection.Min(), aNewText );
 
    if ( !pNewSel )
    {
        maSelection.Min() = aSelection.Min() + aNewText.getLength();
        maSelection.Max() = maSelection.Min();
    }
    else
    {
        maSelection = *pNewSel;
        if ( maSelection.Min() > maText.getLength() )
            maSelection.Min() = maText.getLength();
        if ( maSelection.Max() > maText.getLength() )
            maSelection.Max() = maText.getLength();
    }
 
    ImplAlignAndPaint();
    mbInternModified = true;
}
 
void Edit::ImplSetText( const OUString& rText, const Selection* pNewSelection )
{
    // we delete text by "selecting" the old text completely then calling InsertText; this is flicker free
    if ( ( rText.getLength() > mnMaxTextLen ) ||
         ( std::u16string_view(rText) == std::u16string_view(maText)
           && (!pNewSelection || (*pNewSelection == maSelection)) ) )
        return;
 
    ImplClearLayoutData();
    maSelection.Min() = 0;
    maSelection.Max() = maText.getLength();
    if ( mnXOffset || HasPaintEvent() )
    {
        mnXOffset = 0;
        maText = ImplGetValidString( rText );
 
        // #i54929# recalculate mnXOffset before ImplSetSelection,
        // else cursor ends up in wrong position
        ImplAlign();
 
        if ( pNewSelection )
            ImplSetSelection( *pNewSelection, false );
 
        if ( mnXOffset && !pNewSelection )
            maSelection.Max() = 0;
 
        Invalidate();
    }
    else
        ImplInsertText( rText, pNewSelection );
 
    CallEventListeners( VclEventId::EditModify );
}
 
ControlType Edit::ImplGetNativeControlType() const
{
    ControlType nCtrl = ControlType::Generic;
    const vcl::Window* pControl = mbIsSubEdit ? GetParent() : this;
 
    switch (pControl->GetType())
    {
        case WindowType::COMBOBOX:
        case WindowType::PATTERNBOX:
        case WindowType::NUMERICBOX:
        case WindowType::METRICBOX:
        case WindowType::CURRENCYBOX:
        case WindowType::DATEBOX:
        case WindowType::TIMEBOX:
        case WindowType::LONGCURRENCYBOX:
            nCtrl = ControlType::Combobox;
            break;
 
        case WindowType::MULTILINEEDIT:
            if ( GetWindow( GetWindowType::Border ) != this )
                nCtrl = ControlType::MultilineEditbox;
            else
                nCtrl = ControlType::EditboxNoBorder;
            break;
 
        case WindowType::EDIT:
        case WindowType::PATTERNFIELD:
        case WindowType::METRICFIELD:
        case WindowType::CURRENCYFIELD:
        case WindowType::DATEFIELD:
        case WindowType::TIMEFIELD:
        case WindowType::SPINFIELD:
        case WindowType::FORMATTEDFIELD:
            if (pControl->GetStyle() & WB_SPIN)
                nCtrl = ControlType::Spinbox;
            else
            {
                if (GetWindow(GetWindowType::Border) != this)
                    nCtrl = ControlType::Editbox;
                else
                    nCtrl = ControlType::EditboxNoBorder;
            }
            break;
 
        default:
            nCtrl = ControlType::Editbox;
    }
    return nCtrl;
}
 
void Edit::ImplClearBackground(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle, tools::Long nXStart, tools::Long nXEnd )
{
    /*
    * note: at this point the cursor must be switched off already
    */
    tools::Rectangle aRect(Point(), GetOutputSizePixel());
    aRect.SetLeft( nXStart );
    aRect.SetRight( nXEnd );
 
    if( !(ImplUseNativeBorder(rRenderContext, GetStyle()) || IsPaintTransparent()))
        rRenderContext.Erase(aRect);
    else if (SupportsDoubleBuffering() && mbIsSubEdit)
    {
        // ImplPaintBorder() is a NOP, we have a native border, and this is a sub-edit of a control.
        // That means we have to draw the parent native widget to paint the edit area to clear our background.
        vcl::PaintBufferGuard g(ImplGetWindowImpl()->mpFrameData, GetParent());
        GetParent()->Paint(rRenderContext, rRectangle);
    }
}
 
void Edit::ImplPaintBorder(vcl::RenderContext const & rRenderContext)
{
    // this is not needed when double-buffering
    if (SupportsDoubleBuffering())
        return;
 
    if (!(ImplUseNativeBorder(rRenderContext, GetStyle()) || IsPaintTransparent()))
        return;
 
    // draw the inner part by painting the whole control using its border window
    vcl::Window* pBorder = GetWindow(GetWindowType::Border);
    if (pBorder == this)
    {
        // we have no border, use parent
        vcl::Window* pControl = mbIsSubEdit ? GetParent() : this;
        pBorder = pControl->GetWindow(GetWindowType::Border);
        if (pBorder == this)
            pBorder = GetParent();
    }
 
    if (!pBorder)
        return;
 
    // set proper clipping region to not overdraw the whole control
    vcl::Region aClipRgn = GetPaintRegion();
    if (!aClipRgn.IsNull())
    {
        // transform clipping region to border window's coordinate system
        if (IsRTLEnabled() != pBorder->IsRTLEnabled() && AllSettings::GetLayoutRTL())
        {
            // need to mirror in case border is not RTL but edit is (or vice versa)
 
            // mirror
            tools::Rectangle aBounds(aClipRgn.GetBoundRect());
            int xNew = GetOutputSizePixel().Width() - aBounds.GetWidth() - aBounds.Left();
            aClipRgn.Move(xNew - aBounds.Left(), 0);
 
            // move offset of border window
            Point aBorderOffs = pBorder->ScreenToOutputPixel(OutputToScreenPixel(Point()));
            aClipRgn.Move(aBorderOffs.X(), aBorderOffs.Y());
        }
        else
        {
            // normal case
            Point aBorderOffs = pBorder->ScreenToOutputPixel(OutputToScreenPixel(Point()));
            aClipRgn.Move(aBorderOffs.X(), aBorderOffs.Y());
        }
 
        vcl::Region oldRgn(pBorder->GetOutDev()->GetClipRegion());
        pBorder->GetOutDev()->SetClipRegion(aClipRgn);
 
        pBorder->Paint(*pBorder->GetOutDev(), tools::Rectangle());
 
        pBorder->GetOutDev()->SetClipRegion(oldRgn);
    }
    else
    {
        pBorder->Paint(*pBorder->GetOutDev(), tools::Rectangle());
    }
}
 
void Edit::ImplShowCursor( bool bOnlyIfVisible )
{
    if ( !IsUpdateMode() || ( bOnlyIfVisible && !IsReallyVisible() ) )
        return;
 
    vcl::Cursor* pCursor = GetCursor();
    OUString aText = ImplGetText();
 
    tools::Long nTextPos = 0;
 
    if( !aText.isEmpty() )
    {
        KernArray aDX;
        GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
 
        if( maSelection.Max() < aText.getLength() )
            nTextPos = aDX[ 2*maSelection.Max() ];
        else
            nTextPos = aDX[ 2*aText.getLength()-1 ];
    }
 
    tools::Long nCursorWidth = 0;
    if ( !mbInsertMode && !maSelection.Len() && (maSelection.Max() < aText.getLength()) )
        nCursorWidth = GetTextWidth(aText, maSelection.Max(), 1);
    tools::Long nCursorPosX = nTextPos + mnXOffset + ImplGetExtraXOffset();
 
    // cursor should land in visible area
    const Size aOutSize = GetOutputSizePixel();
    if ( (nCursorPosX < 0) || (nCursorPosX >= aOutSize.Width()) )
    {
        tools::Long nOldXOffset = mnXOffset;
 
        if ( nCursorPosX < 0 )
        {
            mnXOffset = - nTextPos;
            tools::Long nMaxX = 0;
            mnXOffset += aOutSize.Width() / 5;
            if ( mnXOffset > nMaxX )
                mnXOffset = nMaxX;
        }
        else
        {
            mnXOffset = (aOutSize.Width()-ImplGetExtraXOffset()) - nTextPos;
            // Something more?
            if ( (aOutSize.Width()-ImplGetExtraXOffset()) < nTextPos )
            {
                tools::Long nMaxNegX = (aOutSize.Width()-ImplGetExtraXOffset()) - GetTextWidth( aText );
                mnXOffset -= aOutSize.Width() / 5;
                if ( mnXOffset < nMaxNegX )  // both negative...
                    mnXOffset = nMaxNegX;
            }
        }
 
        nCursorPosX = nTextPos + mnXOffset + ImplGetExtraXOffset();
        if ( nCursorPosX == aOutSize.Width() )  // then invisible...
            nCursorPosX--;
 
        if ( mnXOffset != nOldXOffset )
            ImplInvalidateOrRepaint();
    }
 
    const tools::Long nTextHeight = GetTextHeight();
    const tools::Long nCursorPosY = ImplGetTextYPosition();
    if (pCursor)
    {
        pCursor->SetPos( Point( nCursorPosX, nCursorPosY ) );
        pCursor->SetSize( Size( nCursorWidth, nTextHeight ) );
        pCursor->Show();
    }
}
 
void Edit::ImplAlign()
{
    if (mnAlign == EDIT_ALIGN_LEFT && !mnXOffset)
    {
        // short circuit common case and avoid slow GetTextWidth() calc
        return;
    }
 
    tools::Long nTextWidth = GetTextWidth( ImplGetText() );
    tools::Long nOutWidth = GetOutputSizePixel().Width();
 
    if ( mnAlign == EDIT_ALIGN_LEFT )
    {
        if (nTextWidth < nOutWidth)
            mnXOffset = 0;
    }
    else if ( mnAlign == EDIT_ALIGN_RIGHT )
    {
        tools::Long nMinXOffset = nOutWidth - nTextWidth - 1 - ImplGetExtraXOffset();
        bool bRTL = IsRTLEnabled();
        if( mbIsSubEdit && GetParent() )
            bRTL = GetParent()->IsRTLEnabled();
        if( bRTL )
        {
            if( nTextWidth < nOutWidth )
                mnXOffset = nMinXOffset;
        }
        else
        {
            if( nTextWidth < nOutWidth )
                mnXOffset = nMinXOffset;
            else if ( mnXOffset < nMinXOffset )
                mnXOffset = nMinXOffset;
        }
    }
    else if( mnAlign == EDIT_ALIGN_CENTER )
    {
        // would be nicer with check while scrolling but then it's not centred in scrolled state
        mnXOffset = (nOutWidth - nTextWidth) / 2;
    }
}
 
void Edit::ImplAlignAndPaint()
{
    ImplAlign();
    ImplInvalidateOrRepaint();
    ImplShowCursor();
}
 
sal_Int32 Edit::ImplGetCharPos( const Point& rWindowPos ) const
{
    sal_Int32 nIndex = EDIT_NOLIMIT;
    OUString aText = ImplGetText();
 
    if (aText.isEmpty())
        return nIndex;
 
    KernArray aDX;
    GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
    tools::Long nX = rWindowPos.X() - mnXOffset - ImplGetExtraXOffset();
    for (sal_Int32 i = 0; i < aText.getLength(); aText.iterateCodePoints(&i))
    {
        if( (aDX[2*i] >= nX && aDX[2*i+1] <= nX) ||
            (aDX[2*i+1] >= nX && aDX[2*i] <= nX))
        {
            nIndex = i;
            if( aDX[2*i] < aDX[2*i+1] )
            {
                if( nX > (aDX[2*i]+aDX[2*i+1])/2 )
                    aText.iterateCodePoints(&nIndex);
            }
            else
            {
                if( nX < (aDX[2*i]+aDX[2*i+1])/2 )
                    aText.iterateCodePoints(&nIndex);
            }
            break;
        }
    }
    if( nIndex == EDIT_NOLIMIT )
    {
        nIndex = 0;
        sal_Int32 nFinalIndex = 0;
        tools::Long nDiff = std::abs( aDX[0]-nX );
        sal_Int32 i = 0;
        if (!aText.isEmpty())
        {
            aText.iterateCodePoints(&i);    //skip the first character
        }
        while (i < aText.getLength())
        {
            tools::Long nNewDiff = std::abs( aDX[2*i]-nX );
 
            if( nNewDiff < nDiff )
            {
                nIndex = i;
                nDiff = nNewDiff;
            }
 
            nFinalIndex = i;
 
            aText.iterateCodePoints(&i);
        }
        if (nIndex == nFinalIndex && std::abs( aDX[2*nIndex+1] - nX ) < nDiff)
            nIndex = EDIT_NOLIMIT;
    }
 
    return nIndex;
}
 
void Edit::ImplSetCursorPos( sal_Int32 nChar, bool bSelect )
{
    Selection aSelection( maSelection );
    aSelection.Max() = nChar;
    if ( !bSelect )
        aSelection.Min() = aSelection.Max();
    ImplSetSelection( aSelection );
}
 
void Edit::ImplCopyToSelectionClipboard()
{
    if ( GetSelection().Len() )
    {
        css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
        ImplCopy( aSelection );
    }
}
 
void Edit::ImplCopy( uno::Reference< datatransfer::clipboard::XClipboard > const & rxClipboard )
{
    vcl::unohelper::TextDataObject::CopyStringTo( GetSelected(), rxClipboard );
}
 
void Edit::ImplPaste( uno::Reference< datatransfer::clipboard::XClipboard > const & rxClipboard )
{
    if ( !rxClipboard.is() )
        return;
 
    uno::Reference< datatransfer::XTransferable > xDataObj;
 
    try
        {
            SolarMutexReleaser aReleaser;
            xDataObj = rxClipboard->getContents();
        }
    catch( const css::uno::Exception& )
        {
        }
 
    if ( !xDataObj.is() )
        return;
 
    datatransfer::DataFlavor aFlavor;
    SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
    try
    {
        uno::Any aData = xDataObj->getTransferData( aFlavor );
        OUString aText;
        aData >>= aText;
 
        // tdf#127588 - extend selection to the entire field or paste the text
        // from the clipboard to the current position if there is no selection
        if (mnMaxTextLen < EDIT_NOLIMIT && maSelection.Len() == 0)
        {
            const sal_Int32 aTextLen = aText.getLength();
            if (aTextLen == mnMaxTextLen)
            {
                maSelection.Min() = 0;
                maSelection.Max() = mnMaxTextLen;
            } else
                maSelection.Max() = std::min<sal_Int32>(maSelection.Min() + aTextLen, mnMaxTextLen);
        }
 
        Selection aSelection(maSelection);
        aSelection.Normalize();
        if (ImplTruncateToMaxLen(aText, aSelection.Len()))
            ShowTruncationWarning(GetFrameWeld());
 
        ReplaceSelected( aText );
    }
    catch( const css::uno::Exception& )
    {
    }
}
 
void Edit::MouseButtonDown( const MouseEvent& rMEvt )
{
    if ( mpSubEdit )
    {
        Control::MouseButtonDown( rMEvt );
        return;
    }
 
    sal_Int32 nCharPos = ImplGetCharPos( rMEvt.GetPosPixel() );
    Selection aSelection( maSelection );
    aSelection.Normalize();
 
    if ( rMEvt.GetClicks() < 4 )
    {
        mbClickedInSelection = false;
        if ( rMEvt.GetClicks() == 3 )
        {
            ImplSetSelection( Selection( 0, EDIT_NOLIMIT) );
            ImplCopyToSelectionClipboard();
 
        }
        else if ( rMEvt.GetClicks() == 2 )
        {
            uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
            i18n::Boundary aBoundary = xBI->getWordBoundary( maText.toString(), aSelection.Max(),
                     GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
            ImplSetSelection( Selection( aBoundary.startPos, aBoundary.endPos ) );
            ImplCopyToSelectionClipboard();
        }
        else if ( !rMEvt.IsShift() && HasFocus() && aSelection.Contains( nCharPos ) )
            mbClickedInSelection = true;
        else if ( rMEvt.IsLeft() )
            ImplSetCursorPos( nCharPos, rMEvt.IsShift() );
 
        if ( !mbClickedInSelection && rMEvt.IsLeft() && ( rMEvt.GetClicks() == 1 ) )
            StartTracking( StartTrackingFlags::ScrollRepeat );
    }
 
    GrabFocus();
}
 
void Edit::MouseButtonUp( const MouseEvent& rMEvt )
{
    if ( mbClickedInSelection && rMEvt.IsLeft() )
    {
        sal_Int32 nCharPos = ImplGetCharPos( rMEvt.GetPosPixel() );
        ImplSetCursorPos( nCharPos, false );
        mbClickedInSelection = false;
    }
    else if ( rMEvt.IsMiddle() && !mbReadOnly &&
              ( GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
    {
        css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
        ImplPaste( aSelection );
        Modify();
    }
}
 
void Edit::Tracking( const TrackingEvent& rTEvt )
{
    if ( rTEvt.IsTrackingEnded() )
    {
        if ( mbClickedInSelection )
        {
            sal_Int32 nCharPos = ImplGetCharPos( rTEvt.GetMouseEvent().GetPosPixel() );
            ImplSetCursorPos( nCharPos, false );
            mbClickedInSelection = false;
        }
        else if ( rTEvt.GetMouseEvent().IsLeft() )
        {
            ImplCopyToSelectionClipboard();
        }
    }
    else
    {
        if( !mbClickedInSelection )
        {
            sal_Int32 nCharPos = ImplGetCharPos( rTEvt.GetMouseEvent().GetPosPixel() );
            ImplSetCursorPos( nCharPos, true );
        }
    }
}
 
bool Edit::ImplHandleKeyEvent( const KeyEvent& rKEvt )
{
    bool        bDone = false;
    sal_uInt16      nCode = rKEvt.GetKeyCode().GetCode();
    KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction();
 
    mbInternModified = false;
 
    if ( eFunc != KeyFuncType::DONTKNOW )
    {
        switch ( eFunc )
        {
            case KeyFuncType::CUT:
            {
                if ( !mbReadOnly && maSelection.Len() && !mbPassword )
                {
                    Cut();
                    Modify();
                    bDone = true;
                }
            }
            break;
 
            case KeyFuncType::COPY:
            {
                if ( !mbPassword )
                {
                    Copy();
                    bDone = true;
                }
            }
            break;
 
            case KeyFuncType::PASTE:
            {
                if ( !mbReadOnly )
                {
                    Paste();
                    bDone = true;
                }
            }
            break;
 
            case KeyFuncType::UNDO:
            {
                if ( !mbReadOnly )
                {
                    Undo();
                    bDone = true;
                }
            }
            break;
 
            default:
                eFunc = KeyFuncType::DONTKNOW;
        }
    }
 
    if ( !bDone && rKEvt.GetKeyCode().IsMod1() && !rKEvt.GetKeyCode().IsMod2() )
    {
        if ( nCode == KEY_A )
        {
            ImplSetSelection( Selection( 0, maText.getLength() ) );
            bDone = true;
        }
        else if ( rKEvt.GetKeyCode().IsShift() && (nCode == KEY_S) )
        {
            if ( pImplFncGetSpecialChars )
            {
                Selection aSaveSel = GetSelection(); // if someone changes the selection in Get/LoseFocus, e.g. URL bar
                OUString aChars = pImplFncGetSpecialChars( GetFrameWeld(), GetFont() );
                SetSelection( aSaveSel );
                if ( !aChars.isEmpty() )
                {
                    ImplInsertText( aChars );
                    Modify();
                }
                bDone = true;
            }
        }
    }
 
    if ( eFunc == KeyFuncType::DONTKNOW && ! bDone )
    {
        switch ( nCode )
        {
            case css::awt::Key::SELECT_ALL:
            {
                ImplSetSelection( Selection( 0, maText.getLength() ) );
                bDone = true;
            }
            break;
 
            case KEY_LEFT:
            case KEY_RIGHT:
            case KEY_HOME:
            case KEY_END:
            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 ( !rKEvt.GetKeyCode().IsMod2() )
                {
                    ImplClearLayoutData();
                    uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
 
                    Selection aSel( maSelection );
                    bool bWord = rKEvt.GetKeyCode().IsMod1();
                    bool bSelect = rKEvt.GetKeyCode().IsShift();
                    bool bGoLeft = (nCode == KEY_LEFT);
                    bool bGoRight = (nCode == KEY_RIGHT);
                    bool bGoHome = (nCode == KEY_HOME);
                    bool bGoEnd = (nCode == KEY_END);
 
                    switch( nCode )
                    {
                    case css::awt::Key::MOVE_WORD_FORWARD:
                        bGoRight = bWord = true;break;
                    case css::awt::Key::SELECT_WORD_FORWARD:
                        bGoRight = bSelect = bWord = true;break;
                    case css::awt::Key::MOVE_WORD_BACKWARD:
                        bGoLeft = bWord = true;break;
                    case css::awt::Key::SELECT_WORD_BACKWARD:
                        bGoLeft = bSelect = bWord = true;break;
                    case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
                    case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
                    case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
                        bSelect = true;
                        [[fallthrough]];
                    case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
                    case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
                    case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
                        bGoHome = true;break;
                    case css::awt::Key::SELECT_TO_END_OF_LINE:
                    case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
                    case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
                        bSelect = true;
                        [[fallthrough]];
                    case css::awt::Key::MOVE_TO_END_OF_LINE:
                    case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
                    case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
                        bGoEnd = true;break;
                    default:
                        break;
                    }
 
                    // range is checked in ImplSetSelection ...
                    if ( bGoLeft && aSel.Max() )
                    {
                        if ( bWord )
                        {
                            const OUString sText = maText.toString();
                            i18n::Boundary aBoundary = xBI->getWordBoundary( sText, aSel.Max(),
                                    GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
                            if ( aBoundary.startPos == aSel.Max() )
                                aBoundary = xBI->previousWord( sText, aSel.Max(),
                                        GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
                            aSel.Max() = aBoundary.startPos;
                        }
                        else
                        {
                            sal_Int32 nCount = 1;
                            aSel.Max() = xBI->previousCharacters( maText.toString(), aSel.Max(),
                                    GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
                        }
                    }
                    else if ( bGoRight && ( aSel.Max() < maText.getLength() ) )
                    {
                        if ( bWord )
                        {
                            i18n::Boundary aBoundary = xBI->nextWord( maText.toString(), aSel.Max(),
                                    GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
                            aSel.Max() = aBoundary.startPos;
                        }
                        else
                        {
                            sal_Int32 nCount = 1;
                            aSel.Max() = xBI->nextCharacters( maText.toString(), aSel.Max(),
                                    GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
                        }
                    }
                    else if ( bGoHome )
                    {
                        aSel.Max() = 0;
                    }
                    else if ( bGoEnd )
                    {
                        aSel.Max() = EDIT_NOLIMIT;
                    }
 
                    if ( !bSelect )
                        aSel.Min() = aSel.Max();
 
                    if ( aSel != GetSelection() )
                    {
                        ImplSetSelection( aSel );
                        ImplCopyToSelectionClipboard();
                    }
 
                    if (bGoEnd && maAutocompleteHdl.IsSet() && !rKEvt.GetKeyCode().GetModifier())
                    {
                        if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
                        {
                            maAutocompleteHdl.Call(*this);
                        }
                    }
 
                    bDone = true;
                }
            }
            break;
 
            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:
            case KEY_BACKSPACE:
            case KEY_DELETE:
            {
                if ( !mbReadOnly && !rKEvt.GetKeyCode().IsMod2() )
                {
                    sal_uInt8 nDel = (nCode == KEY_DELETE) ? EDIT_DEL_RIGHT : EDIT_DEL_LEFT;
                    sal_uInt8 nMode = rKEvt.GetKeyCode().IsMod1() ? EDIT_DELMODE_RESTOFWORD : EDIT_DELMODE_SIMPLE;
                    if ( (nMode == EDIT_DELMODE_RESTOFWORD) && rKEvt.GetKeyCode().IsShift() )
                        nMode = EDIT_DELMODE_RESTOFCONTENT;
                    switch( nCode )
                    {
                    case css::awt::Key::DELETE_WORD_BACKWARD:
                        nDel = EDIT_DEL_LEFT;
                        nMode = EDIT_DELMODE_RESTOFWORD;
                        break;
                    case css::awt::Key::DELETE_WORD_FORWARD:
                        nDel = EDIT_DEL_RIGHT;
                        nMode = EDIT_DELMODE_RESTOFWORD;
                        break;
                    case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
                        nDel = EDIT_DEL_LEFT;
                        nMode = EDIT_DELMODE_RESTOFCONTENT;
                        break;
                    case css::awt::Key::DELETE_TO_END_OF_LINE:
                        nDel = EDIT_DEL_RIGHT;
                        nMode = EDIT_DELMODE_RESTOFCONTENT;
                        break;
                    default: break;
                    }
                    sal_Int32 nOldLen = maText.getLength();
                    ImplDelete( maSelection, nDel, nMode );
                    if ( maText.getLength() != nOldLen )
                        Modify();
                    bDone = true;
                }
            }
            break;
 
            case KEY_INSERT:
            {
                if ( !mpIMEInfos && !mbReadOnly && !rKEvt.GetKeyCode().IsMod2() )
                {
                    SetInsertMode( !mbInsertMode );
                    bDone = true;
                }
            }
            break;
 
            case KEY_RETURN:
                if (maActivateHdl.IsSet() && !rKEvt.GetKeyCode().GetModifier())
                    bDone = maActivateHdl.Call(*this);
            break;
 
            default:
            {
                if ( IsCharInput( rKEvt ) )
                {
                    bDone = true;   // read characters also when in ReadOnly
                    if ( !mbReadOnly )
                    {
                        ImplInsertText(OUString(rKEvt.GetCharCode()), nullptr, true);
                        if (maAutocompleteHdl.IsSet())
                        {
                            if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
                            {
                                maAutocompleteHdl.Call(*this);
                            }
                        }
                    }
                }
            }
        }
    }
 
    if ( mbInternModified )
        Modify();
 
    return bDone;
}
 
void Edit::KeyInput( const KeyEvent& rKEvt )
{
    if ( mpSubEdit || !ImplHandleKeyEvent( rKEvt ) )
        Control::KeyInput( rKEvt );
}
 
void Edit::FillLayoutData() const
{
    mxLayoutData.emplace();
    const_cast<Edit*>(this)->Invalidate();
}
 
void Edit::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle)
{
    if (!mpSubEdit)
        ImplRepaint(rRenderContext, rRectangle);
}
 
void Edit::Resize()
{
    if ( !mpSubEdit && IsReallyVisible() )
    {
        Control::Resize();
        // because of vertical centering...
        mnXOffset = 0;
        ImplAlign();
        Invalidate();
        ImplShowCursor();
    }
}
 
void Edit::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
{
    ApplySettings(*pDev);
 
    Point aPos = pDev->LogicToPixel( rPos );
    Size aSize = GetSizePixel();
    vcl::Font aFont = GetDrawPixelFont( pDev );
 
    pDev->Push();
    pDev->SetMapMode();
    pDev->SetFont( aFont );
    pDev->SetTextFillColor();
 
    // Border/Background
    pDev->SetLineColor();
    pDev->SetFillColor();
    bool bBorder = (GetStyle() & WB_BORDER);
    bool bBackground = IsControlBackground();
    if ( bBorder || bBackground )
    {
        tools::Rectangle aRect( aPos, aSize );
        if ( bBorder )
        {
            ImplDrawFrame( pDev, aRect );
        }
        if ( bBackground )
        {
            pDev->SetFillColor( GetControlBackground() );
            pDev->DrawRect( aRect );
        }
    }
 
    // Content
    if ( nFlags & SystemTextColorFlags::Mono )
        pDev->SetTextColor( COL_BLACK );
    else
    {
        if ( !IsEnabled() )
        {
            const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
            pDev->SetTextColor( rStyleSettings.GetDisableColor() );
        }
        else
        {
            pDev->SetTextColor( GetTextColor() );
        }
    }
 
    const tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
    const tools::Long nOffX = 3*nOnePixel;
    DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
    tools::Rectangle aTextRect( aPos, aSize );
 
    if ( GetStyle() & WB_CENTER )
        nTextStyle |= DrawTextFlags::Center;
    else if ( GetStyle() & WB_RIGHT )
        nTextStyle |= DrawTextFlags::Right;
    else
        nTextStyle |= DrawTextFlags::Left;
 
    aTextRect.AdjustLeft(nOffX );
    aTextRect.AdjustRight( -nOffX );
 
    OUString    aText = ImplGetText();
    tools::Long        nTextHeight = pDev->GetTextHeight();
    tools::Long        nTextWidth = pDev->GetTextWidth( aText );
    tools::Long        nOffY = (aSize.Height() - nTextHeight) / 2;
 
    // Clipping?
    if ( (nOffY < 0) ||
         ((nOffY+nTextHeight) > aSize.Height()) ||
         ((nOffX+nTextWidth) > aSize.Width()) )
    {
        tools::Rectangle aClip( aPos, aSize );
        if ( nTextHeight > aSize.Height() )
            aClip.AdjustBottom(nTextHeight-aSize.Height()+1 );  // prevent HP printers from 'optimizing'
        pDev->IntersectClipRegion( aClip );
    }
 
    pDev->DrawText( aTextRect, aText, nTextStyle );
    pDev->Pop();
 
    if ( GetSubEdit() )
    {
        Size aOrigSize(GetSubEdit()->GetSizePixel());
        GetSubEdit()->SetSizePixel(GetSizePixel());
        GetSubEdit()->Draw(pDev, rPos, nFlags);
        GetSubEdit()->SetSizePixel(aOrigSize);
    }
}
 
void Edit::ImplInvalidateOutermostBorder( vcl::Window* pWin )
{
    // allow control to show focused state
    vcl::Window *pInvalWin = pWin;
    for (;;)
    {
        vcl::Window* pBorder = pInvalWin->GetWindow( GetWindowType::Border );
        if (pBorder == pInvalWin || !pBorder ||
           pInvalWin->ImplGetFrame() != pBorder->ImplGetFrame() )
           break;
        pInvalWin = pBorder;
    }
 
    pInvalWin->Invalidate( InvalidateFlags::Children | InvalidateFlags::Update );
}
 
void Edit::GetFocus()
{
    Control::GetFocus();
 
    if ( mpSubEdit )
        mpSubEdit->ImplGrabFocus( GetGetFocusFlags() );
    else if ( !mbActivePopup )
    {
        maUndoText = maText.toString();
        SelectionOptions nSelOptions = GetSettings().GetStyleSettings().GetSelectionOptions();
        if ( !( GetStyle() & (WB_NOHIDESELECTION|WB_READONLY) )
                && ( GetGetFocusFlags() & (GetFocusFlags::Init|GetFocusFlags::Tab|GetFocusFlags::CURSOR|GetFocusFlags::Mnemonic) ) )
        {
            if ( nSelOptions & SelectionOptions::ShowFirst )
            {
                maSelection.Min() = maText.getLength();
                maSelection.Max() = 0;
            }
            else
            {
                maSelection.Min() = 0;
                maSelection.Max() = maText.getLength();
            }
            if ( mbIsSubEdit )
                static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
            else
                CallEventListeners( VclEventId::EditSelectionChanged );
        }
 
        ImplShowCursor();
 
        if (IsNativeWidgetEnabled() &&
            IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ))
        {
            ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
        }
        else if ( maSelection.Len() )
        {
            // paint the selection
            if ( !HasPaintEvent() )
                ImplInvalidateOrRepaint();
            else
                Invalidate();
        }
 
        SetInputContext( InputContext( GetFont(), !IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
    }
}
 
void Edit::LoseFocus()
{
    if ( !mpSubEdit )
    {
        if (IsNativeWidgetEnabled() &&
            IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
        {
            ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
        }
 
        if ( !mbActivePopup && !( GetStyle() & WB_NOHIDESELECTION ) && maSelection.Len() )
            ImplInvalidateOrRepaint();    // paint the selection
    }
 
    Control::LoseFocus();
}
 
bool Edit::PreNotify(NotifyEvent& rNEvt)
{
    if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE)
    {
        const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
        if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
        {
            // trigger redraw if mouse over state has changed
            if (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
            {
                if (IsNativeWidgetEnabled() &&
                    IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
                {
                    ImplInvalidateOutermostBorder(this);
                }
            }
        }
    }
 
    return Control::PreNotify(rNEvt);
}
 
void Edit::Command( const CommandEvent& rCEvt )
{
    if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
    {
        VclPtr<PopupMenu> pPopup = Edit::CreatePopupMenu();
 
        bool bEnableCut = true;
        bool bEnableCopy = true;
        bool bEnableDelete = true;
        bool bEnablePaste = true;
        bool bEnableSpecialChar = true;
 
        if ( !maSelection.Len() )
        {
            bEnableCut = false;
            bEnableCopy = false;
            bEnableDelete = false;
        }
 
        if ( IsReadOnly() )
        {
            bEnableCut = false;
            bEnablePaste = false;
            bEnableDelete = false;
            bEnableSpecialChar = false;
        }
        else
        {
            // only paste if text available in clipboard
            bool bData = false;
            uno::Reference< datatransfer::clipboard::XClipboard > xClipboard = GetClipboard();
 
            if ( xClipboard.is() )
            {
                uno::Reference< datatransfer::XTransferable > xDataObj;
                {
                    SolarMutexReleaser aReleaser;
                    xDataObj = xClipboard->getContents();
                }
                if ( xDataObj.is() )
                {
                    datatransfer::DataFlavor aFlavor;
                    SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
                    bData = xDataObj->isDataFlavorSupported( aFlavor );
                }
            }
            bEnablePaste = bData;
        }
 
        pPopup->EnableItem(pPopup->GetItemId(u"cut"), bEnableCut);
        pPopup->EnableItem(pPopup->GetItemId(u"copy"), bEnableCopy);
        pPopup->EnableItem(pPopup->GetItemId(u"delete"), bEnableDelete);
        pPopup->EnableItem(pPopup->GetItemId(u"paste"), bEnablePaste);
        pPopup->SetItemText(pPopup->GetItemId(u"specialchar"),
            BuilderUtils::convertMnemonicMarkup(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)));
        pPopup->EnableItem(pPopup->GetItemId(u"specialchar"), bEnableSpecialChar);
        pPopup->EnableItem(
            pPopup->GetItemId(u"undo"),
            std::u16string_view(maUndoText) != std::u16string_view(maText));
        bool bAllSelected = maSelection.Min() == 0 && maSelection.Max() == maText.getLength();
        pPopup->EnableItem(pPopup->GetItemId(u"selectall"), !bAllSelected);
        pPopup->ShowItem(pPopup->GetItemId(u"specialchar"), pImplFncGetSpecialChars != nullptr);
 
        mbActivePopup = true;
        Selection aSaveSel = GetSelection(); // if someone changes selection in Get/LoseFocus, e.g. URL bar
        Point aPos = rCEvt.GetMousePosPixel();
        if ( !rCEvt.IsMouseEvent() )
        {
            // Show menu eventually centered in selection
            Size aSize = GetOutputSizePixel();
            aPos = Point( aSize.Width()/2, aSize.Height()/2 );
        }
        sal_uInt16 n = pPopup->Execute( this, aPos );
        SetSelection( aSaveSel );
        OUString sCommand = pPopup->GetItemIdent(n);
        if (sCommand == "undo")
        {
            Undo();
            Modify();
        }
        else if (sCommand == "cut")
        {
            Cut();
            Modify();
        }
        else if (sCommand == "copy")
        {
            Copy();
        }
        else if (sCommand == "paste")
        {
            Paste();
            Modify();
        }
        else if (sCommand == "delete")
        {
            DeleteSelected();
            Modify();
        }
        else if (sCommand == "selectall")
        {
            ImplSetSelection( Selection( 0, maText.getLength() ) );
        }
        else if (sCommand == "specialchar" && pImplFncGetSpecialChars)
        {
            OUString aChars = pImplFncGetSpecialChars(GetFrameWeld(), GetFont());
            if (!isDisposed()) // destroyed while the insert special character dialog was still open
            {
                SetSelection( aSaveSel );
                if (!aChars.isEmpty())
                {
                    ImplInsertText( aChars );
                    Modify();
                }
            }
        }
        pPopup.clear();
        mbActivePopup = false;
    }
    else if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
    {
        DeleteSelected();
        sal_Int32 nPos = maSelection.Max();
        mpIMEInfos.reset(new Impl_IMEInfos( nPos, maText.copy(nPos).makeStringAndClear() ));
        mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
    }
    else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
    {
        bool bInsertMode = !mpIMEInfos->bWasCursorOverwrite;
        mpIMEInfos.reset();
 
        SetInsertMode(bInsertMode);
        Modify();
 
        Invalidate();
 
        // #i25161# call auto complete handler for ext text commit also
        if (maAutocompleteHdl.IsSet())
        {
            if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
            {
                maAutocompleteHdl.Call(*this);
            }
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
    {
        const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
 
        maText.remove( mpIMEInfos->nPos, mpIMEInfos->nLen );
        maText.insert( mpIMEInfos->nPos, pData->GetText() );
        if ( mpIMEInfos->bWasCursorOverwrite )
        {
            const sal_Int32 nOldIMETextLen = mpIMEInfos->nLen;
            const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
            if ( ( nOldIMETextLen > nNewIMETextLen ) &&
                 ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
            {
                // restore old characters
                const sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
                maText.insert( mpIMEInfos->nPos + nNewIMETextLen, mpIMEInfos->aOldTextAfterStartPos.subView( nNewIMETextLen, nRestore ) );
            }
            else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
                      ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
            {
                const sal_Int32 nOverwrite = ( nNewIMETextLen > mpIMEInfos->aOldTextAfterStartPos.getLength()
                    ? mpIMEInfos->aOldTextAfterStartPos.getLength() : nNewIMETextLen ) - nOldIMETextLen;
                maText.remove( mpIMEInfos->nPos + nNewIMETextLen, nOverwrite );
            }
        }
 
        if ( pData->GetTextAttr() )
        {
            mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
            mpIMEInfos->bCursor = pData->IsCursorVisible();
        }
        else
        {
            mpIMEInfos->DestroyAttribs();
        }
 
        ImplAlignAndPaint();
        sal_Int32 nCursorPos = mpIMEInfos->nPos + pData->GetCursorPos();
        SetSelection( Selection( nCursorPos, nCursorPos ) );
        SetInsertMode( !pData->IsCursorOverwrite() );
 
        if ( pData->IsCursorVisible() )
            GetCursor()->Show();
        else
            GetCursor()->Hide();
    }
    else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
    {
        if ( mpIMEInfos )
        {
            sal_Int32 nCursorPos = GetSelection().Max();
            SetCursorRect( nullptr, GetTextWidth( maText.toString(), nCursorPos, mpIMEInfos->nPos+mpIMEInfos->nLen-nCursorPos ) );
        }
        else
        {
            SetCursorRect();
        }
    }
    else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange )
    {
        const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData();
        Selection aSelection( pData->GetStart(), pData->GetEnd() );
        SetSelection(aSelection);
    }
    else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition )
    {
        if (mpIMEInfos && mpIMEInfos->nLen > 0)
        {
            OUString aText = ImplGetText();
            KernArray aDX;
            GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
 
            tools::Long    nTH = GetTextHeight();
            Point   aPos( mnXOffset, ImplGetTextYPosition() );
 
            std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen);
            for ( int nIndex = 0; nIndex < mpIMEInfos->nLen; ++nIndex )
            {
                tools::Rectangle aRect( aPos, Size( 10, nTH ) );
                aRect.SetLeft( aDX[2*(nIndex+mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
                aRects[ nIndex ] = aRect;
            }
            SetCompositionCharRect(aRects.data(), mpIMEInfos->nLen);
        }
    }
    else
        Control::Command( rCEvt );
}
 
void Edit::StateChanged( StateChangedType nType )
{
    if (nType == StateChangedType::InitShow)
    {
        if (!mpSubEdit)
        {
            mnXOffset = 0;  // if GrabFocus before while size was still wrong
            ImplAlign();
            if (!mpSubEdit)
                ImplShowCursor(false);
            Invalidate();
        }
    }
    else if (nType == StateChangedType::Enable)
    {
        if (!mpSubEdit)
        {
            // change text color only
            ImplInvalidateOrRepaint();
        }
    }
    else if (nType == StateChangedType::Style || nType == StateChangedType::Mirroring)
    {
        WinBits nStyle = GetStyle();
        if (nType == StateChangedType::Style)
        {
            nStyle = ImplInitStyle(GetStyle());
            SetStyle(nStyle);
        }
 
        sal_uInt16 nOldAlign = mnAlign;
        mnAlign = EDIT_ALIGN_LEFT;
 
        // hack: right align until keyinput and cursor travelling works
        // edits are always RTL disabled
        // however the parent edits contain the correct setting
        if (mbIsSubEdit && GetParent()->IsRTLEnabled())
        {
            if (GetParent()->GetStyle() & WB_LEFT)
                mnAlign = EDIT_ALIGN_RIGHT;
            if (nType == StateChangedType::Mirroring)
                GetOutDev()->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
        }
        else if (mbIsSubEdit && !GetParent()->IsRTLEnabled())
        {
            if (nType == StateChangedType::Mirroring)
                GetOutDev()->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
        }
 
        if (nStyle & WB_RIGHT)
            mnAlign = EDIT_ALIGN_RIGHT;
        else if (nStyle & WB_CENTER)
            mnAlign = EDIT_ALIGN_CENTER;
        if (!maText.isEmpty() && (mnAlign != nOldAlign))
        {
            ImplAlign();
            Invalidate();
        }
 
    }
    else if ((nType == StateChangedType::Zoom) || (nType == StateChangedType::ControlFont))
    {
        if (!mpSubEdit)
        {
            ApplySettings(*GetOutDev());
            ImplShowCursor();
            Invalidate();
        }
    }
    else if ((nType == StateChangedType::ControlForeground) || (nType == StateChangedType::ControlBackground))
    {
        if (!mpSubEdit)
        {
            ApplySettings(*GetOutDev());
            Invalidate();
        }
    }
 
    Control::StateChanged(nType);
}
 
void Edit::DataChanged( const DataChangedEvent& rDCEvt )
{
    if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
         (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
         ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
          (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
    {
        if ( !mpSubEdit )
        {
            ApplySettings(*GetOutDev());
            ImplShowCursor();
            Invalidate();
        }
    }
 
    Control::DataChanged( rDCEvt );
}
 
void Edit::ImplShowDDCursor()
{
    if (!mpDDInfo->bVisCursor)
    {
        tools::Long nTextWidth = GetTextWidth( maText.toString(), 0, mpDDInfo->nDropPos );
        tools::Long nTextHeight = GetTextHeight();
        tools::Rectangle aCursorRect( Point( nTextWidth + mnXOffset, (GetOutDev()->GetOutputSize().Height()-nTextHeight)/2 ), Size( 2, nTextHeight ) );
        mpDDInfo->aCursor.SetWindow( this );
        mpDDInfo->aCursor.SetPos( aCursorRect.TopLeft() );
        mpDDInfo->aCursor.SetSize( aCursorRect.GetSize() );
        mpDDInfo->aCursor.Show();
        mpDDInfo->bVisCursor = true;
    }
}
 
void Edit::ImplHideDDCursor()
{
    if ( mpDDInfo && mpDDInfo->bVisCursor )
    {
        mpDDInfo->aCursor.Hide();
        mpDDInfo->bVisCursor = false;
    }
}
 
TextFilter::TextFilter(OUString _aForbiddenChars)
    : sForbiddenChars(std::move(_aForbiddenChars))
{
}
 
TextFilter::~TextFilter()
{
}
 
OUString TextFilter::filter(const OUString &rText)
{
    OUString sTemp(rText);
    for (sal_Int32 i = 0; i < sForbiddenChars.getLength(); ++i)
    {
        sTemp = sTemp.replaceAll(OUStringChar(sForbiddenChars[i]), "");
    }
    return sTemp;
}
 
void Edit::filterText()
{
    Selection aSel = GetSelection();
    const OUString sOrig = GetText();
    const OUString sNew = mpFilterText->filter(GetText());
    if (sOrig != sNew)
    {
        sal_Int32 nDiff = sOrig.getLength() - sNew.getLength();
        if (nDiff)
        {
            aSel.setMin(aSel.getMin() - nDiff);
            aSel.setMax(aSel.getMin());
        }
        SetText(sNew);
        SetSelection(aSel);
    }
}
 
void Edit::Modify()
{
    if (mpFilterText)
        filterText();
 
    if ( mbIsSubEdit )
    {
        static_cast<Edit*>(GetParent())->Modify();
    }
    else
    {
        if ( ImplCallEventListenersAndHandler( VclEventId::EditModify, [this] () { maModifyHdl.Call(*this); } ) )
            // have been destroyed while calling into the handlers
            return;
 
        // #i13677# notify edit listeners about caret position change
        CallEventListeners( VclEventId::EditCaretChanged );
        // FIXME: this is currently only on macOS
        // check for other platforms that need similar handling
        if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
            IsNativeWidgetEnabled() &&
            IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ) )
        {
            ImplInvalidateOutermostBorder( this );
        }
    }
}
 
void Edit::SetEchoChar( sal_Unicode c )
{
    mcEchoChar = c;
    if ( mpSubEdit )
        mpSubEdit->SetEchoChar( c );
}
 
void Edit::SetReadOnly( bool bReadOnly )
{
    if ( mbReadOnly != bReadOnly )
    {
        mbReadOnly = bReadOnly;
        if ( mpSubEdit )
            mpSubEdit->SetReadOnly( bReadOnly );
 
        CompatStateChanged( StateChangedType::ReadOnly );
    }
}
 
void Edit::SetInsertMode( bool bInsert )
{
    if ( bInsert != mbInsertMode )
    {
        mbInsertMode = bInsert;
        if ( mpSubEdit )
            mpSubEdit->SetInsertMode( bInsert );
        else
            ImplShowCursor();
    }
}
 
bool Edit::IsInsertMode() const
{
    if ( mpSubEdit )
        return mpSubEdit->IsInsertMode();
    else
        return mbInsertMode;
}
 
void Edit::SetMaxTextLen(sal_Int32 nMaxLen)
{
    mnMaxTextLen = nMaxLen > 0 ? nMaxLen : EDIT_NOLIMIT;
 
    if ( mpSubEdit )
        mpSubEdit->SetMaxTextLen( mnMaxTextLen );
    else
    {
        if ( maText.getLength() > mnMaxTextLen )
            ImplDelete( Selection( mnMaxTextLen, maText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
    }
}
 
void Edit::SetSelection( const Selection& rSelection )
{
    // If the selection was changed from outside, e.g. by MouseButtonDown, don't call Tracking()
    // directly afterwards which would change the selection again
    if ( IsTracking() )
        EndTracking();
    else if ( mpSubEdit && mpSubEdit->IsTracking() )
        mpSubEdit->EndTracking();
 
    ImplSetSelection( rSelection );
}
 
void Edit::ImplSetSelection( const Selection& rSelection, bool bPaint )
{
    if ( mpSubEdit )
        mpSubEdit->ImplSetSelection( rSelection );
    else
    {
        if ( rSelection != maSelection )
        {
            Selection aOld( maSelection );
            Selection aNew( rSelection );
 
            if ( aNew.Min() > maText.getLength() )
                aNew.Min() = maText.getLength();
            if ( aNew.Max() > maText.getLength() )
                aNew.Max() = maText.getLength();
            if ( aNew.Min() < 0 )
                aNew.Min() = 0;
            if ( aNew.Max() < 0 )
                aNew.Max() = 0;
 
            if ( aNew != maSelection )
            {
                ImplClearLayoutData();
                Selection aTemp = maSelection;
                maSelection = aNew;
 
                if ( bPaint && ( aOld.Len() || aNew.Len() || IsPaintTransparent() ) )
                    ImplInvalidateOrRepaint();
                ImplShowCursor();
 
                bool bCaret = false, bSelection = false;
                tools::Long nB=aNew.Max(), nA=aNew.Min(),oB=aTemp.Max(), oA=aTemp.Min();
                tools::Long nGap = nB-nA, oGap = oB-oA;
                if (nB != oB)
                    bCaret = true;
                if (nGap != 0 || oGap != 0)
                    bSelection = true;
 
                if (bSelection)
                {
                    if ( mbIsSubEdit )
                        static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
                    else
                        CallEventListeners( VclEventId::EditSelectionChanged );
                }
 
                if (bCaret)
                {
                    if ( mbIsSubEdit )
                        static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditCaretChanged );
                    else
                        CallEventListeners( VclEventId::EditCaretChanged );
                }
 
                // #103511# notify combobox listeners of deselection
                if( !maSelection && GetParent() && GetParent()->GetType() == WindowType::COMBOBOX )
                    static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::ComboboxDeselect );
            }
        }
    }
}
 
const Selection& Edit::GetSelection() const
{
    if ( mpSubEdit )
        return mpSubEdit->GetSelection();
    else
        return maSelection;
}
 
void Edit::ReplaceSelected( const OUString& rStr )
{
    if ( mpSubEdit )
        mpSubEdit->ReplaceSelected( rStr );
    else
        ImplInsertText( rStr );
}
 
void Edit::DeleteSelected()
{
    if ( mpSubEdit )
        mpSubEdit->DeleteSelected();
    else
    {
        if ( maSelection.Len() )
            ImplDelete( maSelection, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
    }
}
 
OUString Edit::GetSelected() const
{
    if ( mpSubEdit )
        return mpSubEdit->GetSelected();
    else
    {
        Selection aSelection( maSelection );
        aSelection.Normalize();
        return OUString( maText.getStr() + aSelection.Min(), aSelection.Len() );
    }
}
 
void Edit::Cut()
{
    if ( !mbPassword )
    {
        Copy();
        ReplaceSelected( OUString() );
    }
}
 
void Edit::Copy()
{
    if ( !mbPassword )
    {
        css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
        ImplCopy( aClipboard );
    }
}
 
void Edit::Paste()
{
    css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
    ImplPaste( aClipboard );
}
 
void Edit::Undo()
{
    if ( mpSubEdit )
        mpSubEdit->Undo();
    else
    {
        const OUString aText( maText.toString() );
        ImplDelete( Selection( 0, aText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
        ImplInsertText( maUndoText );
        ImplSetSelection( Selection( 0, maUndoText.getLength() ) );
        maUndoText = aText;
    }
}
 
void Edit::SetText( const OUString& rStr )
{
    if ( mpSubEdit )
        mpSubEdit->SetText( rStr ); // not directly ImplSetText if SetText overridden
    else
    {
        Selection aNewSel( 0, 0 );  // prevent scrolling
        ImplSetText( rStr, &aNewSel );
    }
}
 
void Edit::SetText( const OUString& rStr, const Selection& rSelection )
{
    if ( mpSubEdit )
        mpSubEdit->SetText( rStr, rSelection );
    else
        ImplSetText( rStr, &rSelection );
}
 
OUString Edit::GetText() const
{
    if ( mpSubEdit )
        return mpSubEdit->GetText();
    else
        return maText.toString();
}
 
void Edit::SetCursorAtLast(){
    ImplSetCursorPos( GetText().getLength(), false );
}
 
void Edit::SetPlaceholderText( const OUString& rStr )
{
    if ( mpSubEdit )
        mpSubEdit->SetPlaceholderText( rStr );
    else if ( maPlaceholderText != rStr )
    {
        maPlaceholderText = rStr;
        if ( GetText().isEmpty() )
            Invalidate();
    }
}
 
void Edit::SetModifyFlag()
{
}
 
void Edit::SetSubEdit(Edit* pEdit)
{
    mpSubEdit.disposeAndClear();
    mpSubEdit.set(pEdit);
 
    if (mpSubEdit)
    {
        SetPointer(PointerStyle::Arrow);    // Only SubEdit has the BEAM...
        mpSubEdit->mbIsSubEdit = true;
 
        mpSubEdit->SetReadOnly(mbReadOnly);
        mpSubEdit->maAutocompleteHdl = maAutocompleteHdl;
    }
}
 
Size Edit::CalcMinimumSizeForText(const OUString &rString) const
{
    ControlType eCtrlType = ImplGetNativeControlType();
 
    Size aSize;
    if (mnWidthInChars != -1)
    {
        //CalcSize calls CalcWindowSize, but we will call that also in this
        //function, so undo the first one with CalcOutputSize
        aSize = CalcOutputSize(CalcSize(mnWidthInChars));
    }
    else
    {
        OUString aString;
        if (mnMaxWidthChars != -1 && mnMaxWidthChars < rString.getLength())
            aString = rString.copy(0, mnMaxWidthChars);
        else
            aString = rString;
 
        aSize.setHeight( GetTextHeight() );
        aSize.setWidth( GetTextWidth(aString) );
        aSize.AdjustWidth(ImplGetExtraXOffset() * 2 );
 
        // do not create edit fields in which one cannot enter anything
        // a default minimum width should exist for at least 3 characters
 
        //CalcSize calls CalcWindowSize, but we will call that also in this
        //function, so undo the first one with CalcOutputSize
        Size aMinSize(CalcOutputSize(CalcSize(3)));
        if (aSize.Width() < aMinSize.Width())
            aSize.setWidth( aMinSize.Width() );
    }
 
    aSize.AdjustHeight(ImplGetExtraYOffset() * 2 );
 
    aSize = CalcWindowSize( aSize );
 
    // ask NWF what if it has an opinion, too
    ImplControlValue aControlValue;
    tools::Rectangle aRect( Point( 0, 0 ), aSize );
    tools::Rectangle aContent, aBound;
    if (GetNativeControlRegion(eCtrlType, ControlPart::Entire, aRect, ControlState::NONE,
                               aControlValue, aBound, aContent))
    {
        if (aBound.GetHeight() > aSize.Height())
            aSize.setHeight( aBound.GetHeight() );
    }
    return aSize;
}
 
Size Edit::CalcMinimumSize() const
{
    return CalcMinimumSizeForText(GetText());
}
 
Size Edit::GetOptimalSize() const
{
    return CalcMinimumSize();
}
 
Size Edit::CalcSize(sal_Int32 nChars) const
{
    // width for N characters, independent from content.
    // works only correct for fixed fonts, average otherwise
    float fUnitWidth = std::max(approximate_char_width(), approximate_digit_width());
    Size aSz(fUnitWidth * nChars, GetTextHeight());
    aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
    aSz = CalcWindowSize( aSz );
    return aSz;
}
 
sal_Int32 Edit::GetMaxVisChars() const
{
    const vcl::Window* pW = mpSubEdit ? mpSubEdit : this;
    sal_Int32 nOutWidth = pW->GetOutputSizePixel().Width();
    float fUnitWidth = std::max(approximate_char_width(), approximate_digit_width());
    return nOutWidth / fUnitWidth;
}
 
namespace vcl
{
    void SetGetSpecialCharsFunction( FncGetSpecialChars fn )
    {
        pImplFncGetSpecialChars = fn;
    }
 
    FncGetSpecialChars GetGetSpecialCharsFunction()
    {
        return pImplFncGetSpecialChars;
    }
}
 
VclPtr<PopupMenu> Edit::CreatePopupMenu()
{
    if (!mpUIBuilder)
        mpUIBuilder.reset(new VclBuilder(nullptr, AllSettings::GetUIRootDir(), u"vcl/ui/editmenu.ui"_ustr, u""_ustr));
    VclPtr<PopupMenu> pPopup = mpUIBuilder->get_menu(u"menu");
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    if (rStyleSettings.GetHideDisabledMenuItems())
        pPopup->SetMenuFlags( MenuFlags::HideDisabledEntries );
    else
        pPopup->SetMenuFlags ( MenuFlags::AlwaysShowDisabledEntries );
    if (rStyleSettings.GetContextMenuShortcuts())
    {
        pPopup->SetAccelKey(pPopup->GetItemId(u"undo"), vcl::KeyCode( KeyFuncType::UNDO));
        pPopup->SetAccelKey(pPopup->GetItemId(u"cut"), vcl::KeyCode( KeyFuncType::CUT));
        pPopup->SetAccelKey(pPopup->GetItemId(u"copy"), vcl::KeyCode( KeyFuncType::COPY));
        pPopup->SetAccelKey(pPopup->GetItemId(u"paste"), vcl::KeyCode( KeyFuncType::PASTE));
        pPopup->SetAccelKey(pPopup->GetItemId(u"delete"), vcl::KeyCode( KeyFuncType::DELETE));
        pPopup->SetAccelKey(pPopup->GetItemId(u"selectall"), vcl::KeyCode( KEY_A, false, true, false, false));
        pPopup->SetAccelKey(pPopup->GetItemId(u"specialchar"), vcl::KeyCode( KEY_S, true, true, false, false));
    }
    return pPopup;
}
 
// css::datatransfer::dnd::XDragGestureListener
void Edit::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
{
    SolarMutexGuard aVclGuard;
 
    if ( !(!IsTracking() && maSelection.Len() &&
         !mbPassword && (!mpDDInfo || !mpDDInfo->bStarterOfDD)) ) // no repeated D&D
        return;
 
    Selection aSel( maSelection );
    aSel.Normalize();
 
    // only if mouse in the selection...
    Point aMousePos( rDGE.DragOriginX, rDGE.DragOriginY );
    sal_Int32 nCharPos = ImplGetCharPos( aMousePos );
    if ( (nCharPos < aSel.Min()) || (nCharPos >= aSel.Max()) )
        return;
 
    if ( !mpDDInfo )
        mpDDInfo.reset(new DDInfo);
 
    mpDDInfo->bStarterOfDD = true;
    mpDDInfo->aDndStartSel = aSel;
 
    if ( IsTracking() )
        EndTracking();  // before D&D disable tracking
 
    rtl::Reference<vcl::unohelper::TextDataObject> pDataObj = new vcl::unohelper::TextDataObject( GetSelected() );
    sal_Int8 nActions = datatransfer::dnd::DNDConstants::ACTION_COPY;
    if ( !IsReadOnly() )
        nActions |= datatransfer::dnd::DNDConstants::ACTION_MOVE;
    rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mxDnDListener );
    if ( GetCursor() )
        GetCursor()->Hide();
}
 
// css::datatransfer::dnd::XDragSourceListener
void Edit::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& rDSDE )
{
    SolarMutexGuard aVclGuard;
 
    if (rDSDE.DropSuccess && (rDSDE.DropAction & datatransfer::dnd::DNDConstants::ACTION_MOVE) && mpDDInfo)
    {
        Selection aSel( mpDDInfo->aDndStartSel );
        if ( mpDDInfo->bDroppedInMe )
        {
            if ( aSel.Max() > mpDDInfo->nDropPos )
            {
                tools::Long nLen = aSel.Len();
                aSel.Min() += nLen;
                aSel.Max() += nLen;
            }
        }
        ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
        Modify();
    }
 
    ImplHideDDCursor();
    mpDDInfo.reset();
}
 
// css::datatransfer::dnd::XDropTargetListener
void Edit::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
{
    SolarMutexGuard aVclGuard;
 
    bool bChanges = false;
    if ( !mbReadOnly && mpDDInfo )
    {
        ImplHideDDCursor();
 
        Selection aSel( maSelection );
        aSel.Normalize();
 
        if ( aSel.Len() && !mpDDInfo->bStarterOfDD )
            ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
 
        mpDDInfo->bDroppedInMe = true;
 
        aSel.Min() = mpDDInfo->nDropPos;
        aSel.Max() = mpDDInfo->nDropPos;
        ImplSetSelection( aSel );
 
        uno::Reference< datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
        if ( xDataObj.is() )
        {
            datatransfer::DataFlavor aFlavor;
            SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
            if ( xDataObj->isDataFlavorSupported( aFlavor ) )
            {
                uno::Any aData = xDataObj->getTransferData( aFlavor );
                OUString aText;
                aData >>= aText;
                ImplInsertText( aText );
                bChanges = true;
                Modify();
            }
        }
 
        if ( !mpDDInfo->bStarterOfDD )
        {
            mpDDInfo.reset();
        }
    }
 
    rDTDE.Context->dropComplete( bChanges );
}
 
void Edit::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& rDTDE )
{
    if ( !mpDDInfo )
    {
        mpDDInfo.reset(new DDInfo);
    }
    // search for string data type
    const Sequence< css::datatransfer::DataFlavor >& rFlavors( rDTDE.SupportedDataFlavors );
    mpDDInfo->bIsStringSupported = std::any_of(rFlavors.begin(), rFlavors.end(),
        [](const css::datatransfer::DataFlavor& rFlavor) {
            sal_Int32 nIndex = 0;
            const std::u16string_view aMimetype = o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex );
            return aMimetype == u"text/plain";
        });
}
 
void Edit::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
{
    SolarMutexGuard aVclGuard;
 
    ImplHideDDCursor();
}
 
void Edit::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
{
    SolarMutexGuard aVclGuard;
 
    Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
 
    sal_Int32 nPrevDropPos = mpDDInfo->nDropPos;
    mpDDInfo->nDropPos = ImplGetCharPos( aMousePos );
 
    /*
    Size aOutSize = GetOutputSizePixel();
    if ( ( aMousePos.X() < 0 ) || ( aMousePos.X() > aOutSize.Width() ) )
    {
        // Scroll?
        // No, I will not receive events in this case...
    }
    */
 
    Selection aSel( maSelection );
    aSel.Normalize();
 
    // Don't accept drop in selection or read-only field...
    if ( IsReadOnly() || aSel.Contains( mpDDInfo->nDropPos ) || ! mpDDInfo->bIsStringSupported )
    {
        ImplHideDDCursor();
        rDTDE.Context->rejectDrag();
    }
    else
    {
        // draw the old cursor away...
        if ( !mpDDInfo->bVisCursor || ( nPrevDropPos != mpDDInfo->nDropPos ) )
        {
            ImplHideDDCursor();
            ImplShowDDCursor();
        }
        rDTDE.Context->acceptDrag( rDTDE.DropAction );
    }
}
 
OUString Edit::GetSurroundingText() const
{
    if (mpSubEdit)
        return mpSubEdit->GetSurroundingText();
    return maText.toString();
}
 
Selection Edit::GetSurroundingTextSelection() const
{
    return GetSelection();
}
 
bool Edit::DeleteSurroundingText(const Selection& rSelection)
{
    SetSelection(rSelection);
    DeleteSelected();
    // maybe we should update mpIMEInfos here
    return true;
}
 
FactoryFunction Edit::GetUITestFactory() const
{
    return EditUIObject::create;
}
 
 
void Edit::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
{
    Control::DumpAsPropertyTree(rJsonWriter);
 
    if (!maPlaceholderText.isEmpty())
        rJsonWriter.put("placeholder", maPlaceholderText);
 
    if (IsPassword())
        rJsonWriter.put("password", true);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V623 Consider inspecting the '?:' operator. A temporary object of the 'VclPtr' type is being created and subsequently destroyed. Check third operand.

V1053 Calling the 'EnableRTL' virtual function indirectly in the constructor may lead to unexpected result at runtime. Check lines: 'edit.cxx:155', 'edit.cxx:283', 'ctrl.hxx:180'.

V1053 Calling the 'EnableRTL' virtual function indirectly in the constructor may lead to unexpected result at runtime. Check lines: 'edit.cxx:161', 'edit.cxx:283', 'ctrl.hxx:180'.

V1051 Consider checking for misprints. It's possible that the 'nStyle' should be checked here.