/* -*- 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 <config_feature_desktop.h>

#include <vcl/weld.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <viewopt.hxx>
#include <frmtool.hxx>
#include <viscrs.hxx>
#include <crsrsh.hxx>
#include <doc.hxx>
#include <swtable.hxx>
#include <viewimp.hxx>
#include <dview.hxx>
#include <rootfrm.hxx>
#include <txtfrm.hxx>
#include <ndtxt.hxx>
#include <txtfld.hxx>
#include <scriptinfo.hxx>
#include <view.hxx>
#include <swmodule.hxx>
#include <IDocumentLayoutAccess.hxx>

#include <svx/sdr/overlay/overlaymanager.hxx>
#include <svx/sdrpaintwindow.hxx>
#include <svx/srchdlg.hxx>
#include <svx/sdr/overlay/overlayselection.hxx>
#include "overlayrangesoutline.hxx"

#include <memory>

#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <comphelper/lok.hxx>
#include <sfx2/lokhelper.hxx>
#include <boost/property_tree/json_parser.hpp>
#include <comphelper/string.hxx>
#include <osl/diagnose.h>
#include <paintfrm.hxx>
#include <PostItMgr.hxx>
#include <SwGrammarMarkUp.hxx>
#include <docsh.hxx>
#include <svtools/optionsdrawinglayer.hxx>
#include <o3tl/string_view.hxx>
#include <tools/json_writer.hxx>
#include <cellfrm.hxx>
#include <wrtsh.hxx>
#include <textcontentcontrol.hxx>
#include <dropdowncontentcontrolbutton.hxx>
#include <datecontentcontrolbutton.hxx>
#include <FrameControlsManager.hxx>

// Here static members are defined. They will get changed on alteration of the
// MapMode. This is done so that on ShowCursor the same size does not have to be
// expensively determined again and again.

tools::Long SwSelPaintRects::s_nPixPtX = 0;
tools::Long SwSelPaintRects::s_nPixPtY = 0;
MapMode* SwSelPaintRects::s_pMapMode = nullptr;

// Starting from here: classes / methods for the non-text-cursor
SwVisibleCursor::SwVisibleCursor(::sw::VisibleCursorState const& rState,
        const SwCursorShell *const pCShell)
    : m_rState(rState)
    , m_pCursorShell(pCShell)
    , m_nPageLastTime(0)
{
    if (&rState == pCShell)
    {
        pCShell->GetWin()->SetCursor( &m_aTextCursor );
    }
    m_bIsVisible = m_aTextCursor.IsVisible();
    m_bIsDragCursor = false;
    m_aTextCursor.SetWidth( 0 );
}

SwVisibleCursor::~SwVisibleCursor()
{
    if( m_bIsVisible && m_aTextCursor.IsVisible() )
        m_aTextCursor.Hide();

    if (m_pCursorShell->GetWin()->GetCursor() == &m_aTextCursor)
    {
        m_pCursorShell->GetWin()->SetCursor( nullptr );
    }
}

void SwVisibleCursor::Show()
{
    if( !m_bIsVisible )
    {
        m_bIsVisible = true;

        // display at all?
        if (m_pCursorShell->VisArea().Overlaps(m_rState.m_aCharRect) || comphelper::LibreOfficeKit::isActive())
            SetPosAndShow(nullptr);
    }
}

void SwVisibleCursor::Hide()
{
    if( m_bIsVisible )
    {
        m_bIsVisible = false;

        if( m_aTextCursor.IsVisible() )      // Shouldn't the flags be in effect?
            m_aTextCursor.Hide();
    }
}

namespace
{

// Build JSON message to be sent to Online
OString buildHyperlinkJSON(const OUString& sText, const OUString& sLink)
{
    boost::property_tree::ptree aTree;
    aTree.put("text", sText);
    aTree.put("link", sLink);
    std::stringstream aStream;
    boost::property_tree::write_json(aStream, aTree, false);

    return OString(o3tl::trim(aStream.str()));
}

}

SwRect SwVisibleCursor::SetPos()
{
    SwRect aRect;
    tools::Long nTmpY = m_rState.m_aCursorHeight.getY();
    if( 0 > nTmpY )
    {
        nTmpY = -nTmpY;
        m_aTextCursor.SetOrientation( 900_deg10 );
        aRect = SwRect( m_rState.m_aCharRect.Pos(),
           Size( m_rState.m_aCharRect.Height(), nTmpY ) );
        aRect.Pos().setX(aRect.Pos().getX() + m_rState.m_aCursorHeight.getX());
        if (m_rState.IsOverwriteCursor())
            aRect.Pos().setY(aRect.Pos().getY() + aRect.Width());
    }
    else
    {
        m_aTextCursor.SetOrientation();
        aRect = SwRect( m_rState.m_aCharRect.Pos(),
           Size( m_rState.m_aCharRect.Width(), nTmpY ) );
        aRect.Pos().setY(aRect.Pos().getY() + m_rState.m_aCursorHeight.getX());
    }

    // check if cursor should show the current cursor bidi level
    m_aTextCursor.SetDirection();
    const SwCursor* pTmpCursor = m_rState.m_pCurrentCursor;

    if ( pTmpCursor && !m_rState.IsOverwriteCursor() )
    {
        SwNode& rNode = pTmpCursor->GetPoint()->GetNode();
        if( rNode.IsTextNode() )
        {
            const SwTextNode& rTNd = *rNode.GetTextNode();
            const SwFrame* pFrame = rTNd.getLayoutFrame(m_pCursorShell->GetLayout(), nullptr, nullptr);
            if ( pFrame )
            {
                const SwScriptInfo* pSI = static_cast<const SwTextFrame*>(pFrame)->GetScriptInfo();
                 // cursor level has to be shown
                if ( pSI && pSI->CountDirChg() > 1 )
                {
                    m_aTextCursor.SetDirection(
                        ( pTmpCursor->GetCursorBidiLevel() % 2 ) ?
                          CursorDirection::RTL :
                          CursorDirection::LTR );
                }
                if ( pFrame->IsRightToLeft() )
                {
                    const OutputDevice *pOut = m_pCursorShell->GetOut();
                    if ( pOut )
                    {
                        tools::Long nSize = pOut->GetSettings().GetStyleSettings().GetCursorSize();
                        Size aSize( nSize, nSize );
                        aSize = pOut->PixelToLogic( aSize );
                        aRect.Left( aRect.Left() - aSize.Width() );
                    }
                }
            }
        }
    }

    if( aRect.Height())
    {
        ::SwCalcPixStatics( m_pCursorShell->GetOut() );

        // Disable pixel alignment when tiled rendering, so that twip values of
        // the cursor don't depend on statics.
        if (!comphelper::LibreOfficeKit::isActive())
            ::SwAlignRect( aRect, static_cast<SwViewShell const *>(m_pCursorShell), m_pCursorShell->GetOut() );
    }
    if( !m_rState.IsOverwriteCursor() || m_bIsDragCursor ||
        m_pCursorShell->IsSelection() )
        aRect.Width( 0 );

    m_aTextCursor.SetSize( aRect.SSize() );
    m_aTextCursor.SetPos( aRect.Pos() );
    return aRect;
}

void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell)
{
    const SwRect aRect = SetPos();

    if (SfxViewShell* pNotifyViewShell = comphelper::LibreOfficeKit::isActive() ? m_pCursorShell->GetSfxViewShell() : nullptr)
    {
        // notify about page number change (if that happened)
        sal_uInt16 nPage, nVirtPage;
        // bCalcFrame=false is important to avoid calculating the layout when
        // we're in the middle of doing that already.
        const_cast<SwCursorShell*>(m_pCursorShell)->GetPageNum(nPage, nVirtPage, /*bAtCursorPos=*/true, /*bCalcFrame=*/false);
        if (nPage != m_nPageLastTime)
        {
            m_nPageLastTime = nPage;
            OString aPayload = OString::number(nPage - 1);
            pNotifyViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload);
        }

        // This may get called often, so instead of sending data on each update, just notify
        // that there's been an update, and the other side will pull the data using
        // getLOKPayload() when it decides to.
        m_aLastLOKRect = aRect;
        if (pViewShell)
        {
            if (pViewShell == pNotifyViewShell)
            {
                SfxLokHelper::notifyUpdatePerViewId(*pViewShell, LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR);
            }
            else
            {
                SfxLokHelper::notifyUpdatePerViewId(*pViewShell, pNotifyViewShell, *pViewShell,
                    LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
            }
        }
        else
        {
            SfxLokHelper::notifyUpdatePerViewId(*pNotifyViewShell, SfxViewShell::Current(),
                *pNotifyViewShell, LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR);
            SfxLokHelper::notifyOtherViewsUpdatePerViewId(pNotifyViewShell, LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
        }
    }

    if ( m_pCursorShell->IsCursorReadonly() && !m_pCursorShell->GetViewOptions()->IsSelectionInReadonly() )
        return;

    if ( m_pCursorShell->GetDrawView() )
        const_cast<SwDrawView*>(static_cast<const SwDrawView*>(m_pCursorShell->GetDrawView()))->SetAnimationEnabled(
                !m_pCursorShell->IsSelection() );

    sal_uInt16 nStyle = m_bIsDragCursor ? CURSOR_SHADOW : 0;
    if( nStyle != m_aTextCursor.GetStyle() )
    {
        m_aTextCursor.SetStyle( nStyle );
        m_aTextCursor.SetWindow( m_bIsDragCursor ? m_pCursorShell->GetWin() : nullptr );
    }

    m_aTextCursor.Show();
}

std::optional<OString> SwVisibleCursor::getLOKPayload(int nType, int nViewId) const
{
    assert(nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR || nType == LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
    if (comphelper::LibreOfficeKit::isActive())
    {
        SwRect aRect = m_aLastLOKRect;

        // notify about the cursor position & size
        tools::Rectangle aSVRect(aRect.Pos().getX(), aRect.Pos().getY(), aRect.Pos().getX() + aRect.SSize().Width(), aRect.Pos().getY() + aRect.SSize().Height());
        OString sRect = aSVRect.toString();

        if(nType == LOK_CALLBACK_INVALIDATE_VIEW_CURSOR)
            return SfxLokHelper::makePayloadJSON(m_pCursorShell->GetSfxViewShell(), nViewId, "rectangle", sRect);

        // is cursor at a misspelled word ?
        bool bIsWrong = false;
        SwView* pView = dynamic_cast<SwView*>(m_pCursorShell->GetSfxViewShell());
        if (pView && pView->GetWrtShellPtr())
        {
            const SwViewOption* pVOpt = pView->GetWrtShell().GetViewOptions();
            if(pVOpt && pVOpt->IsOnlineSpell())
            {
                SwPaM* pCursor = m_pCursorShell->GetCursor();
                SwPosition aPos(*pCursor->GetPoint());
                Point aPt = aRect.Pos();
                SwCursorMoveState eTmpState(CursorMoveState::SetOnlyText);
                SwTextNode *pNode = nullptr;
                if (m_pCursorShell->GetLayout()->GetModelPositionForViewPoint(&aPos, aPt, &eTmpState))
                    pNode = aPos.GetNode().GetTextNode();
                if (pNode && !pNode->IsInProtectSect())
                {
                    sal_Int32 nBegin = aPos.GetContentIndex();
                    sal_Int32 nLen = 1;

                    SwWrongList *pWrong = pNode->GetWrong();
                    if (!pWrong)
                        pWrong = pNode->GetGrammarCheck();
                    if (pWrong)
                        bIsWrong = pWrong->InWrongWord(nBegin,nLen) && !pNode->IsSymbolAt(nBegin);
                }
            }
        }

        OString sHyperlink;
        SwContentAtPos aContentAtPos(IsAttrAtPos::InetAttr);
        bool bIsSelection = m_pCursorShell->IsSelection();

        if (const_cast<SwCursorShell*>(m_pCursorShell)->GetContentAtPos(aRect.Pos(), aContentAtPos))
        {
            const SwFormatINetFormat* pItem = static_cast<const SwFormatINetFormat*>(aContentAtPos.aFnd.pAttr);
            sHyperlink = buildHyperlinkJSON(aContentAtPos.sStr, pItem->GetValue());
        }
        else if (bIsSelection)
        {
            SwWrtShell* pShell = m_pCursorShell->GetDoc()->GetDocShell()->GetWrtShell();
            if (SfxViewShell* pNotifySh = pShell ? m_pCursorShell->GetSfxViewShell() : nullptr)
            {
                SfxItemSetFixed<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT> aSet(pNotifySh->GetPool());
                pShell->GetCurAttr(aSet);
                const SwFormatINetFormat* pItem = nullptr;
                if(SfxItemState::SET <= aSet.GetItemState( RES_TXTATR_INETFMT, true, &pItem ))
                {
                    sHyperlink = buildHyperlinkJSON(m_pCursorShell->GetSelText(),
                                                    pItem->GetValue());
                }
            }
        }

        return SfxLokHelper::makeVisCursorInvalidation(nViewId, sRect, bIsWrong, sHyperlink);
    }
    else
        abort();
}

const vcl::Cursor& SwVisibleCursor::GetTextCursor() const
{
    return m_aTextCursor;
}

SwSelPaintRects::SwSelPaintRects(const SwCursorShell& rCSh)
    : m_pCursorShell( &rCSh )
#if HAVE_FEATURE_DESKTOP
    , m_bShowTextInputFieldOverlay(true)
    , m_bShowContentControlOverlay(true)
#endif
{
}

SwSelPaintRects::~SwSelPaintRects()
{
    Hide();
    m_pContentControlButton.disposeAndClear();
}

void SwSelPaintRects::swapContent(SwSelPaintRects& rSwap)
{
    SwRects::swap(rSwap);

#if HAVE_FEATURE_DESKTOP
    // #i75172# also swap m_pCursorOverlay
    std::swap(m_pCursorOverlay, rSwap.m_pCursorOverlay);
    std::swap(m_bShowTextInputFieldOverlay, rSwap.m_bShowTextInputFieldOverlay);
    std::swap(m_pTextInputFieldOverlay, rSwap.m_pTextInputFieldOverlay);
    std::swap(m_bShowContentControlOverlay, rSwap.m_bShowContentControlOverlay);
    std::swap(m_pContentControlOverlay, rSwap.m_pContentControlOverlay);
#endif
}

void SwSelPaintRects::Hide()
{
#if HAVE_FEATURE_DESKTOP
    m_pCursorOverlay.reset();
    m_pTextInputFieldOverlay.reset();
    m_pContentControlOverlay.reset();
#endif

    SwRects::clear();
}

/**
 * Return a layout rectangle (typically with minimal width) that represents a
 * cursor at rPosition.
 *
 * @param rPoint layout position as a hint about what layout frame contains
 * rPosition (there might be multiple frames for a single node)
 * @param rPosition the doc model position (paragraph / character index)
 */
static SwRect lcl_getLayoutRect(const Point& rPoint, const SwPosition& rPosition)
{
    const SwContentNode* pNode = rPosition.GetNode().GetContentNode();
    std::pair<Point, bool> const tmp(rPoint, true);
    const SwContentFrame* pFrame = pNode->getLayoutFrame(
            pNode->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
            &rPosition, &tmp);
    SwRect aRect;
    pFrame->GetCharRect(aRect, rPosition);
    return aRect;
}

void SwShellCursor::FillStartEnd(SwRect& rStart, SwRect& rEnd) const
{
    const SwShellCursor* pCursor = GetShell()->getShellCursor(false);
    rStart = lcl_getLayoutRect(pCursor->GetSttPos(), *pCursor->Start());
    rEnd = lcl_getLayoutRect(pCursor->GetEndPos(), *pCursor->End());
}

void SwSelPaintRects::Show(std::vector<OString>* pSelectionRectangles)
{
    SdrView *const pView = const_cast<SdrView*>(m_pCursorShell->GetDrawView());

    if(!(pView && pView->PaintWindowCount()))
        return;

    // reset rects
    SwRects::clear();
    FillRects();

#if HAVE_FEATURE_DESKTOP
    // get new rects
    std::vector< basegfx::B2DRange > aNewRanges;
    aNewRanges.reserve(size());
    for(size_type a = 0; a < size(); ++a)
    {
        const SwRect aNextRect((*this)[a]);
        const tools::Rectangle aPntRect(aNextRect.SVRect());

        aNewRanges.emplace_back(
            aPntRect.Left(), aPntRect.Top(),
            aPntRect.Right() + 1, aPntRect.Bottom() + 1);
    }

    if (m_pCursorOverlay)
    {
        if(!aNewRanges.empty())
        {
            m_pCursorOverlay->setRanges(std::move(aNewRanges));
        }
        else
        {
            m_pCursorOverlay.reset();
        }
    }
    else if(!empty())
    {
        SdrPaintWindow* pCandidate = pView->GetPaintWindow(0);
        const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager();

        if (xTargetOverlay.is())
        {
            ::std::optional<Color> oColor;
#if ENABLE_YRS
            if (SwVisibleCursor *const pVisibleCursor{GetShell()->FindVisibleCursorForPeer(*this)})
            {
                ::std::size_t const authorId{SwModule::get()->InsertRedlineAuthor(*pVisibleCursor->m_Author)};
                oColor.emplace(SwPostItMgr::GetColorAnchor(authorId));
            }
#endif
            // get the system's highlight color
            const Color aHighlight(oColor ? *oColor : SvtOptionsDrawinglayer::getHilightColor());

            // create correct selection
            m_pCursorOverlay.reset( new sdr::overlay::OverlaySelection(
                sdr::overlay::OverlayType::Transparent,
                aHighlight,
                std::move(aNewRanges),
                true) );

            xTargetOverlay->add(*m_pCursorOverlay);
        }
    }

    HighlightInputField();
    HighlightContentControl();
#endif

    // Tiled editing does not expose the draw and writer cursor, it just
    // talks about "the" cursor at the moment. As long as that's true,
    // don't say anything about the Writer cursor till a draw object is
    // being edited.
    if (!comphelper::LibreOfficeKit::isActive() || pView->GetTextEditObject())
        return;

    // If pSelectionRectangles is set, we're just collecting the text selections -> don't emit start/end.
    if (!empty() && !pSelectionRectangles)
    {
        SwRect aStartRect;
        SwRect aEndRect;
        FillStartEnd(aStartRect, aEndRect);

        if (aStartRect.HasArea())
            SfxLokHelper::notifyUpdate(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_SELECTION_START);
        if (aEndRect.HasArea())
            SfxLokHelper::notifyUpdate(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_SELECTION_END);
    }

    std::vector<OString> aRect;
    aRect.reserve(size());
    for (size_type i = 0; i < size(); ++i)
    {
        const SwRect& rRect = (*this)[i];
        aRect.push_back(rRect.SVRect().toString());
    }
    OString sRect = comphelper::string::join("; ", aRect);
    if (!pSelectionRectangles)
    {
        SfxLokHelper::notifyUpdate(GetShell()->GetSfxViewShell(),LOK_CALLBACK_TEXT_SELECTION);
        SfxLokHelper::notifyOtherViewsUpdatePerViewId(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_VIEW_SELECTION);
    }
    else
        pSelectionRectangles->push_back(sRect);
}

std::optional<OString> SwSelPaintRects::getLOKPayload(int nType, int nViewId) const
{
    switch( nType )
    {
        case LOK_CALLBACK_TEXT_SELECTION_START:
        case LOK_CALLBACK_TEXT_SELECTION_END:
        {
            // The selection may be a complex polygon, emit the logical
            // start/end cursor rectangle of the selection as separate
            // events, if there is a real selection.
            // This can be used to easily show selection handles on the
            // client side.
            SwRect aStartRect;
            SwRect aEndRect;
            FillStartEnd(aStartRect, aEndRect);

            // no selection rect
            if (!size())
                return {};

            if( nType == LOK_CALLBACK_TEXT_SELECTION_START )
            {
                if (aStartRect.HasArea())
                    return aStartRect.SVRect().toString();
                return {};
            }
            else // LOK_CALLBACK_TEXT_SELECTION_END
            {
                if (aEndRect.HasArea())
                    return aEndRect.SVRect().toString();
                return {};
            }
        }
        break;
        case LOK_CALLBACK_TEXT_SELECTION:
        case LOK_CALLBACK_TEXT_VIEW_SELECTION:
        {
            std::vector<OString> aRect;
            aRect.reserve(size());
            for (size_type i = 0; i < size(); ++i)
            {
                const SwRect& rRect = (*this)[i];
                aRect.push_back(rRect.SVRect().toString());
            }
            OString sRect = comphelper::string::join("; ", aRect);
            if( nType == LOK_CALLBACK_TEXT_SELECTION )
                return sRect;
            else // LOK_CALLBACK_TEXT_VIEW_SELECTION
                return SfxLokHelper::makePayloadJSON(GetShell()->GetSfxViewShell(), nViewId, "selection", sRect);
        }
        break;
    }
    abort();
}

void SwSelPaintRects::HighlightInputField()
{
    std::vector< basegfx::B2DRange > aInputFieldRanges;

    if (m_bShowTextInputFieldOverlay)
    {
        SwTextInputField* pCurTextInputFieldAtCursor =
            dynamic_cast<SwTextInputField*>(SwCursorShell::GetTextFieldAtPos( GetShell()->GetCursor()->Start(), ::sw::GetTextAttrMode::Expand));
        if ( pCurTextInputFieldAtCursor != nullptr )
        {
            SwTextNode* pTextNode = pCurTextInputFieldAtCursor->GetpTextNode();
            std::unique_ptr<SwShellCursor> pCursorForInputTextField(
                new SwShellCursor( *GetShell(), SwPosition( *pTextNode, pCurTextInputFieldAtCursor->GetStart() ) ) );
            pCursorForInputTextField->SetMark();
            pCursorForInputTextField->GetMark()->Assign(*pTextNode, *(pCurTextInputFieldAtCursor->End()) );

            pCursorForInputTextField->FillRects();
            SwRects* pRects = pCursorForInputTextField.get();
            for (const SwRect & rNextRect : *pRects)
            {
                const tools::Rectangle aPntRect(rNextRect.SVRect());

                aInputFieldRanges.emplace_back(
                    aPntRect.Left(), aPntRect.Top(),
                    aPntRect.Right() + 1, aPntRect.Bottom() + 1);
            }
        }
    }

    if ( !aInputFieldRanges.empty() )
    {
        if (m_pTextInputFieldOverlay != nullptr)
        {
            m_pTextInputFieldOverlay->setRanges( std::move(aInputFieldRanges) );
        }
        else
        {
            SdrView* pView = const_cast<SdrView*>(GetShell()->GetDrawView());
            SdrPaintWindow* pCandidate = pView->GetPaintWindow(0);
            const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay = pCandidate->GetOverlayManager();

            if (xTargetOverlay.is())
            {
                // use system's highlight color with decreased luminance as highlight color
                Color aHighlight(SvtOptionsDrawinglayer::getHilightColor());
                aHighlight.DecreaseLuminance( 128 );

                m_pTextInputFieldOverlay.reset( new sw::overlay::OverlayRangesOutline(
                        aHighlight, std::move(aInputFieldRanges) ) );
                xTargetOverlay->add( *m_pTextInputFieldOverlay );
            }
        }
    }
    else
    {
        m_pTextInputFieldOverlay.reset();
    }
}

void SwSelPaintRects::HighlightContentControl()
{
    std::vector<basegfx::B2DRange> aContentControlRanges;
    std::vector<OString> aLOKRectangles;
    SwRect aFirstPortionPaintArea;
    SwRect aLastPortionPaintArea;
    bool bRTL = false;
    std::shared_ptr<SwContentControl> pContentControl;

    if (m_bShowContentControlOverlay)
    {
        const SwPosition* pStart = GetShell()->GetCursor()->Start();
        SwTextNode* pTextNode = pStart->GetNode().GetTextNode();
        SwTextContentControl* pCurContentControlAtCursor = nullptr;
        if (pTextNode)
        {
            // GetTextAttrMode::Parent because this way we highlight when the user will type inside the
            // content control, not outside of it.
            SwTextAttr* pAttr = pTextNode->GetTextAttrAt(
                pStart->GetContentIndex(), RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent);
            if (pAttr)
            {
                pCurContentControlAtCursor = static_txtattr_cast<SwTextContentControl*>(pAttr);
            }
        }
        if (pCurContentControlAtCursor)
        {
            SwShellCursor aCursorForContentControl(
                *GetShell(), SwPosition(*pTextNode, pCurContentControlAtCursor->GetStart()));
            aCursorForContentControl.SetMark();
            aCursorForContentControl.GetMark()->Assign(
                *pTextNode, *(pCurContentControlAtCursor->End()));

            aCursorForContentControl.FillRects();
            SwRects* pRects = &aCursorForContentControl;
            for (const auto& rRect : *pRects)
            {
                tools::Rectangle aRect(rRect.SVRect());

                aContentControlRanges.emplace_back(aRect.Left(), aRect.Top(), aRect.Right() + 1,
                                                   aRect.Bottom() + 1);
                if (comphelper::LibreOfficeKit::isActive())
                {
                    aLOKRectangles.push_back(aRect.toString());
                }
            }

            if (!pRects->empty())
            {
                aFirstPortionPaintArea = (*pRects)[0];
                aLastPortionPaintArea = (*pRects)[pRects->size() - 1];
            }
            pContentControl = pCurContentControlAtCursor->GetContentControl().GetContentControl();

            // The layout knows if the text node is RTL (either set directly, or inherited from the
            // environment).
            SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aFrames(*pTextNode);
            SwTextFrame* pFrame = aFrames.First();
            if (pFrame)
            {
                bRTL = pFrame->IsRightToLeft();
            }
        }
    }

    // clear an obsolete dropdown if the cursor has moved away from the content control
    if (m_pContentControlButton
        && (!pContentControl || m_pContentControlButton->GetContentControl() != pContentControl))
    {
        m_pContentControlButton.disposeAndClear();
    }

    auto pWrtShell = dynamic_cast<const SwWrtShell*>(GetShell());
    if (!aContentControlRanges.empty())
    {
        if (comphelper::LibreOfficeKit::isActive())
        {
            OString aPayload = comphelper::string::join("; ", aLOKRectangles);
            tools::JsonWriter aJson;
            aJson.put("action", "show");
            aJson.put("rectangles", aPayload);

            if (pContentControl && (pContentControl->GetComboBox() || pContentControl->GetDropDown()))
            {
                auto aItems = aJson.startArray("items");
                for (const auto& rItem : pContentControl->GetListItems())
                {
                    aJson.putSimpleValue(rItem.ToString());
                }
            }

            if (pContentControl && pContentControl->GetDate())
            {
                aJson.put("date", "true");
            }

            if (pContentControl && !pContentControl->GetAlias().isEmpty())
            {
                aJson.put("alias", pContentControl->GetAlias());
            }

            OString pJson(aJson.finishAndGetAsOString());
            GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL, pJson);
        }
        if (m_pContentControlOverlay)
        {
            m_pContentControlOverlay->setRanges(std::move(aContentControlRanges));
        }
        else
        {
            SdrView* pView = const_cast<SdrView*>(GetShell()->GetDrawView());
            SdrPaintWindow* pCandidate = pView->GetPaintWindow(0);
            const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay
                = pCandidate->GetOverlayManager();

            if (xTargetOverlay.is())
            {
                // Use the system's highlight color with decreased luminance as highlight color.
                Color aHighlight(SvtOptionsDrawinglayer::getHilightColor());
                aHighlight.DecreaseLuminance(128);

                m_pContentControlOverlay.reset(new sw::overlay::OverlayRangesOutline(
                    aHighlight, std::move(aContentControlRanges)));
                xTargetOverlay->add(*m_pContentControlOverlay);
            }
        }

        if (pContentControl && (pContentControl->GetComboBox() || pContentControl->GetDropDown()))
        {
            if (pWrtShell)
            {
                auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
                if (!m_pContentControlButton)
                {
                    m_pContentControlButton = VclPtr<SwDropDownContentControlButton>::Create(
                        &rEditWin, pContentControl);
                }
                m_pContentControlButton->SetRTL(bRTL);
                if (bRTL)
                {
                    m_pContentControlButton->CalcPosAndSize(aFirstPortionPaintArea);
                }
                else
                {
                    m_pContentControlButton->CalcPosAndSize(aLastPortionPaintArea);
                }
                m_pContentControlButton->Show();
            }
        }
        if (pContentControl && pContentControl->GetDate())
        {
            if (pWrtShell)
            {
                auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
                if (!m_pContentControlButton)
                {
                    m_pContentControlButton = VclPtr<SwDateContentControlButton>::Create(
                        &rEditWin, pContentControl, pWrtShell->GetDoc()->GetNumberFormatter());
                }
                m_pContentControlButton->CalcPosAndSize(aLastPortionPaintArea);
                m_pContentControlButton->Show();
            }
        }

        if (pWrtShell)
        {
            auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
            SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager();
            if (pContentControl && !pContentControl->GetAlias().isEmpty())
            {
                Point aTopLeftPixel = rEditWin.LogicToPixel(aFirstPortionPaintArea.TopLeft());
                rMngr.SetContentControlAliasButton(pContentControl.get(), aTopLeftPixel);
            }
            else
            {
                rMngr.HideControls(FrameControlType::ContentControl);
            }
        }
    }
    else
    {
        if (SfxViewShell* pNotifySh = comphelper::LibreOfficeKit::isActive() && m_pContentControlOverlay ? GetShell()->GetSfxViewShell() : nullptr)
        {
            tools::JsonWriter aJson;
            aJson.put("action", "hide");
            OString pJson(aJson.finishAndGetAsOString());
            pNotifySh->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL, pJson);
        }
        m_pContentControlOverlay.reset();

        if (pWrtShell)
        {
            auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
            SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager();
            rMngr.HideControls(FrameControlType::ContentControl);
        }
    }
}

const VclPtr<SwContentControlButton>& SwSelPaintRects::GetContentControlButton() const
{
    return m_pContentControlButton;
}

void SwSelPaintRects::Invalidate( const SwRect& rRect )
{
    size_type nSz = size();
    if( !nSz )
        return;

    SwRegionRects aReg( GetShell()->VisArea() );
    aReg.assign( begin(), end() );
    aReg -= rRect;
    SwRects::erase( begin(), begin() + nSz );
    SwRects::insert( begin(), aReg.begin(), aReg.end() );

    // If the selection is to the right or at the bottom, outside the
    // visible area, it is never aligned on one pixel at the right/bottom.
    // This has to be determined here and if that is the case the
    // rectangle has to be expanded.
    if( !(GetShell()->m_bVisPortChgd && 0 != ( nSz = size())) )
        return;

    SwSelPaintRects::Get1PixelInLogic( *GetShell() );
    iterator it = begin();
    for( ; nSz--; ++it )
    {
        SwRect& rRectIt = *it;
        if( rRectIt.Right() == GetShell()->m_aOldRBPos.X() )
            rRectIt.AddRight( s_nPixPtX );
        if( rRectIt.Bottom() == GetShell()->m_aOldRBPos.Y() )
            rRectIt.AddBottom( s_nPixPtY );
    }
}

// check current MapMode of the shell and set possibly the static members.
// Optional set the parameters pX, pY
void SwSelPaintRects::Get1PixelInLogic( const SwViewShell& rSh,
                                        tools::Long* pX, tools::Long* pY )
{
    const OutputDevice* pOut = rSh.GetWin()->GetOutDev();
    if ( ! pOut )
        pOut = rSh.GetOut();

    const MapMode& rMM = pOut->GetMapMode();
    if (s_pMapMode->GetMapUnit() != rMM.GetMapUnit() ||
        s_pMapMode->GetScaleX() != rMM.GetScaleX() ||
        s_pMapMode->GetScaleY() != rMM.GetScaleY())
    {
        *s_pMapMode = rMM;
        Size aTmp( 1, 1 );
        aTmp = pOut->PixelToLogic( aTmp );
        s_nPixPtX = aTmp.Width();
        s_nPixPtY = aTmp.Height();
    }
    if( pX )
        *pX = s_nPixPtX;
    if( pY )
        *pY = s_nPixPtY;
}

SwShellCursor::SwShellCursor(
    const SwCursorShell& rCShell,
    const SwPosition &rPos )
    : SwCursor(rPos,nullptr)
    , SwSelPaintRects(rCShell)
    , m_pInitialPoint(SwPaM::GetPoint())
{}

SwShellCursor::SwShellCursor(
    const SwCursorShell& rCShell,
    const SwPosition &rPos,
    const Point& rPtPos,
    SwPaM* pRing )
    : SwCursor(rPos, pRing)
    , SwSelPaintRects(rCShell)
    , m_MarkPt(rPtPos)
    , m_PointPt(rPtPos)
    , m_pInitialPoint(SwPaM::GetPoint())
{}

SwShellCursor::SwShellCursor( SwShellCursor& rICursor )
    : SwCursor(rICursor, &rICursor)
    , SwSelPaintRects(*rICursor.GetShell())
    , m_MarkPt(rICursor.GetMkPos())
    , m_PointPt(rICursor.GetPtPos())
    , m_pInitialPoint(SwPaM::GetPoint())
{}

SwShellCursor::~SwShellCursor()
{}

bool SwShellCursor::IsReadOnlyAvailable() const
{
    return GetShell()->IsReadOnlyAvailable();
}

void SwShellCursor::SetMark()
{
    if (SwPaM::GetPoint() == m_pInitialPoint)
        m_MarkPt = m_PointPt;
    else
        m_PointPt = m_MarkPt;
    SwPaM::SetMark();
}

void SwShellCursor::FillRects()
{
    // calculate the new rectangles
    if( HasMark() &&
        GetPoint()->GetNode().IsContentNode() &&
        GetPoint()->GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) &&
        (GetMark()->GetNode() == GetPoint()->GetNode() ||
        (GetMark()->GetNode().IsContentNode() &&
         GetMark()->GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) )   ))
    {
        GetShell()->GetLayout()->CalcFrameRects(*this, *this);
    }
#if ENABLE_YRS
    if (!HasMark())
    {
        if (SwVisibleCursor *const pVisibleCursor{GetShell()->FindVisibleCursorForPeer(*this)})
        {
            // use OutDev.GetSettings().GetStyleSettings().GetCursorSize() as width?
            auto cursorRect{ pVisibleCursor->SetPos() };
            if (cursorRect.IsEmpty())
            {
                cursorRect.Width(20);
            }
            SAL_INFO("sw.yrs", "YRS FillRects extra rect " << cursorRect);
            emplace_back(cursorRect);
            SwRect const temp{Point{cursorRect.Left() - cursorRect.Height()/2,
                cursorRect.Bottom()}, Size{cursorRect.Height() + 20, 20}};
            emplace_back(temp);
        }
    }
#endif
}

void SwShellCursor::Show(SfxViewShell const * pViewShell)
{
    std::vector<OString> aSelectionRectangles;
    for(SwPaM& rPaM : GetRingContainer())
    {
        SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rPaM);
        if(pShCursor)
            pShCursor->SwSelPaintRects::Show(&aSelectionRectangles);
    }

    if (!comphelper::LibreOfficeKit::isActive())
        return;

    std::vector<OString> aRect;
    for (const OString & rSelectionRectangle : aSelectionRectangles)
    {
        if (rSelectionRectangle.isEmpty())
            continue;
        aRect.push_back(rSelectionRectangle);
    }
    OString sRect = comphelper::string::join("; ", aRect);
    if (pViewShell)
    {
        // Just notify pViewShell about our existing selection.
        if (pViewShell != GetShell()->GetSfxViewShell())
            SfxLokHelper::notifyOtherView(*GetShell()->GetSfxViewShell(), pViewShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect);
    }
    else
    {
        const SwCursorShell* pShell = GetShell();
        if (!pShell)
        {
            return;
        }

        SfxViewShell* pSfxViewShell = pShell->GetSfxViewShell();
        if (!pSfxViewShell)
        {
            return;
        }

        pSfxViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRect);
        SfxLokHelper::notifyOtherViews(pSfxViewShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect);
    }
}

// This rectangle gets painted anew, therefore the SSelection in this
// area is invalid.
void SwShellCursor::Invalidate( const SwRect& rRect )
{
    for(SwPaM& rPaM : GetRingContainer())
    {
        SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rPaM);
        // skip any non SwShellCursor objects in the ring
        // see also: SwAutoFormat::DeleteSel()
        if(pShCursor)
            pShCursor->SwSelPaintRects::Invalidate(rRect);
    }
}

void SwShellCursor::Hide()
{
    for(SwPaM& rPaM : GetRingContainer())
    {
        SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rPaM);
        if(pShCursor)
            pShCursor->SwSelPaintRects::Hide();
    }
}

SwCursor* SwShellCursor::Create( SwPaM* pRing ) const
{
    return new SwShellCursor( *GetShell(), *GetPoint(), GetPtPos(), pRing );
}

short SwShellCursor::MaxReplaceArived()
{
    short nRet = RET_YES;
    SvxSearchDialog* pDlg = SwView::GetSearchDialog();
    if( pDlg )
    {
        // Terminate old actions. The table-frames get constructed and
        // a SSelection can be created.
        std::vector<sal_uInt16> vActionCounts;
        for(SwViewShell& rShell : const_cast< SwCursorShell* >( GetShell() )->GetRingContainer())
        {
            sal_uInt16 nActCnt = 0;
            while(rShell.ActionPend())
            {
                rShell.EndAction();
                ++nActCnt;
            }
            vActionCounts.push_back(nActCnt);
        }
        std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pDlg->getDialog(), u"modules/swriter/ui/asksearchdialog.ui"_ustr));
        std::unique_ptr<weld::MessageDialog> xDialog(xBuilder->weld_message_dialog(u"AskSearchDialog"_ustr));
        nRet = xDialog->run();
        auto pActionCount = vActionCounts.begin();
        for(SwViewShell& rShell : const_cast< SwCursorShell* >( GetShell() )->GetRingContainer())
        {
            while(*pActionCount)
            {
                rShell.StartAction();
                --(*pActionCount);
            }
            ++pActionCount;
        }
    }
    else
        // otherwise from the Basic, and then switch to RET_YES
        nRet = RET_YES;

    return nRet;
}

void SwShellCursor::SaveTableBoxContent( const SwPosition* pPos )
{
    const_cast<SwCursorShell*>(GetShell())->SaveTableBoxContent( pPos );
}

bool SwShellCursor::UpDown( bool bUp, sal_uInt16 nCnt )
{
    return SwCursor::UpDown( bUp, nCnt,
                            &GetPtPos(), GetShell()->GetUpDownX(),
                            *GetShell()->GetLayout());
}

// if <true> than the cursor can be set to the position.
bool SwShellCursor::IsAtValidPos( bool bPoint ) const
{
    if( GetShell() && ( GetShell()->IsAllProtect() ||
        GetShell()->GetViewOptions()->IsReadonly() ||
        ( GetShell()->Imp()->GetDrawView() &&
          GetShell()->Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() )))
        return true;

    return SwCursor::IsAtValidPos( bPoint );
}

SwShellTableCursor::SwShellTableCursor( const SwCursorShell& rCursorSh,
                                    const SwPosition& rPos )
    : SwCursor(rPos,nullptr), SwShellCursor(rCursorSh, rPos), SwTableCursor(rPos)
{
}

SwShellTableCursor::SwShellTableCursor( const SwCursorShell& rCursorSh,
                    const SwPosition& rMkPos, const Point& rMkPt,
                    const SwPosition& rPtPos, const Point& rPtPt )
    : SwCursor(rPtPos,nullptr), SwShellCursor(rCursorSh, rPtPos), SwTableCursor(rPtPos)
{
    SetMark();
    *GetMark() = rMkPos;
    GetMkPos() = rMkPt;
    GetPtPos() = rPtPt;
}

SwShellTableCursor::~SwShellTableCursor() {}

void SwShellTableCursor::SetMark()                { SwShellCursor::SetMark(); }

SwCursor* SwShellTableCursor::Create( SwPaM* pRing ) const
{
    return SwShellCursor::Create( pRing );
}

short SwShellTableCursor::MaxReplaceArived()
{
    return SwShellCursor::MaxReplaceArived();
}

void SwShellTableCursor::SaveTableBoxContent( const SwPosition* pPos )
{
    SwShellCursor::SaveTableBoxContent( pPos );
}

void SwShellTableCursor::FillRects()
{
    // Calculate the new rectangles. If the cursor is still "parked" do nothing
    if (m_SelectedBoxes.empty() || m_bParked || !GetPoint()->GetNodeIndex())
        return;

    bool bStart = true;
    SwRegionRects aReg( comphelper::LibreOfficeKit::isActive()
        ? GetShell()->getIDocumentLayoutAccess().GetCurrentLayout()->getFrameArea()
        : GetShell()->VisArea() );
    SwFrame* pEndFrame = nullptr;
    for (size_t n = 0; n < m_SelectedBoxes.size(); ++n)
    {
        const SwStartNode* pSttNd = m_SelectedBoxes[n]->GetSttNd();
        const SwTableNode* pSelTableNd = pSttNd->FindTableNode();

        SwNodeIndex aIdx( *pSttNd );
        SwContentNode* pCNd = SwNodes::GoNextSection(&aIdx, true, false);

        // table in table
        // (see also lcl_FindTopLevelTable in unoobj2.cxx for a different
        // version to do this)
        const SwTableNode* pCurTableNd = pCNd ? pCNd->FindTableNode() : nullptr;
        while ( pSelTableNd != pCurTableNd && pCurTableNd )
        {
            aIdx = pCurTableNd->EndOfSectionIndex();
            pCNd = SwNodes::GoNextSection(&aIdx, true, false);
            pCurTableNd = pCNd->FindTableNode();
        }

        if( !pCNd )
            continue;

        std::pair<Point, bool> const tmp(GetSttPos(), false);
        SwFrame* pFrame = pCNd->getLayoutFrame(GetShell()->GetLayout(), nullptr, &tmp);
        while( pFrame && !pFrame->IsCellFrame() )
            pFrame = pFrame->GetUpper();

        OSL_ENSURE( pFrame, "Node not in a table" );

        while ( pFrame )
        {
            if( aReg.GetOrigin().Overlaps( pFrame->getFrameArea() ) )
            {
                aReg -= pFrame->getFrameArea();
                if (bStart)
                {
                    bStart = false;
                    m_aStart = SwRect(pFrame->getFrameArea().Left(), pFrame->getFrameArea().Top(), 1, pFrame->getFrameArea().Height());
                }
            }

            pEndFrame = pFrame;
            pFrame = pFrame->GetNextCellLeaf();
        }
    }
    if (pEndFrame)
        m_aEnd = SwRect(pEndFrame->getFrameArea().Right(), pEndFrame->getFrameArea().Top(), 1, pEndFrame->getFrameArea().Height());
    aReg.Invert();
    insert( begin(), aReg.begin(), aReg.end() );
}

void SwShellTableCursor::FillStartEnd(SwRect& rStart, SwRect& rEnd) const
{
    rStart = m_aStart;
    rEnd = m_aEnd;
}

// Check if the SPoint is within the Table-SSelection.
bool SwShellTableCursor::Contains( const Point& rPt ) const
{
    // Calculate the new rectangles. If the cursor is still "parked" do nothing
    if (m_SelectedBoxes.empty() || m_bParked || !GetPoint()->GetNodeIndex())
        return false;

    for (size_t n = 0; n < m_SelectedBoxes.size(); ++n)
    {
        SwNodeIndex aIdx( *m_SelectedBoxes[n]->GetSttNd() );
        SwContentNode* pCNd = SwNodes::GoNextSection(&aIdx, true, false);
        if( !pCNd )
            continue;

        std::pair<Point, bool> const tmp(GetPtPos(), true);
        SwFrame* pFrame = pCNd->getLayoutFrame(GetShell()->GetLayout(), nullptr, &tmp);
        while( pFrame && !pFrame->IsCellFrame() )
            pFrame = pFrame->GetUpper();
        OSL_ENSURE( pFrame, "Node not in a table" );
        if( pFrame && pFrame->getFrameArea().Contains( rPt ) )
            return true;

        for ( SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(pFrame); pCellFrame; pCellFrame = pCellFrame->GetFollowCell() )
        {
            if( pCellFrame->getFrameArea().Contains( rPt ) )
                return true;
        }
    }
    return false;
}

bool SwShellTableCursor::IsAtValidPos( bool bPoint ) const
{
    return SwShellCursor::IsAtValidPos( bPoint );
}

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