/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <sal/config.h>

#include <cassert>
#include <string_view>

#include <helpids.h>
#include <iderid.hxx>
#include <strings.hrc>
#include <bitmaps.hlst>

#include "baside2.hxx"
#include "brkdlg.hxx"
#include <basidesh.hxx>
#include <basobj.hxx>
#include <iderdll.hxx>

#include <basctl/basctldllpublic.hxx>
#include <basic/sbmeth.hxx>
#include <basic/sbuno.hxx>
#include <com/sun/star/beans/XMultiPropertySet.hpp>
#include <com/sun/star/beans/XPropertiesChangeListener.hpp>
#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
#include <com/sun/star/script/XLibraryContainer2.hpp>
#include <com/sun/star/script/XStorageBasedLibraryContainer.hpp>
#include <comphelper/string.hxx>
#include <o3tl/string_view.hxx>
#include <officecfg/Office/Common.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/progress.hxx>
#include <sfx2/viewfrm.hxx>
#include <tools/debug.hxx>
#include <utility>
#include <vcl/image.hxx>
#include <vcl/weld.hxx>
#include <vcl/weldutils.hxx>
#include <svl/urihelper.hxx>
#include <svx/svxids.hrc>
#include <vcl/commandevent.hxx>
#include <vcl/xtextedt.hxx>
#include <vcl/textview.hxx>
#include <vcl/txtattr.hxx>
#include <vcl/settings.hxx>
#include <vcl/ptrstyle.hxx>
#include <vcl/event.hxx>
#include <vcl/svapp.hxx>
#include <vcl/taskpanelist.hxx>
#include <vcl/help.hxx>
#include <cppuhelper/implbase.hxx>
#include <vector>
#include <com/sun/star/reflection/theCoreReflection.hpp>
#include <unotools/charclass.hxx>
#include "textwindowaccessibility.hxx"
#include "uiobject.hxx"
#include <basegfx/utils/zoomtools.hxx>
#include <svl/itemset.hxx>
#include <BasicColorConfig.hxx>

namespace basctl
{

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::cpo::uno;

namespace
{

sal_uInt16 const NoMarker = 0xFFFF;
tools::Long const nBasePad = 2;
tools::Long const nCursorPad = 5;

tools::Long nVirtToolBoxHeight;    // inited in WatchWindow, used in Stackwindow

// Returns pBase converted to SbxVariable if valid and is not an SbxMethod.
SbxVariable* IsSbxVariable (SbxBase* pBase)
{
    if (SbxVariable* pVar = dynamic_cast<SbxVariable*>(pBase))
        if (!dynamic_cast<SbxMethod*>(pVar))
            return pVar;
    return nullptr;
}

Image GetImage(const OUString& rId)
{
    return Image(StockImage::Yes, rId);
}

int const nScrollLine = 12;
int const nScrollPage = 60;
int const DWBORDER = 3;

std::u16string_view const cSuffixes = u"%&!#@$";

} // namespace


/**
 * Helper functions to get/set text in TextEngine using
 * the stream interface.
 *
 * get/setText() only supports tools Strings limited to 64K).
 */
OUString getTextEngineText (ExtTextEngine& rEngine)
{
    SvMemoryStream aMemStream;
    aMemStream.SetStreamEncoding( RTL_TEXTENCODING_UTF8 );
    aMemStream.SetLineDelimiter( LINEEND_LF );
    rEngine.Write( aMemStream );
    std::size_t nSize = aMemStream.Tell();
    OUString aText( static_cast<const char*>(aMemStream.GetData()),
        nSize, RTL_TEXTENCODING_UTF8 );
    return aText;
}

void setTextEngineText (ExtTextEngine& rEngine, std::u16string_view aStr)
{
    rEngine.SetText(OUString());
    OString aUTF8Str = OUStringToOString( aStr, RTL_TEXTENCODING_UTF8 );
    SvMemoryStream aMemStream( const_cast<char *>(aUTF8Str.getStr()), aUTF8Str.getLength(),
        StreamMode::READ );
    aMemStream.SetStreamEncoding( RTL_TEXTENCODING_UTF8 );
    aMemStream.SetLineDelimiter( LINEEND_LF );
    rEngine.Read(aMemStream);
}

namespace
{

void lcl_DrawIDEWindowFrame(DockingWindow const * pWin, vcl::RenderContext& rRenderContext)
{
    if (pWin->IsFloatingMode())
        return;

    Size aSz(pWin->GetOutputSizePixel());
    const Color aOldLineColor(rRenderContext.GetLineColor());
    rRenderContext.SetLineColor(COL_WHITE);
    // White line on top
    rRenderContext.DrawLine(Point(0, 0), Point(aSz.Width(), 0));
    // Black line at bottom
    rRenderContext.SetLineColor(COL_BLACK);
    rRenderContext.DrawLine(Point(0, aSz.Height() - 1),
                            Point(aSz.Width(), aSz.Height() - 1));
    rRenderContext.SetLineColor(aOldLineColor);
}

void lcl_SeparateNameAndIndex( const OUString& rVName, OUString& rVar, OUString& rIndex )
{
    rVar = rVName;
    rIndex.clear();
    sal_Int32 nIndexStart = rVar.indexOf( '(' );
    if ( nIndexStart != -1 )
    {
        sal_Int32 nIndexEnd = rVar.indexOf( ')', nIndexStart );
        if (nIndexEnd != -1)
        {
            rIndex = rVar.copy(nIndexStart + 1, nIndexEnd - nIndexStart - 1);
            rVar = rVar.copy(0, nIndexStart);
            rVar = comphelper::string::stripEnd(rVar, ' ');
            rIndex = comphelper::string::strip(rIndex, ' ');
        }
    }

    if ( !rVar.isEmpty() )
    {
        sal_uInt16 nLastChar = rVar.getLength()-1;
        if ( cSuffixes.find(rVar[ nLastChar ] ) != std::u16string_view::npos )
            rVar = rVar.replaceAt( nLastChar, 1, u"" );
    }
    if ( !rIndex.isEmpty() )
    {
        sal_uInt16 nLastChar = rIndex.getLength()-1;
        if ( cSuffixes.find(rIndex[ nLastChar ] ) != std::u16string_view::npos )
            rIndex = rIndex.replaceAt( nLastChar, 1, u"" );
    }
}

} // namespace


// EditorWindow


class EditorWindow::ChangesListener:
    public cppu::WeakImplHelper< beans::XPropertiesChangeListener >
{
public:
    explicit ChangesListener(EditorWindow & editor): editor_(editor) {}

private:
    virtual ~ChangesListener() override {}

    virtual void disposing(lang::EventObject const &) override
    {
        std::unique_lock g(editor_.mutex_);
        editor_.notifier_.clear();
    }

    virtual void propertiesChange(
        Sequence< beans::PropertyChangeEvent > const &) override
    {
        SolarMutexGuard g;
        editor_.ImplSetFont();
    }

    EditorWindow & editor_;
};

class EditorWindow::ProgressInfo : public SfxProgress
{
public:
    ProgressInfo (SfxObjectShell* pObjSh, OUString const& rText, sal_uInt32 nRange) :
        SfxProgress(pObjSh, rText, nRange),
        nCurState(0)
    { }

    void StepProgress ()
    {
        SetState(++nCurState);
    }

private:
    sal_uInt32 nCurState;
};

EditorWindow::EditorWindow (vcl::Window* pParent, ModulWindow* pModulWindow) :
    Window(pParent, WB_BORDER),
    rModulWindow(*pModulWindow),
    nCurTextWidth(0),
    m_nSetSourceInBasicId(nullptr),
    aHighlighter(HighlighterLanguage::Basic),
    aSyntaxIdle( "basctl EditorWindow aSyntaxIdle" ),
    bHighlighting(false),
    bDoSyntaxHighlight(true),
    bDelayHighlight(true),
    m_nLastHighlightPara(0),
    pCodeCompleteWnd(VclPtr<CodeCompleteWindow>::Create(this))
{
    set_id(u"EditorWindow"_ustr);
    const Wallpaper aBackground(rModulWindow.GetLayout().GetSyntaxBackgroundColor());
    SetBackground(aBackground);
    GetWindow(GetWindowType::Border)->SetBackground(aBackground);
    SetLineHighlightColor(GetShell()->GetColorConfig()->GetCurrentColorScheme().m_aLineHighlightColor);
    SetPointer( PointerStyle::Text );
    SetHelpId( HID_BASICIDE_EDITORWINDOW );

    listener_ = new ChangesListener(*this);
    Reference< beans::XMultiPropertySet > n(
        officecfg::Office::Common::Font::SourceViewFont::get(),
        UNO_QUERY_THROW);
    {
        std::unique_lock g(mutex_);
        notifier_ = n;
    }

    // The zoom level applied to the editor window is the zoom slider value in the shell
    nCurrentZoomLevel = GetShell()->GetCurrentZoomSliderValue();

    const Sequence<OUString> aPropertyNames{u"FontHeight"_ustr, u"FontName"_ustr};
    n->addPropertiesChangeListener(aPropertyNames, listener_);
}


EditorWindow::~EditorWindow()
{
    disposeOnce();
}

void EditorWindow::dispose()
{
    if (m_nSetSourceInBasicId)
    {
        Application::RemoveUserEvent(m_nSetSourceInBasicId);
        m_nSetSourceInBasicId = nullptr;
    }

    Reference< beans::XMultiPropertySet > n;
    {
        std::unique_lock g(mutex_);
        n = notifier_;
    }
    if (n.is()) {
        n->removePropertiesChangeListener(listener_);
    }

    aSyntaxIdle.Stop();

    if ( pEditEngine )
    {
        EndListening( *pEditEngine );
        pEditEngine->RemoveView(pEditView.get());
    }
    pCodeCompleteWnd.disposeAndClear();
    vcl::Window::dispose();
}

OUString EditorWindow::GetWordAtCursor()
{
    OUString aWord;

    if ( pEditView )
    {
        TextEngine* pTextEngine = pEditView->GetTextEngine();
        if ( pTextEngine )
        {
            // check first, if the cursor is at a help URL
            const TextSelection& rSelection = pEditView->GetSelection();
            const TextPaM& rSelStart = rSelection.GetStart();
            const TextPaM& rSelEnd = rSelection.GetEnd();
            OUString aText = pTextEngine->GetText( rSelEnd.GetPara() );
            CharClass aClass( ::comphelper::getProcessComponentContext() , Application::GetSettings().GetLanguageTag() );
            sal_Int32 nSelStart = rSelStart.GetIndex();
            sal_Int32 nSelEnd = rSelEnd.GetIndex();
            sal_Int32 nLength = aText.getLength();
            sal_Int32 nStart = 0;
            sal_Int32 nEnd = nLength;
            while ( nStart < nLength )
            {
                OUString aURL( URIHelper::FindFirstURLInText( aText, nStart, nEnd, aClass ) );
                INetURLObject aURLObj( aURL );
                if ( aURLObj.GetProtocol() == INetProtocol::VndSunStarHelp
                     && nSelStart >= nStart && nSelStart <= nEnd && nSelEnd >= nStart && nSelEnd <= nEnd )
                {
                    aWord = aURL;
                    break;
                }
                nStart = nEnd;
                nEnd = nLength;
            }

            // Not the selected range, but at the CursorPosition,
            // if a word is partially selected.
            if ( aWord.isEmpty() )
                aWord = pTextEngine->GetWord( rSelEnd );

            // Can be empty when full word selected, as Cursor behind it
            if ( aWord.isEmpty() && pEditView->HasSelection() )
                aWord = pTextEngine->GetWord( rSelStart );
        }
    }

    return aWord;
}

void EditorWindow::RequestHelp( const HelpEvent& rHEvt )
{
    bool bDone = false;

    // Should have been activated at some point
    if ( pEditEngine )
    {
        if ( rHEvt.GetMode() & HelpEventMode::CONTEXT )
        {
            OUString aKeyword = GetWordAtCursor();
            Application::GetHelp()->SearchKeyword( aKeyword );
            bDone = true;
        }
        else if ( rHEvt.GetMode() & HelpEventMode::QUICK )
        {
            OUString aHelpText;
            tools::Rectangle aHelpRect;
            if ( StarBASIC::IsRunning() )
            {
                Point aWindowPos = rHEvt.GetMousePosPixel();
                aWindowPos = ScreenToOutputPixel( aWindowPos );
                Point aDocPos = GetEditView()->GetDocPos( aWindowPos );
                TextPaM aCursor = GetEditView()->GetTextEngine()->GetPaM(aDocPos);
                TextPaM aStartOfWord;
                OUString aWord = GetEditView()->GetTextEngine()->GetWord( aCursor, &aStartOfWord );
                if ( !aWord.isEmpty() && !comphelper::string::isdigitAsciiString(aWord) )
                {
                    sal_uInt16 nLastChar = aWord.getLength() - 1;
                    if ( cSuffixes.find(aWord[ nLastChar ] ) != std::u16string_view::npos )
                        aWord = aWord.replaceAt( nLastChar, 1, u"" );
                    SbxBase* pSBX = StarBASIC::FindSBXInCurrentScope( aWord );
                    if (SbxVariable const* pVar = IsSbxVariable(pSBX))
                    {
                        SbxDataType eType = pVar->GetType();
                        if ( static_cast<sal_uInt8>(eType) == sal_uInt8(SbxOBJECT) )
                            // might cause a crash e. g. at the selections-object
                            // Type == Object does not mean pVar == Object!
                            ; // aHelpText = ((SbxObject*)pVar)->GetClassName();
                        else if ( eType & SbxARRAY )
                            ; // aHelpText = "{...}";
                        else if ( static_cast<sal_uInt8>(eType) != sal_uInt8(SbxEMPTY) )
                        {
                            aHelpText = pVar->GetName();
                            if ( aHelpText.isEmpty() )     // name is not copied with the passed parameters
                                aHelpText = aWord;
                            aHelpText += "=" + pVar->GetOUString();
                        }
                    }
                    if ( !aHelpText.isEmpty() )
                    {
                        tools::Rectangle aStartWordRect(GetEditView()->GetTextEngine()->PaMtoEditCursor(aStartOfWord));
                        TextPaM aEndOfWord(aStartOfWord.GetPara(), aStartOfWord.GetIndex() + aWord.getLength());
                        tools::Rectangle aEndWordRect(GetEditView()->GetTextEngine()->PaMtoEditCursor(aEndOfWord));
                        aHelpRect = aStartWordRect.GetUnion(aEndWordRect);

                        Point aTopLeft = GetEditView()->GetWindowPos(aHelpRect.TopLeft());
                        aTopLeft = GetEditView()->GetWindow()->OutputToScreenPixel(aTopLeft);

                        aHelpRect.SetPos(aTopLeft);
                    }
                }
            }
            Help::ShowQuickHelp( this, aHelpRect, aHelpText, QuickHelpFlags::NONE);
            bDone = true;
        }
    }

    if ( !bDone )
        Window::RequestHelp( rHEvt );
}


void EditorWindow::Resize()
{
    // ScrollBars, etc. happens in Adjust...
    if ( !pEditView )
        return;

    tools::Long nVisY = pEditView->GetStartDocPos().Y();

    pEditView->ShowCursor();
    Size aOutSz( GetOutputSizePixel() );
    tools::Long nMaxVisAreaStart = pEditView->GetTextEngine()->GetTextHeight() - aOutSz.Height();
    if ( nMaxVisAreaStart < 0 )
        nMaxVisAreaStart = 0;
    if ( pEditView->GetStartDocPos().Y() > nMaxVisAreaStart )
    {
        Point aStartDocPos( pEditView->GetStartDocPos() );
        aStartDocPos.setY( nMaxVisAreaStart );
        pEditView->SetStartDocPos( aStartDocPos );
        pEditView->ShowCursor();
        rModulWindow.GetBreakPointWindow().GetCurYOffset() = aStartDocPos.Y();
        rModulWindow.GetLineNumberWindow().GetCurYOffset() = aStartDocPos.Y();
    }
    InitScrollBars();
    if ( nVisY != pEditView->GetStartDocPos().Y() )
        Invalidate();
}


void EditorWindow::MouseMove( const MouseEvent &rEvt )
{
    if ( pEditView )
        pEditView->MouseMove( rEvt );
}


void EditorWindow::MouseButtonUp( const MouseEvent &rEvt )
{
    if ( pEditView )
    {
        pEditView->MouseButtonUp( rEvt );
        if (SfxBindings* pBindings = GetBindingsPtr())
        {
            pBindings->Invalidate( SID_BASICIDE_STAT_POS );
            pBindings->Invalidate( SID_BASICIDE_STAT_TITLE );
        }
    }
}

void EditorWindow::MouseButtonDown( const MouseEvent &rEvt )
{
    GrabFocus();
    if (!pEditView)
        return;
    pEditView->MouseButtonDown(rEvt);
    if( pCodeCompleteWnd->IsVisible() )
    {
        if (pEditView->GetSelection() != pCodeCompleteWnd->GetTextSelection())
        {
            //selection changed, code complete window should be hidden
            pCodeCompleteWnd->HideAndRestoreFocus();
        }
    }
}

void EditorWindow::Command( const CommandEvent& rCEvt )
{
    if ( !pEditView )
        return;

    pEditView->Command( rCEvt );
    if ( ( rCEvt.GetCommand() == CommandEventId::Wheel ) ||
         ( rCEvt.GetCommand() == CommandEventId::StartAutoScroll ) ||
         ( rCEvt.GetCommand() == CommandEventId::AutoScroll ) )
    {
        const CommandWheelData* pData = rCEvt.GetWheelData();

        // Check if it is a Ctrl+Wheel zoom command
        if (pData && pData->IsMod1())
        {
            const sal_uInt16 nOldZoom = GetCurrentZoom();
            sal_uInt16 nNewZoom;
            if( pData->GetDelta() < 0 )
                nNewZoom = std::max<sal_uInt16>(basctl::Shell::GetMinZoom(),
                                                basegfx::zoomtools::zoomOut(nOldZoom));
            else
                nNewZoom = std::min<sal_uInt16>(basctl::Shell::GetMaxZoom(),
                                                basegfx::zoomtools::zoomIn(nOldZoom));
            GetShell()->SetGlobalEditorZoomLevel(nNewZoom);
        }
        else
            HandleScrollCommand(rCEvt, &rModulWindow.GetEditHScrollBar(), &rModulWindow.GetEditVScrollBar());
    }
    else if ( rCEvt.GetCommand() == CommandEventId::ContextMenu ) {
        SfxDispatcher* pDispatcher = GetDispatcher();
        if ( pDispatcher )
        {
            SfxDispatcher::ExecutePopup();
        }
        if( pCodeCompleteWnd->IsVisible() ) // hide the code complete window
            pCodeCompleteWnd->ClearAndHide();
    }
}

bool EditorWindow::ImpCanModify()
{
    bool bCanModify = true;
    if ( StarBASIC::IsRunning() && rModulWindow.GetBasicStatus().bIsRunning )
    {
        // If in Trace-mode, abort the trace or refuse input
        // Remove markers in the modules in Notify at Basic::Stopped
        std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
                                                       VclMessageType::Question, VclButtonsType::OkCancel,
                                                       IDEResId(RID_STR_WILLSTOPPRG)));
        if (xQueryBox->run() == RET_OK)
        {
            rModulWindow.GetBasicStatus().bIsRunning = false;
            StopBasic();
        }
        else
            bCanModify = false;
    }
    return bCanModify;
}

void EditorWindow::KeyInput( const KeyEvent& rKEvt )
{
    if ( !pEditView )   // Happens in Win95
        return;

    bool const bWasModified = pEditEngine->IsModified();
    // see if there is an accelerator to be processed first
    SfxViewShell *pVS( SfxViewShell::Current());
    bool bDone = pVS && pVS->KeyInput( rKEvt );

    if (pCodeCompleteWnd->IsVisible() && CodeCompleteOptions::IsCodeCompleteOn())
    {
        pCodeCompleteWnd->HandleKeyInput(rKEvt);
        if( rKEvt.GetKeyCode().GetCode() == KEY_UP
            || rKEvt.GetKeyCode().GetCode() == KEY_DOWN
            || rKEvt.GetKeyCode().GetCode() == KEY_TAB
            || rKEvt.GetKeyCode().GetCode() == KEY_POINT)
            return;
    }

    if( (rKEvt.GetKeyCode().GetCode() == KEY_SPACE ||
        rKEvt.GetKeyCode().GetCode() == KEY_TAB ||
        rKEvt.GetKeyCode().GetCode() == KEY_RETURN ) && CodeCompleteOptions::IsAutoCorrectOn() )
    {
        HandleAutoCorrect();
    }

    if( rKEvt.GetCharCode() == '"' && CodeCompleteOptions::IsAutoCloseQuotesOn() )
    {//autoclose double quotes
        HandleAutoCloseDoubleQuotes();
    }

    if( rKEvt.GetCharCode() == '(' && CodeCompleteOptions::IsAutoCloseParenthesisOn() )
    {//autoclose parenthesis
        HandleAutoCloseParen();
    }

    if( rKEvt.GetKeyCode().GetCode() == KEY_RETURN && CodeCompleteOptions::IsProcedureAutoCompleteOn() )
    {//autoclose implementation
       HandleProcedureCompletion();
    }

    if( rKEvt.GetKeyCode().GetCode() == KEY_POINT && CodeCompleteOptions::IsCodeCompleteOn() )
    {
        HandleCodeCompletion();
    }
    if ( !bDone && ( !TextEngine::DoesKeyChangeText( rKEvt ) || ImpCanModify()  ) )
    {
        if ( ( rKEvt.GetKeyCode().GetCode() == KEY_TAB ) && !rKEvt.GetKeyCode().IsMod1() &&
              !rKEvt.GetKeyCode().IsMod2() && !GetEditView()->IsReadOnly() )
        {
            TextSelection aSel( pEditView->GetSelection() );
            if ( aSel.GetStart().GetPara() != aSel.GetEnd().GetPara() )
            {
                bDelayHighlight = false;
                if ( !rKEvt.GetKeyCode().IsShift() )
                    pEditView->IndentBlock();
                else
                    pEditView->UnindentBlock();
                bDelayHighlight = true;
                bDone = true;
            }
        }
        if ( !bDone )
            bDone = pEditView->KeyInput( rKEvt );
    }
    if ( !bDone )
    {
            Window::KeyInput( rKEvt );
    }
    else
    {
        if (SfxBindings* pBindings = GetBindingsPtr())
        {
            pBindings->Invalidate( SID_BASICIDE_STAT_POS );
            pBindings->Invalidate( SID_BASICIDE_STAT_TITLE );
            if ( rKEvt.GetKeyCode().GetGroup() == KEYGROUP_CURSOR )
            {
                pBindings->Update( SID_BASICIDE_STAT_POS );
                pBindings->Update( SID_BASICIDE_STAT_TITLE );
            }
            if ( rKEvt.GetKeyCode().GetGroup() == KEYGROUP_ALPHA ||
                 rKEvt.GetKeyCode().GetGroup() == KEYGROUP_NUM )
            {
                // If the module is read-only, warn that it can't be edited
                if ( rModulWindow.IsReadOnly() )
                    rModulWindow.ShowReadOnlyInfoBar();
            }
            if ( !bWasModified && pEditEngine->IsModified() )
            {
                pBindings->Invalidate( SID_SAVEDOC );
                pBindings->Invalidate( SID_DOC_MODIFIED );
                pBindings->Invalidate( SID_UNDO );
            }
            if ( rKEvt.GetKeyCode().GetCode() == KEY_INSERT )
                pBindings->Invalidate( SID_ATTR_INSERT );
        }
    }
}

void EditorWindow::HandleAutoCorrect()
{
    TextSelection aSel = GetEditView()->GetSelection();
    const sal_uInt32 nLine =  aSel.GetStart().GetPara();
    const sal_Int32 nIndex =  aSel.GetStart().GetIndex();
    OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified
    const OUString sActSubName = GetActualSubName( nLine ); // the actual procedure

    std::vector<HighlightPortion> aPortions;
    aHighlighter.getHighlightPortions( aLine, aPortions );

    if( aPortions.empty() )
        return;

    HighlightPortion& r = aPortions.back();
    if( static_cast<size_t>(nIndex) != aPortions.size()-1 )
    {//cursor is not standing at the end of the line
        for (auto const& portion : aPortions)
        {
            if( portion.nEnd == nIndex )
            {
                r = portion;
                break;
            }
        }
    }

    OUString sStr = aLine.copy( r.nBegin, r.nEnd - r.nBegin );
    //if WS or empty string: stop, nothing to do
    if( ( r.tokenType == TokenType::Whitespace ) || sStr.isEmpty() )
        return;
    //create the appropriate TextSelection, and update the cache
    TextPaM aStart( nLine, r.nBegin );
    TextPaM aEnd( nLine, r.nBegin + sStr.getLength() );
    TextSelection sTextSelection( aStart, aEnd );
    rModulWindow.UpdateModule();
    rModulWindow.GetSbModule()->GetCodeCompleteDataFromParse( aCodeCompleteCache );
    // correct the last entered keyword
    if( r.tokenType == TokenType::Keywords )
    {
        sStr = sStr.toAsciiLowerCase();
        if( !SbModule::GetKeywordCase(sStr).isEmpty() )
        // if it is a keyword, get its correct case
            sStr = SbModule::GetKeywordCase(sStr);
        else
        // else capitalize first letter/select the correct one, and replace
            sStr = sStr.replaceAt( 0, 1, OUString(sStr[0]).toAsciiUpperCase() );

        pEditEngine->ReplaceText( sTextSelection, sStr );
        pEditView->SetSelection( aSel );
    }
    if( r.tokenType != TokenType::Identifier )
        return;

// correct variables
    if( !aCodeCompleteCache.GetCorrectCaseVarName( sStr, sActSubName ).isEmpty() )
    {
        sStr = aCodeCompleteCache.GetCorrectCaseVarName( sStr, sActSubName );
        pEditEngine->ReplaceText( sTextSelection, sStr );
        pEditView->SetSelection( aSel );
    }
    else
    {
        //autocorrect procedures
        SbxArray* pArr = rModulWindow.GetSbModule()->GetMethods().get();
        for (sal_uInt32 i = 0; i < pArr->Count(); ++i)
        {
            if (pArr->Get(i)->GetName().equalsIgnoreAsciiCase(sStr))
            {
                sStr = pArr->Get(i)->GetName(); //if found, get the correct case
                pEditEngine->ReplaceText( sTextSelection, sStr );
                pEditView->SetSelection( aSel );
                return;
            }
        }
    }
}

void EditorWindow::SetLineHighlightColor(Color aColor)
{
    m_aLineHighlightColor = aColor;
}

TextSelection EditorWindow::GetLastHighlightPortionTextSelection() const
{//creates a text selection from the highlight portion on the cursor
    const sal_uInt32 nLine = GetEditView()->GetSelection().GetStart().GetPara();
    const sal_Int32 nIndex = GetEditView()->GetSelection().GetStart().GetIndex();
    OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified
    std::vector<HighlightPortion> aPortions;
    aHighlighter.getHighlightPortions( aLine, aPortions );

    assert(!aPortions.empty());
    HighlightPortion& r = aPortions.back();
    if( static_cast<size_t>(nIndex) != aPortions.size()-1 )
    {//cursor is not standing at the end of the line
        for (auto const& portion : aPortions)
        {
            if( portion.nEnd == nIndex )
            {
                r = portion;
                break;
            }
        }
    }

    if( aPortions.empty() )
        return TextSelection();

    std::u16string_view sStr = aLine.subView( r.nBegin, r.nEnd - r.nBegin );
    TextPaM aStart( nLine, r.nBegin );
    TextPaM aEnd( nLine, r.nBegin + sStr.size() );
    return TextSelection( aStart, aEnd );
}

void EditorWindow::HandleAutoCloseParen()
{
    TextSelection aSel = GetEditView()->GetSelection();
    const sal_uInt32 nLine =  aSel.GetStart().GetPara();
    OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified

    if( aLine.getLength() > 0 && aLine[aSel.GetEnd().GetIndex()-1] != '(' )
    {
        GetEditView()->InsertText(u")"_ustr);
        //leave the cursor on its place: inside the parenthesis
        TextPaM aEnd(nLine, aSel.GetEnd().GetIndex());
        GetEditView()->SetSelection( TextSelection( aEnd, aEnd ) );
    }
}

void EditorWindow::HandleAutoCloseDoubleQuotes()
{
    TextSelection aSel = GetEditView()->GetSelection();
    const sal_uInt32 nLine =  aSel.GetStart().GetPara();
    OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified

    std::vector<HighlightPortion> aPortions;
    aHighlighter.getHighlightPortions( aLine, aPortions );

    if( aPortions.empty() )
        return;

    if( aLine.getLength() > 0 && !aLine.endsWith("\"") && (aPortions.back().tokenType != TokenType::String) )
    {
        GetEditView()->InsertText(u"\""_ustr);
        //leave the cursor on its place: inside the two double quotes
        TextPaM aEnd(nLine, aSel.GetEnd().GetIndex());
        GetEditView()->SetSelection( TextSelection( aEnd, aEnd ) );
    }
}

void EditorWindow::HandleProcedureCompletion()
{

    TextSelection aSel = GetEditView()->GetSelection();
    const sal_uInt32 nLine = aSel.GetStart().GetPara();
    OUString aLine( pEditEngine->GetText( nLine ) );

    OUString sProcType;
    OUString sProcName;
    bool bFoundName = GetProcedureName(aLine, sProcType, sProcName);
    if (!bFoundName)
      return;

    OUString sText(u"\nEnd "_ustr);
    aSel = GetEditView()->GetSelection();
    if( sProcType.equalsIgnoreAsciiCase("function") )
        sText += "Function\n";
    if( sProcType.equalsIgnoreAsciiCase("sub") )
        sText += "Sub\n";

    if( nLine+1 == pEditEngine->GetParagraphCount() )
    {
        pEditView->InsertText( sText );//append to the end
        GetEditView()->SetSelection(aSel);
    }
    else
    {
        for( sal_uInt32 i = nLine+1; i < pEditEngine->GetParagraphCount(); ++i )
        {//searching forward for end token, or another sub/function definition
            OUString aCurrLine = pEditEngine->GetText( i );
            std::vector<HighlightPortion> aCurrPortions;
            aHighlighter.getHighlightPortions( aCurrLine, aCurrPortions );

            if( aCurrPortions.size() >= 3 )
            {//at least 3 tokens: (sub|function) whitespace identifier...
                HighlightPortion& r = aCurrPortions.front();
                std::u16string_view sStr = aCurrLine.subView(r.nBegin, r.nEnd - r.nBegin);

                if( r.tokenType == TokenType::Keywords )
                {
                    if( o3tl::equalsIgnoreAsciiCase(sStr, u"sub") || o3tl::equalsIgnoreAsciiCase(sStr, u"function") )
                    {
                        pEditView->InsertText( sText );//append to the end
                        GetEditView()->SetSelection(aSel);
                        break;
                    }
                    if( o3tl::equalsIgnoreAsciiCase(sStr, u"end") )
                        break;
                }
            }
        }
    }
}

bool EditorWindow::GetProcedureName(std::u16string_view rLine, OUString& rProcType, OUString& rProcName) const
{
    std::vector<HighlightPortion> aPortions;
    aHighlighter.getHighlightPortions(rLine, aPortions);

    if( aPortions.empty() )
        return false;

    bool bFoundType = false;
    bool bFoundName = false;

    for (auto const& portion : aPortions)
    {
        std::u16string_view sTokStr = rLine.substr(portion.nBegin, portion.nEnd - portion.nBegin);

        if( portion.tokenType == TokenType::Keywords && ( o3tl::equalsIgnoreAsciiCase(sTokStr, u"sub")
            || o3tl::equalsIgnoreAsciiCase(sTokStr, u"function")) )
        {
            rProcType = sTokStr;
            bFoundType = true;
        }
        if( portion.tokenType == TokenType::Identifier && bFoundType )
        {
            rProcName = sTokStr;
            bFoundName = true;
            break;
        }
    }

    if( !bFoundType || !bFoundName )
        return false;// no sub/function keyword or there is no identifier

    return true;

}

void EditorWindow::HandleCodeCompletion()
{
    rModulWindow.UpdateModule();
    rModulWindow.GetSbModule()->GetCodeCompleteDataFromParse(aCodeCompleteCache);
    TextSelection aSel = GetEditView()->GetSelection();
    const sal_uInt32 nLine =  aSel.GetStart().GetPara();
    OUString aLine( pEditEngine->GetText( nLine ) ); // the line being modified
    std::vector< OUString > aVect; //vector to hold the base variable+methods for the nested reflection

    std::vector<HighlightPortion> aPortions;
    aLine = aLine.copy(0, aSel.GetEnd().GetIndex());
    aHighlighter.getHighlightPortions( aLine, aPortions );
    if( aPortions.empty() )
        return;

    //use the syntax highlighter to grab out nested reflection calls, eg. aVar.aMethod("aa").aOtherMethod ..
    for( std::vector<HighlightPortion>::reverse_iterator i(
             aPortions.rbegin());
         i != aPortions.rend(); ++i)
    {
        if( i->tokenType == TokenType::Whitespace ) // a whitespace: stop; if there is no ws, it goes to the beginning of the line
            break;
        if( i->tokenType == TokenType::Identifier || i->tokenType == TokenType::Keywords ) // extract the identifiers(methods, base variable)
        /* an example: Dim aLocVar2 as com.sun.star.beans.PropertyValue
         * here, aLocVar2.Name, and PropertyValue's Name field is treated as a keyword(?!)
         * */
            aVect.insert( aVect.begin(), aLine.copy(i->nBegin, i->nEnd - i->nBegin) );
    }

    if( aVect.empty() )//nothing to do
        return;

    OUString sBaseName = aVect[aVect.size()-1];//variable name
    OUString sVarType = aCodeCompleteCache.GetVarType( sBaseName );

    if( !sVarType.isEmpty() && CodeCompleteOptions::IsAutoCorrectOn() )
    {//correct variable name, if autocorrection on
        const OUString sStr = aCodeCompleteCache.GetCorrectCaseVarName( sBaseName, GetActualSubName(nLine) );
        if( !sStr.isEmpty() )
        {
            TextPaM aStart(nLine, aSel.GetStart().GetIndex() - sStr.getLength() );
            TextSelection sTextSelection(aStart, TextPaM(nLine, aSel.GetStart().GetIndex()));
            pEditEngine->ReplaceText( sTextSelection, sStr );
            pEditView->SetSelection( aSel );
        }
    }

    UnoTypeCodeCompletetor aTypeCompletor( aVect, sVarType );

    if( !aTypeCompletor.CanCodeComplete() )
        return;

    std::vector< OUString > aEntryVect;//entries to be inserted into the list
    std::vector< OUString > aFieldVect = aTypeCompletor.GetXIdlClassFields();//fields
    aEntryVect.insert(aEntryVect.end(), aFieldVect.begin(), aFieldVect.end() );
    if( CodeCompleteOptions::IsExtendedTypeDeclaration() )
    {// if extended types on, reflect classes, else just the structs (XIdlClass without methods)
        std::vector< OUString > aMethVect = aTypeCompletor.GetXIdlClassMethods();//methods
        aEntryVect.insert(aEntryVect.end(), aMethVect.begin(), aMethVect.end() );
    }
    if( !aEntryVect.empty() )
        SetupAndShowCodeCompleteWnd( aEntryVect, aSel );
}

void EditorWindow::SetupAndShowCodeCompleteWnd( const std::vector< OUString >& aEntryVect, TextSelection aSel )
{
    // clear the listbox
    pCodeCompleteWnd->ClearListBox();
    // fill the listbox
    for(const auto & l : aEntryVect)
    {
        pCodeCompleteWnd->InsertEntry( l );
    }
    // show it
    pCodeCompleteWnd->Show();
    pCodeCompleteWnd->ResizeAndPositionListBox();
    pCodeCompleteWnd->SelectFirstEntry();
    // correct text selection, and set it
    ++aSel.GetStart().GetIndex();
    ++aSel.GetEnd().GetIndex();
    pCodeCompleteWnd->SetTextSelection( aSel );
    //give the focus to the EditView
    pEditView->GetWindow()->GrabFocus();
}

void EditorWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    if (!pEditEngine)     // We need it now at latest
        CreateEditEngine();

    HighlightCurrentLine(rRenderContext);

    pEditView->Paint(rRenderContext, rRect);
}

void EditorWindow::HighlightCurrentLine(vcl::RenderContext& rRenderContext)
{
    // If the cursor is in a single line and nothing is selected, then a highlight color
    // is applied to the background of the current line
    TextPaM aStartPaM = pEditView->GetSelection().GetStart();
    TextPaM aEndPaM = pEditView->GetSelection().GetEnd();
    if (aStartPaM == aEndPaM)
    {
        Size aWinSize(GetOutputSizePixel());
        sal_Int16 nDocPosY = pEditView->GetStartDocPos().Y();
        sal_Int16 nY1 = pEditEngine->PaMtoEditCursor(aStartPaM).TopLeft().Y();
        sal_Int16 nY2 = pEditEngine->PaMtoEditCursor(aStartPaM).BottomRight().Y();
        // Only draw if the cursor is in a visible position
        if ((nY1 >= nDocPosY && nY1 <= nDocPosY + aWinSize.Height())
            || (nY2 >= nDocPosY && nY2 <= nDocPosY + aWinSize.Height()))
        {
            tools::Rectangle aRect(Point(0, nY1 - nDocPosY), Point(aWinSize.Width(), nY2 - nDocPosY));
            rRenderContext.SetFillColor(m_aLineHighlightColor);
            rRenderContext.DrawRect(aRect);
        }
    }
}

void EditorWindow::LoseFocus()
{
    // tdf#114258 wait until the next event loop cycle to do this so it doesn't
    // happen during a mouse down/up selection in the treeview whose contents
    // this may update
    if (!m_nSetSourceInBasicId)
        m_nSetSourceInBasicId = Application::PostUserEvent(LINK(this, EditorWindow, SetSourceInBasicHdl));
    Window::LoseFocus();
}

IMPL_LINK_NOARG(EditorWindow, SetSourceInBasicHdl, void*, void)
{
    m_nSetSourceInBasicId = nullptr;
    SetSourceInBasic();
}

void EditorWindow::SetSourceInBasic()
{
    if ( pEditEngine && pEditEngine->IsModified()
        && !GetEditView()->IsReadOnly() )   // Added for #i60626, otherwise
            // any read only bug in the text engine could lead to a crash later
    {
        if ( !StarBASIC::IsRunning() ) // Not at runtime!
        {
            rModulWindow.UpdateModule();
        }
    }
}

// Returns the position of the last character of any of the following
// EOL char combinations: CR, CR/LF, LF, return -1 if no EOL is found
sal_Int32 searchEOL( std::u16string_view rStr, sal_Int32 fromIndex )
{
    size_t iLF = rStr.find( LINE_SEP, fromIndex );
    if( iLF != std::u16string_view::npos )
        return iLF;

    size_t iCR = rStr.find( LINE_SEP_CR, fromIndex );
    return iCR == std::u16string_view::npos ? -1 : iCR;
}

void EditorWindow::CreateEditEngine()
{
    if (pEditEngine)
        return;

    pEditEngine.reset(new ExtTextEngine);
    pEditView.reset(new TextView(pEditEngine.get(), this));
    pEditView->SetAutoIndentMode(true);
    pEditEngine->SetUpdateMode(false);
    pEditEngine->InsertView(pEditView.get());

    ImplSetFont();

    aSyntaxIdle.SetInvokeHandler( LINK( this, EditorWindow, SyntaxTimerHdl ) );

    bool bWasDoSyntaxHighlight = bDoSyntaxHighlight;
    bDoSyntaxHighlight = false; // too slow for large texts...
    OUString aOUSource(rModulWindow.GetModule());
    sal_Int32 nLines = 0;
    sal_Int32 nIndex = -1;
    do
    {
        nLines++;
        nIndex = searchEOL( aOUSource, nIndex+1 );
    }
    while (nIndex >= 0);

    // nLines*4: SetText+Formatting+DoHighlight+Formatting
    // it could be cut down on one formatting but you would wait even longer
    // for the text then if the source code is long...
    pProgress.reset(new ProgressInfo(GetShell()->GetViewFrame().GetObjectShell(),
                                     IDEResId(RID_STR_GENERATESOURCE),
                                     nLines * 4));
    setTextEngineText(*pEditEngine, aOUSource);

    pEditView->SetStartDocPos(Point(0, 0));
    pEditView->SetSelection(TextSelection());
    rModulWindow.GetBreakPointWindow().GetCurYOffset() = 0;
    rModulWindow.GetLineNumberWindow().GetCurYOffset() = 0;
    pEditEngine->SetUpdateMode(true);
    rModulWindow.PaintImmediately();   // has only been invalidated at UpdateMode = true

    pEditView->ShowCursor();

    StartListening(*pEditEngine);

    aSyntaxIdle.Stop();
    bDoSyntaxHighlight = bWasDoSyntaxHighlight;

    for (sal_Int32 nLine = 0; nLine < nLines; nLine++)
        aSyntaxLineTable.insert(nLine);
    ForceSyntaxTimeout();

    pProgress.reset();

    pEditEngine->SetModified( false );
    pEditEngine->EnableUndo( true );

    InitScrollBars();

    if (SfxBindings* pBindings = GetBindingsPtr())
    {
        pBindings->Invalidate(SID_BASICIDE_STAT_POS);
        pBindings->Invalidate(SID_BASICIDE_STAT_TITLE);
    }

    DBG_ASSERT(rModulWindow.GetBreakPointWindow().GetCurYOffset() == 0, "CreateEditEngine: breakpoints moved?");

    // set readonly mode for readonly libraries
    ScriptDocument aDocument(rModulWindow.GetDocument());
    OUString aOULibName(rModulWindow.GetLibName());
    Reference< script::XLibraryContainer2 > xModLibContainer( aDocument.getLibraryContainer( E_SCRIPTS ) );
    if (xModLibContainer.is()
     && xModLibContainer->hasByName(aOULibName)
     && xModLibContainer->isLibraryReadOnly(aOULibName))
    {
        rModulWindow.SetReadOnly(true);
    }

    if (aDocument.isDocument() && aDocument.isReadOnly())
        rModulWindow.SetReadOnly(true);
}

void EditorWindow::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
{
    if( rHint.GetId() == SfxHintId::TextViewScrolled )
    {
        rModulWindow.GetEditVScrollBar().SetThumbPos( pEditView->GetStartDocPos().Y() );
        rModulWindow.GetEditHScrollBar().SetThumbPos( pEditView->GetStartDocPos().X() );
        rModulWindow.GetBreakPointWindow().DoScroll
            ( rModulWindow.GetBreakPointWindow().GetCurYOffset() - pEditView->GetStartDocPos().Y() );
        rModulWindow.GetLineNumberWindow().DoScroll
            ( rModulWindow.GetLineNumberWindow().GetCurYOffset() - pEditView->GetStartDocPos().Y() );
    }
    else if( rHint.GetId() == SfxHintId::TextHeightChanged )
    {
        if ( pEditView->GetStartDocPos().Y() )
        {
            tools::Long nOutHeight = GetOutputSizePixel().Height();
            tools::Long nTextHeight = pEditEngine->GetTextHeight();
            if ( nTextHeight < nOutHeight )
                pEditView->Scroll( 0, pEditView->GetStartDocPos().Y() );

            rModulWindow.GetLineNumberWindow().Invalidate();
        }

        SetScrollBarRanges();
    }
    else if( rHint.GetId() == SfxHintId::TextFormatted )
    {

        const tools::Long nWidth = pEditEngine->CalcTextWidth();
        if ( nWidth != nCurTextWidth )
        {
            nCurTextWidth = nWidth;
            rModulWindow.GetEditHScrollBar().SetRange( Range( 0, nCurTextWidth-1) );
            rModulWindow.GetEditHScrollBar().SetThumbPos( pEditView->GetStartDocPos().X() );
        }
        tools::Long nPrevTextWidth = nCurTextWidth;
        nCurTextWidth = pEditEngine->CalcTextWidth();
        if ( nCurTextWidth != nPrevTextWidth )
            SetScrollBarRanges();
    }
    else if( rHint.GetId() == SfxHintId::TextParaInserted )
    {
        TextHint const & rTextHint = static_cast<TextHint const&>(rHint);
        ParagraphInsertedDeleted( rTextHint.GetValue(), true );
        DoDelayedSyntaxHighlight( rTextHint.GetValue() );
    }
    else if( rHint.GetId() == SfxHintId::TextParaRemoved )
    {
        TextHint const & rTextHint = static_cast<TextHint const&>(rHint);
        ParagraphInsertedDeleted( rTextHint.GetValue(), false );
    }
    else if( rHint.GetId() == SfxHintId::TextParaContentChanged )
    {
        TextHint const & rTextHint = static_cast<TextHint const&>(rHint);
        DoDelayedSyntaxHighlight( rTextHint.GetValue() );
    }
    else if( rHint.GetId() == SfxHintId::TextViewSelectionChanged )
    {
        if (SfxBindings* pBindings = GetBindingsPtr())
        {
            pBindings->Invalidate( SID_CUT );
            pBindings->Invalidate( SID_COPY );
        }
    }
    else if( rHint.GetId() == SfxHintId::TextViewCaretChanged )
    {
        // Check whether the line number where the caret is has changed and the
        // highlight needs to be redrawn
        sal_uInt32 nStartPara = pEditView->GetSelection().GetStart().GetPara();
        sal_uInt32 nEndPara = pEditView->GetSelection().GetEnd().GetPara();
        if (nStartPara == nEndPara && nStartPara != m_nLastHighlightPara)
        {
            m_nLastHighlightPara = nStartPara;
            Invalidate();
            rModulWindow.GetLineNumberWindow().Invalidate();
        }
        else if (nStartPara != nEndPara)
        {
            // If multiple lines are selected, then update the line number window
            rModulWindow.GetLineNumberWindow().Invalidate();
        }
    }
}

OUString EditorWindow::GetActualSubName( sal_uInt32 nLine )
{
    SbxArrayRef pMethods = rModulWindow.GetSbModule()->GetMethods();
    for (sal_uInt32 i = 0; i < pMethods->Count(); i++)
    {
        SbMethod* pMeth = dynamic_cast<SbMethod*>(pMethods->Get(i));
        if( pMeth )
        {
            sal_uInt16 l1,l2;
            pMeth->GetLineRange(l1,l2);
            if( (l1 <= nLine+1) && (nLine+1 <= l2) )
            {
                return pMeth->GetName();
            }
        }
    }
    return OUString();
}

void EditorWindow::SetScrollBarRanges()
{
    // extra method, not InitScrollBars, because for EditEngine events too
    if ( !pEditEngine )
        return;

    rModulWindow.GetEditVScrollBar().SetRange( Range( 0, pEditEngine->GetTextHeight()-1 ) );
    rModulWindow.GetEditHScrollBar().SetRange( Range( 0, nCurTextWidth-1 ) );
}

void EditorWindow::InitScrollBars()
{
    if (!pEditEngine)
        return;

    SetScrollBarRanges();
    Size aOutSz(GetOutputSizePixel());
    rModulWindow.GetEditVScrollBar().SetVisibleSize(aOutSz.Height());
    rModulWindow.GetEditVScrollBar().SetPageSize(aOutSz.Height() * 8 / 10);
    rModulWindow.GetEditVScrollBar().SetLineSize(GetTextHeight());
    rModulWindow.GetEditVScrollBar().SetThumbPos(pEditView->GetStartDocPos().Y());
    rModulWindow.GetEditVScrollBar().Show();

    rModulWindow.GetEditHScrollBar().SetVisibleSize(aOutSz.Width());
    rModulWindow.GetEditHScrollBar().SetPageSize(aOutSz.Width() * 8 / 10);
    rModulWindow.GetEditHScrollBar().SetLineSize(GetTextWidth( u"x"_ustr ));
    rModulWindow.GetEditHScrollBar().SetThumbPos(pEditView->GetStartDocPos().X());
    rModulWindow.GetEditHScrollBar().Show();
}

void EditorWindow::ImpDoHighlight( sal_uInt32 nLine )
{
    if ( !bDoSyntaxHighlight )
        return;

    OUString aLine( pEditEngine->GetText( nLine ) );
    bool const bWasModified = pEditEngine->IsModified();
    pEditEngine->RemoveAttribs( nLine );
    std::vector<HighlightPortion> aPortions;
    aHighlighter.getHighlightPortions( aLine, aPortions );

    for (auto const& portion : aPortions)
    {
        Color const aColor = rModulWindow.GetLayout().GetSyntaxColor(portion.tokenType);
        pEditEngine->SetAttrib(TextAttribFontColor(aColor), nLine, portion.nBegin, portion.nEnd);
    }

    pEditEngine->SetModified(bWasModified);
}

void EditorWindow::ChangeFontColor( Color aColor )
{
    if (pEditEngine)
    {
        vcl::Font aFont(pEditEngine->GetFont());
        aFont.SetColor(aColor);
        pEditEngine->SetFont(aFont);
    }
}

void EditorWindow::UpdateSyntaxHighlighting ()
{
    if (pEditEngine)
    {
        const sal_uInt32 nCount = pEditEngine->GetParagraphCount();
        for (sal_uInt32 i = 0; i < nCount; ++i)
            DoDelayedSyntaxHighlight(i);
    }
}

void EditorWindow::ImplSetFont()
{
    // Get default font name and height defined in the Options dialog
    OUString sFontName(officecfg::Office::Common::Font::SourceViewFont::FontName::get().value_or(OUString()));
    if (sFontName.isEmpty())
    {
        vcl::Font aTmpFont(OutputDevice::GetDefaultFont(DefaultFontType::FIXED,
                                                        Application::GetSettings().GetUILanguageTag().getLanguageType(),
                                                        GetDefaultFontFlags::NONE, GetOutDev()));
        sFontName = aTmpFont.GetFamilyName();
    }
    sal_uInt16 nDefaultFontHeight = officecfg::Office::Common::Font::SourceViewFont::FontHeight::get();

    // Calculate font size considering zoom level
    sal_uInt16 nNewFontHeight = nDefaultFontHeight * (static_cast<float>(nCurrentZoomLevel) / 100);
    Size aFontSize(0, nNewFontHeight);

    vcl::Font aFont(sFontName, aFontSize);
    aFont.SetColor(rModulWindow.GetLayout().GetFontColor());
    SetPointFont(*GetOutDev(), aFont); // FIXME RenderContext
    aFont = GetFont();

    rModulWindow.GetBreakPointWindow().SetFont(aFont);
    rModulWindow.GetLineNumberWindow().SetFont(aFont);
    rModulWindow.Invalidate();

    if (pEditEngine)
    {
        bool const bModified = pEditEngine->IsModified();
        pEditEngine->SetFont(aFont);
        pEditEngine->SetModified(bModified);
    }

    // Update controls
    if (SfxBindings* pBindings = GetBindingsPtr())
    {
        pBindings->Invalidate( SID_BASICIDE_CURRENT_ZOOM );
        pBindings->Invalidate( SID_ATTR_ZOOMSLIDER );
    }
}

void EditorWindow::SetEditorZoomLevel(sal_uInt16 nNewZoomLevel)
{
    if (nCurrentZoomLevel == nNewZoomLevel)
        return;

    if (nNewZoomLevel < MIN_ZOOM_LEVEL || nNewZoomLevel > MAX_ZOOM_LEVEL)
        return;

    nCurrentZoomLevel = nNewZoomLevel;
    ImplSetFont();
}

void EditorWindow::DoSyntaxHighlight( sal_uInt32 nPara )
{
    // because of the DelayedSyntaxHighlight it's possible
    // that this line does not exist anymore!
    if ( nPara < pEditEngine->GetParagraphCount() )
    {
        // unfortunately I'm not sure that exactly this line does Modified()...
        if ( pProgress )
            pProgress->StepProgress();
        ImpDoHighlight( nPara );
    }
}

void EditorWindow::DoDelayedSyntaxHighlight( sal_uInt32 nPara )
{
    // line is only added to list, processed in TimerHdl
    // => don't manipulate breaks while EditEngine is formatting
    if ( pProgress )
        pProgress->StepProgress();

    if ( !bHighlighting && bDoSyntaxHighlight )
    {
        if ( bDelayHighlight )
        {
            aSyntaxLineTable.insert( nPara );
            aSyntaxIdle.Start();
        }
        else
            DoSyntaxHighlight( nPara );
    }
}

IMPL_LINK_NOARG(EditorWindow, SyntaxTimerHdl, Timer *, void)
{
    DBG_ASSERT( pEditView, "Not yet a View, but Syntax-Highlight?!" );

    bool const bWasModified = pEditEngine->IsModified();
    //pEditEngine->SetUpdateMode(false);

    bHighlighting = true;
    for (auto const& syntaxLine : aSyntaxLineTable)
    {
        DoSyntaxHighlight(syntaxLine);
    }

    // #i45572#
    if ( pEditView )
        pEditView->ShowCursor( false );

    pEditEngine->SetModified( bWasModified );

    aSyntaxLineTable.clear();
    bHighlighting = false;
}

void EditorWindow::ParagraphInsertedDeleted( sal_uInt32 nPara, bool bInserted )
{
    if ( pProgress )
        pProgress->StepProgress();

    if ( !bInserted && ( nPara == TEXT_PARA_ALL ) )
    {
        rModulWindow.GetBreakPoints().reset();
        rModulWindow.GetBreakPointWindow().Invalidate();
        rModulWindow.GetLineNumberWindow().Invalidate();
    }
    else
    {
        rModulWindow.GetBreakPoints().AdjustBreakPoints( static_cast<sal_uInt16>(nPara)+1, bInserted );

        tools::Long nLineHeight = GetTextHeight();
        Size aSz = rModulWindow.GetBreakPointWindow().GetOutDev()->GetOutputSize();
        tools::Rectangle aInvRect( Point( 0, 0 ), aSz );
        tools::Long nY = nPara*nLineHeight - rModulWindow.GetBreakPointWindow().GetCurYOffset();
        aInvRect.SetTop( nY );
        rModulWindow.GetBreakPointWindow().Invalidate( aInvRect );

        Size aLnSz(rModulWindow.GetLineNumberWindow().GetWidth(),
                   GetOutputSizePixel().Height() - 2 * DWBORDER);
        rModulWindow.GetLineNumberWindow().SetPosSizePixel(Point(DWBORDER + 19, DWBORDER), aLnSz);
        rModulWindow.GetLineNumberWindow().Invalidate();
    }
}

void EditorWindow::CreateProgress( const OUString& rText, sal_uInt32 nRange )
{
    DBG_ASSERT( !pProgress, "ProgressInfo exists already" );
    pProgress.reset(new ProgressInfo(
        GetShell()->GetViewFrame().GetObjectShell(),
        rText,
        nRange
    ));
}

void EditorWindow::DestroyProgress()
{
    pProgress.reset();
}

void EditorWindow::ForceSyntaxTimeout()
{
    aSyntaxIdle.Stop();
    aSyntaxIdle.Invoke();
}

FactoryFunction EditorWindow::GetUITestFactory() const
{
    return EditorWindowUIObject::create;
}


// BreakPointWindow

BreakPointWindow::BreakPointWindow (vcl::Window* pParent, ModulWindow* pModulWindow)
    : Window(pParent, WB_BORDER)
    , rModulWindow(*pModulWindow)
    , nCurYOffset(0) // memorize nCurYOffset and not take it from EditEngine
    , nMarkerPos(NoMarker)
    , bErrorMarker(false)
{
    setBackgroundColor(GetSettings().GetStyleSettings().GetFieldColor());
    SetHelpId(HID_BASICIDE_BREAKPOINTWINDOW);
}

void BreakPointWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
{
    SyncYOffset(); // Don't return even if invalidated, to avoid flicker

    Size const aOutSz = rRenderContext.GetOutputSize();
    tools::Long const nLineHeight = rRenderContext.GetTextHeight();

    Image const aBrk[2] =
    {
        GetImage(RID_BMP_BRKDISABLED),
        GetImage(RID_BMP_BRKENABLED)
    };

    Size const aBmpSz = rRenderContext.PixelToLogic(aBrk[1].GetSizePixel());
    Point const aBmpOff((aOutSz.Width() - aBmpSz.Width()) / 2,
                        (nLineHeight - aBmpSz.Height()) / 2);

    for (size_t i = 0, n = GetBreakPoints().size(); i < n; ++i)
    {
        BreakPoint& rBrk = GetBreakPoints().at(i);
        sal_uInt16 const nLine = rBrk.nLine - 1;
        size_t const nY = nLine*nLineHeight - nCurYOffset;
        rRenderContext.DrawImage(Point(0, nY) + aBmpOff, aBrk[rBrk.bEnabled]);
    }

    ShowMarker(rRenderContext);
}

void BreakPointWindow::ShowMarker(vcl::RenderContext& rRenderContext)
{
    if (nMarkerPos == NoMarker)
        return;

    Size const aOutSz = GetOutDev()->GetOutputSize();
    tools::Long const nLineHeight = GetTextHeight();

    Image aMarker = GetImage(bErrorMarker ? RID_BMP_ERRORMARKER : RID_BMP_STEPMARKER);

    Size aMarkerSz(aMarker.GetSizePixel());
    aMarkerSz = rRenderContext.PixelToLogic(aMarkerSz);
    Point aMarkerOff(0, 0);
    aMarkerOff.setX( (aOutSz.Width() - aMarkerSz.Width()) / 2 );
    aMarkerOff.setY( (nLineHeight - aMarkerSz.Height()) / 2 );

    tools::Long nY = nMarkerPos * nLineHeight - nCurYOffset;
    Point aPos(0, nY);
    aPos += aMarkerOff;

    rRenderContext.DrawImage(aPos, aMarker);
}

void BreakPointWindow::DoScroll( tools::Long nVertScroll )
{
    nCurYOffset -= nVertScroll;
    Window::Scroll(0, nVertScroll, ScrollFlags::Update);
}

void BreakPointWindow::SetMarkerPos( sal_uInt16 nLine, bool bError )
{
    bool bPaintImmediately = SyncYOffset();

    nMarkerPos = nLine;
    bErrorMarker = bError;
    Invalidate();
    if (bPaintImmediately)
        PaintImmediately();
}

void BreakPointWindow::SetNoMarker ()
{
    SetMarkerPos(NoMarker);
}

BreakPoint* BreakPointWindow::FindBreakPoint( const Point& rMousePos )
{
    size_t nLineHeight = GetTextHeight();
    nLineHeight = nLineHeight > 0 ? nLineHeight : 1;
    size_t nYPos = rMousePos.Y() + nCurYOffset;

    for ( size_t i = 0, n = GetBreakPoints().size(); i < n ; ++i )
    {
        BreakPoint& rBrk = GetBreakPoints().at( i );
        sal_uInt16 nLine = rBrk.nLine-1;
        size_t nY = nLine*nLineHeight;
        if ( ( nYPos > nY ) && ( nYPos < ( nY + nLineHeight ) ) )
            return &rBrk;
    }
    return nullptr;
}

void BreakPointWindow::MouseButtonDown( const MouseEvent& rMEvt )
{
    if ( rMEvt.GetClicks() == 2 )
    {
        Point aMousePos( PixelToLogic( rMEvt.GetPosPixel() ) );
        tools::Long nLineHeight = GetTextHeight();
        if(nLineHeight)
        {
            tools::Long nYPos = aMousePos.Y() + nCurYOffset;
            tools::Long nLine = nYPos / nLineHeight + 1;
            rModulWindow.ToggleBreakPoint( static_cast<sal_uInt16>(nLine) );
            Invalidate();
        }
    }
}

void BreakPointWindow::Command( const CommandEvent& rCEvt )
{
    if ( rCEvt.GetCommand() != CommandEventId::ContextMenu )
        return;

    Point aPos( rCEvt.IsMouseEvent() ? rCEvt.GetMousePosPixel() : Point(1,1) );
    tools::Rectangle aRect(aPos, Size(1, 1));
    weld::Window* pPopupParent = weld::GetPopupParent(*this, aRect);

    std::unique_ptr<weld::Builder> xUIBuilder(Application::CreateBuilder(pPopupParent, u"modules/BasicIDE/ui/breakpointmenus.ui"_ustr));

    Point aEventPos( PixelToLogic( aPos ) );
    BreakPoint* pBrk = rCEvt.IsMouseEvent() ? FindBreakPoint( aEventPos ) : nullptr;
    if ( pBrk )
    {
        // test if break point is enabled...
        std::unique_ptr<weld::Menu> xBrkPropMenu = xUIBuilder->weld_menu(u"breakmenu"_ustr);
        xBrkPropMenu->set_active(u"active"_ustr, pBrk->bEnabled);
        OUString sCommand = xBrkPropMenu->popup_at_rect(pPopupParent, aRect);
        if (sCommand == "active")
        {
            pBrk->bEnabled = !pBrk->bEnabled;
            rModulWindow.UpdateBreakPoint( *pBrk );
            Invalidate();
        }
        else if (sCommand == "properties")
        {
            BreakPointDialog aBrkDlg(pPopupParent, GetBreakPoints());
            aBrkDlg.SetCurrentBreakPoint( *pBrk );
            aBrkDlg.run();
            Invalidate();
        }
    }
    else
    {
        std::unique_ptr<weld::Menu> xBrkListMenu = xUIBuilder->weld_menu(u"breaklistmenu"_ustr);
        OUString sCommand = xBrkListMenu->popup_at_rect(pPopupParent, aRect);
        if (sCommand == "manage")
        {
            BreakPointDialog aBrkDlg(pPopupParent, GetBreakPoints());
            aBrkDlg.run();
            Invalidate();
        }
    }
}

bool BreakPointWindow::SyncYOffset()
{
    TextView* pView = rModulWindow.GetEditView();
    if ( pView )
    {
        tools::Long nViewYOffset = pView->GetStartDocPos().Y();
        if ( nCurYOffset != nViewYOffset )
        {
            nCurYOffset = nViewYOffset;
            Invalidate();
            return true;
        }
    }
    return false;
}

// virtual
void BreakPointWindow::DataChanged(DataChangedEvent const & rDCEvt)
{
    Window::DataChanged(rDCEvt);
    if (rDCEvt.GetType() == DataChangedEventType::SETTINGS
        && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
    {
        Color aColor(GetSettings().GetStyleSettings().GetFieldColor());
        const AllSettings* pOldSettings = rDCEvt.GetOldSettings();
        if (!pOldSettings || aColor != pOldSettings->GetStyleSettings().GetFieldColor())
        {
            setBackgroundColor(aColor);
            Invalidate();
        }
    }
}

void BreakPointWindow::setBackgroundColor(Color aColor)
{
    SetBackground(Wallpaper(aColor));
}

namespace {

struct WatchItem
{
    OUString        maName;
    OUString        maDisplayName;
    SbxObjectRef    mpObject;
    std::vector<OUString> maMemberList;

    SbxDimArrayRef  mpArray;
    int             nDimLevel;  // 0 = Root
    int             nDimCount;
    std::vector<sal_Int32> vIndices;

    WatchItem*      mpArrayParentItem;

    explicit WatchItem (OUString aName):
        maName(std::move(aName)),
        nDimLevel(0),
        nDimCount(0),
        mpArrayParentItem(nullptr)
    { }

    void clearWatchItem ()
    {
        maMemberList.clear();
    }

    WatchItem* GetRootItem();
    SbxDimArray* GetRootArray();
};

}

WatchWindow::WatchWindow(Layout* pParent)
    : DockingWindow(pParent, u"modules/BasicIDE/ui/dockingwatch.ui"_ustr, u"DockingWatch"_ustr)
    , m_nUpdateWatchesId(nullptr)
{
    m_xTitleArea = m_xBuilder->weld_container(u"titlearea"_ustr);

    nVirtToolBoxHeight = m_xTitleArea->get_preferred_size().Height();

    m_xTitle = m_xBuilder->weld_label(u"title"_ustr);
    m_xTitle->set_label(IDEResId(RID_STR_REMOVEWATCH));

    m_xEdit = m_xBuilder->weld_entry(u"edit"_ustr);
    m_xRemoveWatchButton = m_xBuilder->weld_button(u"remove"_ustr);
    m_xTreeListBox = m_xBuilder->weld_tree_view(u"treeview"_ustr);

    m_xEdit->set_accessible_name(IDEResId(RID_STR_WATCHNAME));
    m_xEdit->set_help_id(HID_BASICIDE_WATCHWINDOW_EDIT);
    m_xEdit->set_size_request(LogicToPixel(Size(80, 0), MapMode(MapUnit::MapAppFont)).Width(), -1);
    m_xEdit->connect_activate(LINK( this, WatchWindow, ActivateHdl));
    m_xEdit->connect_key_press(LINK( this, WatchWindow, KeyInputHdl));
    m_xTreeListBox->set_accessible_name(IDEResId(RID_STR_WATCHNAME));

    m_xRemoveWatchButton->set_sensitive(false);
    m_xRemoveWatchButton->connect_clicked(LINK( this, WatchWindow, ButtonHdl));
    m_xRemoveWatchButton->set_help_id(HID_BASICIDE_REMOVEWATCH);
    m_xRemoveWatchButton->set_tooltip_text(IDEResId(RID_STR_REMOVEWATCHTIP));

    m_xTreeListBox->set_help_id(HID_BASICIDE_WATCHWINDOW_LIST);
    m_xTreeListBox->connect_editing(LINK(this, WatchWindow, EditingEntryHdl),
                                    LINK(this, WatchWindow, EditedEntryHdl));
    m_xTreeListBox->connect_selection_changed(LINK(this, WatchWindow, TreeListHdl));
    m_xTreeListBox->connect_expanding(LINK(this, WatchWindow, RequestingChildrenHdl));

    // VarTabWidth, ValueTabWidth, TypeTabWidth
    std::vector<int> aWidths { 220, 100, 1250 };
    std::vector<bool> aEditables { false, true, false };
    m_xTreeListBox->set_column_fixed_widths(aWidths);
    m_xTreeListBox->set_column_editables(aEditables);

    SetText(IDEResId(RID_STR_WATCHNAME));

    SetHelpId( HID_BASICIDE_WATCHWINDOW );

    // make watch window keyboard accessible
    GetSystemWindow()->GetTaskPaneList()->AddWindow( this );
}

WatchWindow::~WatchWindow()
{
    disposeOnce();
}

void WatchWindow::dispose()
{
    if (m_nUpdateWatchesId)
    {
        Application::RemoveUserEvent(m_nUpdateWatchesId);
        m_nUpdateWatchesId = nullptr;
    }

    // Destroy user data
    m_xTreeListBox->all_foreach([this](weld::TreeIter& rEntry){
        WatchItem* pItem = weld::fromId<WatchItem*>(m_xTreeListBox->get_id(rEntry));
        delete pItem;
        return false;
    });

    m_xTitle.reset();
    m_xEdit.reset();
    m_xRemoveWatchButton.reset();
    m_xTitleArea.reset();
    m_xTreeListBox.reset();
    GetSystemWindow()->GetTaskPaneList()->RemoveWindow( this );
    DockingWindow::dispose();
}

void WatchWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
{
    lcl_DrawIDEWindowFrame(this, rRenderContext);
}

void WatchWindow::Resize()
{
    Size aSz = GetOutputSizePixel();
    Size aBoxSz(aSz.Width() - 2*DWBORDER, aSz.Height() - 2*DWBORDER);

    if ( aBoxSz.Width() < 4 )
        aBoxSz.setWidth( 0 );
    if ( aBoxSz.Height() < 4 )
        aBoxSz.setHeight( 0 );

    m_xBox->SetPosSizePixel(Point(DWBORDER, DWBORDER), aBoxSz);

    Invalidate();
}

WatchItem* WatchItem::GetRootItem()
{
    WatchItem* pItem = mpArrayParentItem;
    while( pItem )
    {
        if( pItem->mpArray.is() )
            break;
        pItem = pItem->mpArrayParentItem;
    }
    return pItem;
}

SbxDimArray* WatchItem::GetRootArray()
{
    WatchItem* pRootItem = GetRootItem();
    SbxDimArray* pRet = nullptr;
    if( pRootItem )
        pRet = pRootItem->mpArray.get();
    return pRet;
}

void WatchWindow::AddWatch( const OUString& rVName )
{
    OUString aVar, aIndex;
    lcl_SeparateNameAndIndex( rVName, aVar, aIndex );
    WatchItem* pWatchItem = new WatchItem(aVar);

    OUString sId(weld::toId(pWatchItem));
    std::unique_ptr<weld::TreeIter> xRet = m_xTreeListBox->make_iterator();
    m_xTreeListBox->insert(nullptr, -1, &aVar, &sId, nullptr, nullptr, false, xRet.get());
    m_xTreeListBox->set_text(*xRet, u""_ustr, 1);
    m_xTreeListBox->set_text(*xRet, u""_ustr, 2);

    m_xTreeListBox->set_cursor(*xRet);
    m_xTreeListBox->select(*xRet);
    m_xTreeListBox->scroll_to_row(*xRet);
    m_xRemoveWatchButton->set_sensitive(true);

    UpdateWatches(false);
}

void WatchWindow::RemoveSelectedWatch()
{
    std::unique_ptr<weld::TreeIter> xEntry = m_xTreeListBox->make_iterator();
    bool bEntry = m_xTreeListBox->get_cursor(xEntry.get());
    if (bEntry)
    {
        m_xTreeListBox->remove(*xEntry);
        bEntry = m_xTreeListBox->get_cursor(xEntry.get());
        if (bEntry)
            m_xEdit->set_text(weld::fromId<WatchItem*>(m_xTreeListBox->get_id(*xEntry))->maName);
        else
            m_xEdit->set_text(OUString());
        if ( !m_xTreeListBox->n_children() )
            m_xRemoveWatchButton->set_sensitive(false);
    }
}

IMPL_STATIC_LINK_NOARG(WatchWindow, ButtonHdl, weld::Button&, void)
{
    if (SfxDispatcher* pDispatcher = GetDispatcher())
        pDispatcher->Execute(SID_BASICIDE_REMOVEWATCH);
}

IMPL_LINK_NOARG(WatchWindow, TreeListHdl, weld::TreeView&, void)
{
    std::unique_ptr<weld::TreeIter> xCurEntry = m_xTreeListBox->make_iterator();
    bool bCurEntry = m_xTreeListBox->get_cursor(xCurEntry.get());
    if (!bCurEntry)
        return;
    WatchItem* pItem = weld::fromId<WatchItem*>(m_xTreeListBox->get_id(*xCurEntry));
    if (!pItem)
        return;
    m_xEdit->set_text(pItem->maName);
}

IMPL_LINK_NOARG(WatchWindow, ActivateHdl, weld::Entry&, bool)
{
    OUString aCurText(m_xEdit->get_text());
    if (!aCurText.isEmpty())
    {
        AddWatch(aCurText);
        m_xEdit->select_region(0, -1);
    }
    return true;
}

IMPL_LINK(WatchWindow, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    bool bHandled = false;

    sal_uInt16 nKeyCode = rKEvt.GetKeyCode().GetCode();
    if (nKeyCode == KEY_ESCAPE)
    {
        m_xEdit->set_text(OUString());
        bHandled = true;
    }

    return bHandled;
}

// StackWindow
StackWindow::StackWindow(Layout* pParent)
    : DockingWindow(pParent, u"modules/BasicIDE/ui/dockingstack.ui"_ustr, u"DockingStack"_ustr)
{
    m_xTitle = m_xBuilder->weld_label(u"title"_ustr);
    m_xTitle->set_label(IDEResId(RID_STR_STACK));

    m_xTitle->set_size_request(-1, nVirtToolBoxHeight); // so the two title areas are the same height

    m_xTreeListBox = m_xBuilder->weld_tree_view(u"stack"_ustr);

    m_xTreeListBox->set_help_id(HID_BASICIDE_STACKWINDOW_LIST);
    m_xTreeListBox->set_accessible_name(IDEResId(RID_STR_STACKNAME));
    m_xTreeListBox->set_selection_mode(SelectionMode::NONE);
    m_xTreeListBox->append_text(OUString());

    SetText(IDEResId(RID_STR_STACKNAME));

    SetHelpId( HID_BASICIDE_STACKWINDOW );

    // make stack window keyboard accessible
    GetSystemWindow()->GetTaskPaneList()->AddWindow( this );
}

StackWindow::~StackWindow()
{
    disposeOnce();
}

void StackWindow::dispose()
{
    GetSystemWindow()->GetTaskPaneList()->RemoveWindow( this );
    m_xTitle.reset();
    m_xTreeListBox.reset();
    DockingWindow::dispose();
}

void StackWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
{
    lcl_DrawIDEWindowFrame(this, rRenderContext);
}

void StackWindow::Resize()
{
    Size aSz = GetOutputSizePixel();
    Size aBoxSz(aSz.Width() - 2*DWBORDER, aSz.Height() - 2*DWBORDER);

    if ( aBoxSz.Width() < 4 )
        aBoxSz.setWidth( 0 );
    if ( aBoxSz.Height() < 4 )
        aBoxSz.setHeight( 0 );

    m_xBox->SetPosSizePixel(Point(DWBORDER, DWBORDER), aBoxSz);

    Invalidate();
}

void StackWindow::UpdateCalls()
{
    m_xTreeListBox->freeze();
    m_xTreeListBox->clear();

    if (StarBASIC::IsRunning())
    {
        ErrCode eOld = SbxBase::GetError();
        m_xTreeListBox->set_selection_mode(SelectionMode::Single);

        sal_Int32 nScope = 0;
        SbMethod* pMethod = StarBASIC::GetActiveMethod( nScope );
        while ( pMethod )
        {
            OUStringBuffer aEntry( OUString::number(nScope ));
            if ( aEntry.getLength() < 2 )
                aEntry.insert(0, " ");
            aEntry.append(": " + pMethod->GetName());
            SbxArray* pParams = pMethod->GetParameters();
            SbxInfo* pInfo = pMethod->GetInfo();
            if ( pParams )
            {
                aEntry.append("(");
                // 0 is the sub's name...
                for (sal_uInt32 nParam = 1; nParam < pParams->Count(); nParam++)
                {
                    SbxVariable* pVar = pParams->Get(nParam);
                    assert(pVar && "Parameter?!");
                    if ( !pVar->GetName().isEmpty() )
                    {
                        aEntry.append(pVar->GetName());
                    }
                    else if ( pInfo )
                    {
                        assert(nParam <= std::numeric_limits<sal_uInt16>::max());
                        const SbxParamInfo* pParam = pInfo->GetParam( sal::static_int_cast<sal_uInt16>(nParam) );
                        if ( pParam )
                        {
                            aEntry.append(pParam->aName);
                        }
                    }
                    aEntry.append("=");
                    SbxDataType eType = pVar->GetType();
                    if( eType & SbxARRAY )
                    {
                        aEntry.append("...");
                    }
                    else if( eType != SbxOBJECT )
                    {
                        aEntry.append(pVar->GetOUString());
                    }
                    if (nParam < (pParams->Count() - 1))
                    {
                        aEntry.append(", ");
                    }
                }
                aEntry.append(")");
            }
            m_xTreeListBox->append_text(aEntry.makeStringAndClear());
            nScope++;
            pMethod = StarBASIC::GetActiveMethod( nScope );
        }

        SbxBase::ResetError();
        if( eOld != ERRCODE_NONE )
            SbxBase::SetError( eOld );
    }
    else
    {
        m_xTreeListBox->set_selection_mode(SelectionMode::NONE);
        m_xTreeListBox->append_text(OUString());
    }

    m_xTreeListBox->thaw();
}

ComplexEditorWindow::ComplexEditorWindow( ModulWindow* pParent ) :
    Window( pParent, WB_3DLOOK | WB_CLIPCHILDREN ),
    aBrkWindow(VclPtr<BreakPointWindow>::Create(this, pParent)),
    aLineNumberWindow(VclPtr<LineNumberWindow>::Create(this, pParent)),
    aEdtWindow(VclPtr<EditorWindow>::Create(this, pParent)),
    aEWVScrollBar(VclPtr<ScrollAdaptor>::Create(this, false)),
    aEWHScrollBar(VclPtr<ScrollAdaptor>::Create(this, true))
{
    // tdf#153853 The line numbering and breakpoint windows should appear on
    // the left, even on RTL locales
    EnableRTL(false);

    aEdtWindow->Show();
    aBrkWindow->Show();

    aEWVScrollBar->SetLineSize(nScrollLine);
    aEWVScrollBar->SetPageSize(nScrollPage);
    aEWVScrollBar->SetScrollHdl( LINK( this, ComplexEditorWindow, ScrollHdl ) );
    aEWVScrollBar->Show();

    aEWHScrollBar->SetLineSize(nScrollLine);
    aEWHScrollBar->SetPageSize(nScrollPage);
    aEWHScrollBar->SetScrollHdl( LINK( this, ComplexEditorWindow, ScrollHdl ) );
    aEWHScrollBar->Show();
}

ComplexEditorWindow::~ComplexEditorWindow()
{
    disposeOnce();
}

void ComplexEditorWindow::dispose()
{
    aBrkWindow.disposeAndClear();
    aLineNumberWindow.disposeAndClear();
    aEdtWindow.disposeAndClear();
    aEWVScrollBar.disposeAndClear();
    aEWHScrollBar.disposeAndClear();
    vcl::Window::dispose();
}

void ComplexEditorWindow::Resize()
{
    Size aOutSz = GetOutputSizePixel();
    Size aSz(aOutSz);
    aSz.AdjustWidth( -(2*DWBORDER) );
    aSz.AdjustHeight( -(2*DWBORDER) );
    tools::Long nBrkWidth = 20;
    tools::Long nSBWidth = aEWVScrollBar->GetSizePixel().Width();
    tools::Long nSBHeight = aEWHScrollBar->GetSizePixel().Height();

    Size aBrkSz(nBrkWidth, aSz.Height() - nSBHeight);

    if (aLineNumberWindow->IsVisible())
    {
        Size aLnSz(aLineNumberWindow->GetWidth(), aSz.Height() - nSBHeight);
        Size aEWSz(aSz.Width() - nBrkWidth - aLineNumberWindow->GetWidth() - nSBWidth, aSz.Height() - nSBHeight);
        aBrkWindow->SetPosSizePixel(Point(DWBORDER, DWBORDER), aBrkSz);
        aLineNumberWindow->SetPosSizePixel(Point(DWBORDER + nBrkWidth, DWBORDER), aLnSz);
        aEdtWindow->SetPosSizePixel(Point(DWBORDER + nBrkWidth + aLnSz.Width(), DWBORDER), aEWSz);
    }
    else
    {
        Size aEWSz(aSz.Width() - nBrkWidth - nSBWidth, aSz.Height() - nSBHeight);
        aBrkWindow->SetPosSizePixel( Point( DWBORDER, DWBORDER ), aBrkSz );
        aEdtWindow->SetPosSizePixel(Point(DWBORDER + nBrkWidth, DWBORDER), aEWSz);
    }

    aEWVScrollBar->SetPosSizePixel(Point(aOutSz.Width() - DWBORDER - nSBWidth, DWBORDER),
                                   Size(nSBWidth, aSz.Height() - nSBHeight));
    aEWHScrollBar->SetPosSizePixel(Point(DWBORDER, aOutSz.Height() - DWBORDER - nSBHeight),
                                   Size(aSz.Width() - nSBWidth, nSBHeight));
}

IMPL_LINK_NOARG(ComplexEditorWindow, ScrollHdl, weld::Scrollbar&, void)
{
    if (aEdtWindow->GetEditView())
    {
        tools::Long nXDiff = aEdtWindow->GetEditView()->GetStartDocPos().X() - aEWHScrollBar->GetThumbPos();
        tools::Long nYDiff = aEdtWindow->GetEditView()->GetStartDocPos().Y() - aEWVScrollBar->GetThumbPos();
        aEdtWindow->GetEditView()->Scroll(nXDiff, nYDiff);
        aBrkWindow->DoScroll( nYDiff );
        aLineNumberWindow->DoScroll( nYDiff );
        aEdtWindow->GetEditView()->ShowCursor(false);
        aEWVScrollBar->SetThumbPos( aEdtWindow->GetEditView()->GetStartDocPos().Y() );
    }
}

void ComplexEditorWindow::DataChanged(DataChangedEvent const & rDCEvt)
{
    Window::DataChanged(rDCEvt);
    if (rDCEvt.GetType() == DataChangedEventType::SETTINGS
        && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
    {
        Color aColor(GetSettings().GetStyleSettings().GetFaceColor());
        const AllSettings* pOldSettings = rDCEvt.GetOldSettings();
        if (!pOldSettings || aColor != pOldSettings->GetStyleSettings().GetFaceColor())
        {
            SetBackground(Wallpaper(aColor));
            Invalidate();
        }
    }
}

void ComplexEditorWindow::SetLineNumberDisplay(bool b)
{
    aLineNumberWindow->Show(b);
    Resize();
}

rtl::Reference<comphelper::OAccessible> EditorWindow::CreateAccessible()
{
    // Make sure edit engine and view are available:
    if (!pEditEngine)
        CreateEditEngine();

    return new ::accessibility::Document(this, *pEditView->GetTextEngine(), *pEditView);
}

static sal_uInt32 getCorrectedPropCount(SbxArray* p)
{
    sal_uInt32 nPropCount = p->Count();
    if (nPropCount >= 3 && p->Get(nPropCount - 1)->GetName().equalsIgnoreAsciiCase("Dbg_Methods")
        && p->Get(nPropCount - 2)->GetName().equalsIgnoreAsciiCase("Dbg_Properties")
        && p->Get(nPropCount - 3)->GetName().equalsIgnoreAsciiCase("Dbg_SupportedInterfaces"))
    {
        nPropCount -= 3;
    }
    return nPropCount;
}

IMPL_LINK(WatchWindow, RequestingChildrenHdl, const weld::TreeIter&, rParent, bool)
{
    if( !StarBASIC::IsRunning() )
        return true;

    if (m_xTreeListBox->iter_has_child(rParent))
        return true;

    WatchItem* pItem = weld::fromId<WatchItem*>(m_xTreeListBox->get_id(rParent));
    std::unique_ptr<weld::TreeIter> xRet = m_xTreeListBox->make_iterator();

    SbxDimArray* pArray = pItem->mpArray.get();
    SbxDimArray* pRootArray = pItem->GetRootArray();
    bool bArrayIsRootArray = false;
    if( !pArray && pRootArray )
    {
        pArray = pRootArray;
        bArrayIsRootArray = true;
    }

    SbxObject* pObj = pItem->mpObject.get();
    if( pObj )
    {
        createAllObjectProperties( pObj );
        SbxArray* pProps = pObj->GetProperties();
        const sal_uInt32 nPropCount = getCorrectedPropCount(pProps);
        pItem->maMemberList.reserve(nPropCount);

        for( sal_uInt32 i = 0 ; i < nPropCount ; ++i )
        {
            SbxVariable* pVar = pProps->Get(i);

            pItem->maMemberList.push_back(pVar->GetName());
            OUString const& rName = pItem->maMemberList.back();

            WatchItem* pWatchItem = new WatchItem(rName);
            OUString sId(weld::toId(pWatchItem));

            m_xTreeListBox->insert(&rParent, -1, &rName, &sId, nullptr, nullptr, false, xRet.get());
            m_xTreeListBox->set_text(*xRet, u""_ustr, 1);
            m_xTreeListBox->set_text(*xRet, u""_ustr, 2);
        }

        if (nPropCount > 0 && !m_nUpdateWatchesId)
        {
            m_nUpdateWatchesId = Application::PostUserEvent(LINK(this, WatchWindow, ExecuteUpdateWatches));
        }
    }
    else if( pArray )
    {
        sal_uInt16 nElementCount = 0;

        // Loop through indices of current level
        int nParentLevel = bArrayIsRootArray ? pItem->nDimLevel : 0;
        int nThisLevel = nParentLevel + 1;
        sal_Int32 nMin, nMax;
        if (pArray->GetDim(nThisLevel, nMin, nMax))
        {
            for (sal_Int32 i = nMin; i <= nMax; i++)
            {
                WatchItem* pChildItem = new WatchItem(pItem->maName);

                // Copy data and create name

                OUStringBuffer aIndexStr = "(";
                pChildItem->mpArrayParentItem = pItem;
                pChildItem->nDimLevel = nThisLevel;
                pChildItem->nDimCount = pItem->nDimCount;
                pChildItem->vIndices.resize(pChildItem->nDimCount);
                sal_Int32 j;
                for (j = 0; j < nParentLevel; j++)
                {
                    sal_Int32 n = pChildItem->vIndices[j] = pItem->vIndices[j];
                    aIndexStr.append( OUString::number(n) + "," );
                }
                pChildItem->vIndices[nParentLevel] = i;
                aIndexStr.append( OUString::number(i) + ")" );

                OUString aDisplayName;
                WatchItem* pArrayRootItem = pChildItem->GetRootItem();
                if (pArrayRootItem && pArrayRootItem->mpArrayParentItem)
                    aDisplayName = pItem->maDisplayName;
                else
                    aDisplayName = pItem->maName;
                aDisplayName += aIndexStr;
                pChildItem->maDisplayName = aDisplayName;

                OUString sId(weld::toId(pChildItem));

                m_xTreeListBox->insert(&rParent, -1, &aDisplayName, &sId, nullptr, nullptr, false,
                                       xRet.get());
                m_xTreeListBox->set_text(*xRet, u""_ustr, 1);
                m_xTreeListBox->set_text(*xRet, u""_ustr, 2);

                nElementCount++;
            }
        }
        if (nElementCount > 0 && !m_nUpdateWatchesId)
        {
            m_nUpdateWatchesId = Application::PostUserEvent(LINK(this, WatchWindow, ExecuteUpdateWatches));
        }
    }

    return true;
}

IMPL_LINK_NOARG(WatchWindow, ExecuteUpdateWatches, void*, void)
{
    m_nUpdateWatchesId = nullptr;
    UpdateWatches();
}

SbxBase* WatchWindow::ImplGetSBXForEntry(const weld::TreeIter& rEntry, bool& rbArrayElement)
{
    SbxBase* pSBX = nullptr;
    rbArrayElement = false;

    WatchItem* pItem = weld::fromId<WatchItem*>(m_xTreeListBox->get_id(rEntry));
    OUString aVName( pItem->maName );

    std::unique_ptr<weld::TreeIter> xParentEntry = m_xTreeListBox->make_iterator(&rEntry);
    bool bParentEntry = m_xTreeListBox->iter_parent(*xParentEntry);
    WatchItem* pParentItem = bParentEntry ? weld::fromId<WatchItem*>(m_xTreeListBox->get_id(*xParentEntry)) : nullptr;
    if( pParentItem )
    {
        SbxObject* pObj = pParentItem->mpObject.get();
        SbxDimArray* pArray;
        if( pObj )
        {
            pSBX = pObj->Find( aVName, SbxClassType::DontCare );
            if (SbxVariable const* pVar = IsSbxVariable(pSBX))
            {
                // Force getting value
                SbxValues aRes;
                aRes.eType = SbxVOID;
                if (!isVeryLargeUnoProperty(pVar))
                    pVar->Get( aRes );
                else
                {
                    aRes.eType = SbxSTRING;
                    aRes.pOUString = new OUString("<" + IDEResId(RID_VARIABLE_TOO_LARGE_TO_DISPLAY) + ">");
                }
            }
        }
        // Array?
        else if( (pArray = pItem->GetRootArray()) != nullptr )
        {
            rbArrayElement = true;
            if( pParentItem->nDimLevel + 1 == pParentItem->nDimCount )
                pSBX = pArray->Get(pItem->vIndices.empty() ? nullptr : &*pItem->vIndices.begin());
        }
    }
    else
    {
        pSBX = StarBASIC::FindSBXInCurrentScope( aVName );
    }
    return pSBX;
}

IMPL_LINK(WatchWindow, EditingEntryHdl, const weld::TreeIter&, rIter, bool)
{
    WatchItem* pItem = weld::fromId<WatchItem*>(m_xTreeListBox->get_id(rIter));

    bool bEdit = false;
    if (StarBASIC::IsRunning() && StarBASIC::GetActiveMethod() && !SbxBase::IsError())
    {
        // No out of scope entries
        bool bArrayElement;
        SbxBase* pSbx = ImplGetSBXForEntry(rIter, bArrayElement);
        if (IsSbxVariable(pSbx) || bArrayElement)
        {
            // Accept no objects and only end nodes of arrays for editing
            if( !pItem->mpObject.is() && ( !pItem->mpArray.is() || pItem->nDimLevel == pItem->nDimCount ) )
            {
                aEditingRes = m_xTreeListBox->get_text(rIter, 1);
                aEditingRes = comphelper::string::strip(aEditingRes, ' ');
                bEdit = true;
            }
        }
    }

    return bEdit;
}

IMPL_LINK(WatchWindow, EditedEntryHdl, const IterString&, rIterString, bool)
{
    const weld::TreeIter& rIter = rIterString.first;
    OUString aResult = comphelper::string::strip(rIterString.second, ' ');

    sal_uInt16 nResultLen = aResult.getLength();
    sal_Unicode cFirst = aResult[0];
    sal_Unicode cLast  = aResult[ nResultLen - 1 ];
    if( cFirst == '\"' && cLast == '\"' )
        aResult = aResult.copy( 1, nResultLen - 2 );

    if (aResult == aEditingRes)
        return false;

    bool bArrayElement;
    SbxBase* pSBX = ImplGetSBXForEntry(rIter, bArrayElement);

    if (SbxVariable* pVar = IsSbxVariable(pSBX))
    {
        SbxDataType eType = pVar->GetType();
        if ( static_cast<sal_uInt8>(eType) != sal_uInt8(SbxOBJECT)
             && ( eType & SbxARRAY ) == 0 )
        {
            // If the type is variable, the conversion of the SBX does not matter,
            // else the string is converted.
            pVar->PutStringExt( aResult );
        }
    }

    if ( SbxBase::IsError() )
    {
        SbxBase::ResetError();
    }

    UpdateWatches();

    // The text should never be taken/copied 1:1,
    // as the UpdateWatches will be lost
    return false;
}

namespace
{

void implCollapseModifiedObjectEntry(const weld::TreeIter& rParent, weld::TreeView& rTree)
{
    rTree.collapse_row(rParent);

    std::unique_ptr<weld::TreeIter> xDeleteEntry = rTree.make_iterator(&rParent);

    while (rTree.iter_children(*xDeleteEntry))
    {
        implCollapseModifiedObjectEntry(*xDeleteEntry, rTree);

        WatchItem* pItem = weld::fromId<WatchItem*>(rTree.get_id(*xDeleteEntry));
        delete pItem;
        rTree.remove(*xDeleteEntry);
        rTree.copy_iterator(rParent, *xDeleteEntry);
    }
}

OUString implCreateTypeStringForDimArray( WatchItem* pItem, SbxDataType eType )
{
    OUString aRetStr = getBasicTypeName( eType );

    SbxDimArray* pArray = pItem->mpArray.get();
    if( !pArray )
        pArray = pItem->GetRootArray();
    if( pArray )
    {
        int nDimLevel = pItem->nDimLevel;
        int nDims = pItem->nDimCount;
        if( nDimLevel < nDims )
        {
            aRetStr += "(";
            for( int i = nDimLevel ; i < nDims ; i++ )
            {
                sal_Int32 nMin, nMax;
                pArray->GetDim(sal::static_int_cast<sal_Int32>(i + 1), nMin, nMax);
                aRetStr += OUString::number(nMin) + " to "  + OUString::number(nMax);
                if( i < nDims - 1 )
                    aRetStr += ", ";
            }
            aRetStr += ")";
        }
    }
    return aRetStr;
}

} // namespace

void WatchWindow::implEnableChildren(const weld::TreeIter& rEntry, bool bEnable)
{
    if (bEnable)
    {
        if (!m_xTreeListBox->get_row_expanded(rEntry))
            m_xTreeListBox->set_children_on_demand(rEntry, true);
    }
    else
    {
        assert(!m_xTreeListBox->get_row_expanded(rEntry));
        m_xTreeListBox->set_children_on_demand(rEntry, false);
    }
}

void WatchWindow::UpdateWatches(bool bBasicStopped)
{
    SbMethod* pCurMethod = StarBASIC::GetActiveMethod();

    ErrCode eOld = SbxBase::GetError();
    setBasicWatchMode( true );

    m_xTreeListBox->all_foreach([this, pCurMethod, bBasicStopped](weld::TreeIter& rEntry){
        WatchItem* pItem = weld::fromId<WatchItem*>(m_xTreeListBox->get_id(rEntry));
        DBG_ASSERT( !pItem->maName.isEmpty(), "Var? - Must not be empty!" );
        OUString aWatchStr;
        OUString aTypeStr;
        if ( pCurMethod )
        {
            bool bCollapse = false;
            TriState eEnableChildren = TRISTATE_INDET;

            bool bArrayElement;
            SbxBase* pSBX = ImplGetSBXForEntry(rEntry, bArrayElement);

            // Array? If no end node create type string
            if( bArrayElement && pItem->nDimLevel < pItem->nDimCount )
            {
                SbxDimArray* pRootArray = pItem->GetRootArray();
                SbxDataType eType = pRootArray->GetType();
                aTypeStr = implCreateTypeStringForDimArray( pItem, eType );
                eEnableChildren = TRISTATE_TRUE;
            }

            if (SbxVariable* pVar = dynamic_cast<SbxVariable*>(pSBX))
            {
                // extra treatment of arrays
                SbxDataType eType = pVar->GetType();
                if (isVeryLargeUnoProperty(pVar))
                {
                    aWatchStr += "<" + IDEResId(RID_VARIABLE_TOO_LARGE_TO_DISPLAY) + ">";
                }
                else if ( eType & SbxARRAY )
                {
                    // consider multidimensional arrays!
                    if (SbxDimArray* pNewArray = dynamic_cast<SbxDimArray*>(pVar->GetObject()))
                    {
                        SbxDimArray* pOldArray = pItem->mpArray.get();

                        bool bArrayChanged = false;
                        if (pOldArray != nullptr)
                        {
                            // Compare Array dimensions to see if array has changed
                            // Can be a copy, so comparing pointers does not work
                            sal_Int32 nOldDims = pOldArray->GetDims();
                            sal_Int32 nNewDims = pNewArray->GetDims();
                            if( nOldDims != nNewDims )
                            {
                                bArrayChanged = true;
                            }
                            else
                            {
                                for( sal_Int32 i = 0 ; i < nOldDims ; i++ )
                                {
                                    sal_Int32 nOldMin, nOldMax;
                                    sal_Int32 nNewMin, nNewMax;

                                    pOldArray->GetDim(i + 1, nOldMin, nOldMax);
                                    pNewArray->GetDim(i + 1, nNewMin, nNewMax);
                                    if( nOldMin != nNewMin || nOldMax != nNewMax )
                                    {
                                        bArrayChanged = true;
                                        break;
                                    }
                                }
                            }
                        }
                        else
                        {
                            bArrayChanged = true;
                        }
                        eEnableChildren = TRISTATE_TRUE;
                        // #i37227 Clear always and replace array
                        if( pNewArray != pOldArray )
                        {
                            pItem->clearWatchItem();

                            pItem->mpArray = pNewArray;
                            sal_Int32 nDims = pNewArray->GetDims();
                            pItem->nDimLevel = 0;
                            pItem->nDimCount = nDims;
                        }
                        if( bArrayChanged && pOldArray != nullptr )
                        {
                            bCollapse = true;
                        }
                        aTypeStr = implCreateTypeStringForDimArray( pItem, eType );
                    }
                    else
                    {
                        aWatchStr += "<?>";
                    }
                }
                else if ( static_cast<sal_uInt8>(eType) == sal_uInt8(SbxOBJECT) )
                {
                    if (SbxObject* pObj = dynamic_cast<SbxObject*>(pVar->GetObject()))
                    {
                        if ( pItem->mpObject.is() && !pItem->maMemberList.empty() )
                        {
                            createAllObjectProperties(pObj);
                            SbxArray* pProps = pObj->GetProperties();
                            const sal_uInt32 nPropCount = getCorrectedPropCount(pProps);
                            // Check if member list has changed
                            bCollapse = pItem->maMemberList.size() != nPropCount;
                            for( sal_uInt32 i = 0 ; !bCollapse && i < nPropCount ; i++ )
                            {
                                SbxVariable* pVar_ = pProps->Get(i);
                                if( pItem->maMemberList[i] != pVar_->GetName() )
                                    bCollapse = true;
                            }
                        }

                        pItem->mpObject = pObj;
                        eEnableChildren = TRISTATE_TRUE;
                        aTypeStr = getBasicObjectTypeName( pObj );
                    }
                    else
                    {
                        aWatchStr = u"Null"_ustr;
                        if( pItem->mpObject.is() )
                        {
                            bCollapse = true;
                            eEnableChildren = TRISTATE_FALSE;
                        }
                    }
                }
                else
                {
                    if( pItem->mpObject.is() )
                    {
                        bCollapse = true;
                        eEnableChildren = TRISTATE_FALSE;
                    }

                    bool bString = (static_cast<sal_uInt8>(eType) == sal_uInt8(SbxSTRING));
                    OUString aStrStr( u"\""_ustr );
                    if( bString )
                    {
                        aWatchStr += aStrStr;
                    }
                    // tdf#57308 - avoid a second call to retrieve the data
                    const SbxFlagBits nFlags = pVar->GetFlags();
                    pVar->SetFlag(SbxFlagBits::NoBroadcast);
                    aWatchStr += pVar->GetOUString();
                    pVar->SetFlags(nFlags);
                    if( bString )
                    {
                        aWatchStr += aStrStr;
                    }
                }
                if( aTypeStr.isEmpty() )
                {
                    if( !pVar->IsFixed() )
                    {
                        aTypeStr = u"Variant/"_ustr;
                    }
                    aTypeStr += getBasicTypeName( pVar->GetType() );
                }
            }
            else if( !bArrayElement )
            {
                aWatchStr += "<Out of Scope>";
            }

            if( bCollapse )
            {
                implCollapseModifiedObjectEntry(rEntry, *m_xTreeListBox);
                pItem->clearWatchItem();
            }

            if (eEnableChildren != TRISTATE_INDET)
                implEnableChildren(rEntry, eEnableChildren == TRISTATE_TRUE);
        }
        else if( bBasicStopped )
        {
            if( pItem->mpObject.is() || pItem->mpArray.is() )
            {
                implCollapseModifiedObjectEntry(rEntry, *m_xTreeListBox);
                pItem->mpObject.clear();
                pItem->mpArray.clear();
            }
            pItem->clearWatchItem();
        }

        m_xTreeListBox->set_text(rEntry, aWatchStr, 1);
        m_xTreeListBox->set_text(rEntry, aTypeStr, 2);

        return false;
    });

    SbxBase::ResetError();
    if( eOld != ERRCODE_NONE )
        SbxBase::SetError( eOld );
    setBasicWatchMode( false );
}

IMPL_LINK_NOARG(CodeCompleteWindow, ImplDoubleClickHdl, weld::TreeView&, bool)
{
    InsertSelectedEntry();
    return true;
}

IMPL_LINK_NOARG(CodeCompleteWindow, ImplSelectHdl, weld::TreeView&, void)
{
    //give back the focus to the parent
    pParent->GrabFocus();
}

TextView* CodeCompleteWindow::GetParentEditView()
{
    return pParent->GetEditView();
}

void CodeCompleteWindow::InsertSelectedEntry()
{
    OUString sSelectedEntry = m_xListBox->get_selected_text();

    if( !aFuncBuffer.isEmpty() )
    {
        // if the user typed in something: remove, and insert
        GetParentEditView()->SetSelection(pParent->GetLastHighlightPortionTextSelection());
        GetParentEditView()->DeleteSelected();

        if (!sSelectedEntry.isEmpty())
        {
            // if the user selected something
            GetParentEditView()->InsertText(sSelectedEntry);
        }
    }
    else
    {
        if (!sSelectedEntry.isEmpty())
        {
            // if the user selected something
            GetParentEditView()->InsertText(sSelectedEntry);
        }
    }
    HideAndRestoreFocus();
}

void CodeCompleteWindow::SetMatchingEntries()
{
    for (sal_Int32 i = 0, nEntryCount = m_xListBox->n_children(); i< nEntryCount; ++i)
    {
        OUString sEntry = m_xListBox->get_text(i);
        if (sEntry.startsWithIgnoreAsciiCase(aFuncBuffer))
        {
            m_xListBox->select(i);
            break;
        }
    }
}

IMPL_LINK(CodeCompleteWindow, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    return HandleKeyInput(rKEvt);
}

bool CodeCompleteWindow::HandleKeyInput( const KeyEvent& rKeyEvt )
{
    bool bHandled = true;

    sal_Unicode aChar = rKeyEvt.GetKeyCode().GetCode();
    if( (( aChar >= KEY_A ) && ( aChar <= KEY_Z ))
        || ((aChar >= KEY_0) && (aChar <= KEY_9)) )
    {
        aFuncBuffer.append(rKeyEvt.GetCharCode());
        SetMatchingEntries();
    }
    else
    {
        switch( aChar )
        {
            case KEY_POINT:
                break;
            case KEY_ESCAPE: // hide, do nothing
            case KEY_SPACE:
                HideAndRestoreFocus();
                break;
            case KEY_RIGHT:
            {
                TextSelection aTextSelection( GetParentEditView()->GetSelection() );
                if( aTextSelection.GetEnd().GetPara() != GetTextSelection().GetEnd().GetPara()-1 )
                {
                    HideAndRestoreFocus();
                }
                break;
            }
            case KEY_LEFT:
            {
                TextSelection aTextSelection( GetParentEditView()->GetSelection() );
                if( aTextSelection.GetStart().GetIndex()-1 < GetTextSelection().GetStart().GetIndex() )
                {//leave the cursor where it is
                    HideAndRestoreFocus();
                }
                break;
            }
            case KEY_TAB:
            {
                TextSelection aTextSelection = pParent->GetLastHighlightPortionTextSelection();
                OUString sTypedText = pParent->GetEditEngine()->GetText(aTextSelection);
                if( !aFuncBuffer.isEmpty() )
                {
                    sal_Int32 nInd = m_xListBox->get_selected_index();
                    if (nInd != -1)
                    {
                        int nEntryCount = m_xListBox->n_children();
                        //if there is something selected
                        bool bFound = false;
                        for (sal_Int32 i = nInd; i != nEntryCount; ++i)
                        {
                            OUString sEntry = m_xListBox->get_text(i);
                            if( sEntry.startsWithIgnoreAsciiCase( aFuncBuffer )
                                && (std::u16string_view(aFuncBuffer) != sTypedText) && (i != nInd) )
                            {
                                m_xListBox->select(i);
                                bFound = true;
                                break;
                            }
                        }
                        if( !bFound )
                            SetMatchingEntries();

                        GetParentEditView()->SetSelection( aTextSelection );
                        GetParentEditView()->DeleteSelected();
                        GetParentEditView()->InsertText(m_xListBox->get_selected_text());
                    }
                }
                break;
            }
            case KEY_BACKSPACE: case KEY_DELETE:
                if( !aFuncBuffer.isEmpty() )
                {
                    //if there was something inserted by tab: add it to aFuncBuffer
                    TextSelection aSel( GetParentEditView()->GetSelection() );
                    TextPaM aEnd( GetParentEditView()->CursorEndOfLine(GetTextSelection().GetEnd()) );
                    GetParentEditView()->SetSelection(TextSelection(GetTextSelection().GetStart(), aEnd ) );
                    OUString aTabInsertedStr( GetParentEditView()->GetSelected() );
                    GetParentEditView()->SetSelection( aSel );

                    if( !aTabInsertedStr.isEmpty() && aTabInsertedStr != std::u16string_view(aFuncBuffer) )
                    {
                        aFuncBuffer = aTabInsertedStr;
                    }
                    aFuncBuffer.remove(aFuncBuffer.getLength()-1, 1);
                    SetMatchingEntries();
                }
                else
                {
                    ClearAndHide();
                    bHandled = false;
                }
                break;
            case KEY_RETURN:
                InsertSelectedEntry();
                break;
            case KEY_UP:
            {
                int nInd = m_xListBox->get_selected_index();
                if (nInd)
                    m_xListBox->select(nInd - 1);
                break;
            }
            case KEY_DOWN:
            {
                int nInd = m_xListBox->get_selected_index();
                if (nInd + 1 < m_xListBox->n_children())
                    m_xListBox->select(nInd + 1);
                break;
            }
            default:
                bHandled = false;
                break;
        }
    }

    return bHandled;
}

void CodeCompleteWindow::HideAndRestoreFocus()
{
    Hide();
    pParent->GrabFocus();
}

CodeCompleteWindow::CodeCompleteWindow(EditorWindow* pPar)
    : InterimItemWindow(pPar, u"modules/BasicIDE/ui/codecomplete.ui"_ustr, u"CodeComplete"_ustr)
    , pParent(pPar)
    , m_xListBox(m_xBuilder->weld_tree_view(u"treeview"_ustr))
{
    m_xListBox->connect_row_activated(LINK(this, CodeCompleteWindow, ImplDoubleClickHdl));
    m_xListBox->connect_selection_changed(LINK(this, CodeCompleteWindow, ImplSelectHdl));
    m_xListBox->connect_key_press(LINK(this, CodeCompleteWindow, KeyInputHdl));
    m_xListBox->make_sorted();
    m_xListBox->set_direction(false);

    m_xListBox->set_size_request(150, 150); // default, this will adopt the line length
    SetSizePixel(m_xContainer->get_preferred_size());
}

CodeCompleteWindow::~CodeCompleteWindow()
{
    disposeOnce();
}

void CodeCompleteWindow::dispose()
{
    m_xListBox.reset();
    pParent.reset();
    InterimItemWindow::dispose();
}

void CodeCompleteWindow::InsertEntry( const OUString& aStr )
{
    m_xListBox->append_text(aStr);
}

void CodeCompleteWindow::ClearListBox()
{
    m_xListBox->clear();
    aFuncBuffer.setLength(0);
}

void CodeCompleteWindow::SetTextSelection( const TextSelection& aSel )
{
    m_aTextSelection = aSel;
}

void CodeCompleteWindow::ResizeAndPositionListBox()
{
    if (m_xListBox->n_children() < 1)
        return;

    // if there is at least one element inside
    // calculate basic position: under the current line
    tools::Rectangle aRect = static_cast<TextEngine*>(pParent->GetEditEngine())->PaMtoEditCursor( pParent->GetEditView()->GetSelection().GetEnd() );
    tools::Long nViewYOffset = pParent->GetEditView()->GetStartDocPos().Y();
    Point aPos = aRect.BottomRight();// this variable will be used later (if needed)
    aPos.setY( (aPos.Y() - nViewYOffset) + nBasePad );

    // get line count
    const sal_uInt16 nLines = static_cast<sal_uInt16>(std::min(6, m_xListBox->n_children()));

    m_xListBox->set_size_request(-1, m_xListBox->get_height_rows(nLines));

    Size aSize = m_xContainer->get_preferred_size();
    //set the size
    SetSizePixel( aSize );

    //calculate position
    const tools::Rectangle aVisArea( pParent->GetEditView()->GetStartDocPos(), pParent->GetOutputSizePixel() ); //the visible area
    const Point aBottomPoint = aVisArea.BottomRight();

    if( aVisArea.TopRight().getY() + aPos.getY() + aSize.getHeight() > aBottomPoint.getY() )
    {//clipped at the bottom: move it up
        const tools::Long nParentFontHeight = pParent->GetEditEngine()->GetFont().GetFontHeight(); //parent's font (in the IDE): needed for height
        aPos.AdjustY( -(aSize.getHeight() + nParentFontHeight + nCursorPad) );
    }

    if( aVisArea.TopLeft().getX() + aPos.getX() + aSize.getWidth() > aBottomPoint.getX() )
    {//clipped at the right side, move it a bit left
        aPos.AdjustX( -(aSize.getWidth() + aVisArea.TopLeft().getX()) );
    }
    //set the position
    SetPosPixel( aPos );
}

void CodeCompleteWindow::SelectFirstEntry()
{
    if (m_xListBox->n_children() > 0)
        m_xListBox->select(0);
}

void CodeCompleteWindow::ClearAndHide()
{
    ClearListBox();
    HideAndRestoreFocus();
}

UnoTypeCodeCompletetor::UnoTypeCodeCompletetor( const std::vector< OUString >& aVect, const OUString& sVarType )
: bCanComplete( true )
{
    if( aVect.empty() || sVarType.isEmpty() )
    {
        bCanComplete = false;//invalid parameters, nothing to code complete
        return;
    }

    try
    {
        // Get the base class for reflection:
        xClass = css::reflection::theCoreReflection::get(
            comphelper::getProcessComponentContext())->forName(sVarType);
    }
    catch( const Exception& )
    {
        bCanComplete = false;
        return;
    }

    //start from aVect[1]: aVect[0] is the variable name
    bCanComplete = std::none_of(aVect.begin() + 1, aVect.end(), [this](const OUString& rMethName) {
        return (!CodeCompleteOptions::IsExtendedTypeDeclaration() || !CheckMethod(rMethName)) && !CheckField(rMethName); });
}

std::vector< OUString > UnoTypeCodeCompletetor::GetXIdlClassMethods() const
{
    std::vector< OUString > aRetVect;
    if( bCanComplete && ( xClass != nullptr ) )
    {
        const Sequence< Reference< reflection::XIdlMethod > > aMethods = xClass->getMethods();
        for(Reference< reflection::XIdlMethod > const & rMethod : aMethods)
        {
            aRetVect.push_back( rMethod->getName() );
        }
    }
    return aRetVect;//this is empty when cannot code complete
}

std::vector< OUString > UnoTypeCodeCompletetor::GetXIdlClassFields() const
{
    std::vector< OUString > aRetVect;
    if( bCanComplete && ( xClass != nullptr ) )
    {
        const Sequence< Reference< reflection::XIdlField > > aFields = xClass->getFields();
        for(Reference< reflection::XIdlField > const & rxField : aFields)
        {
            aRetVect.push_back( rxField->getName() );
        }
    }
    return aRetVect;//this is empty when cannot code complete
}


bool UnoTypeCodeCompletetor::CheckField( const OUString& sFieldName )
{// modifies xClass!!!

    if ( xClass == nullptr )
        return false;

    Reference< reflection::XIdlField> xField = xClass->getField( sFieldName );
    if( xField != nullptr )
    {
        xClass = xField->getType();
        if( xClass != nullptr )
        {
            return true;
        }
    }
    return false;
}

bool UnoTypeCodeCompletetor::CheckMethod( const OUString& sMethName )
{// modifies xClass!!!


    if ( xClass == nullptr )
        return false;

    Reference< reflection::XIdlMethod> xMethod = xClass->getMethod( sMethName );
    if( xMethod != nullptr ) //method OK, check return type
    {
        xClass = xMethod->getReturnType();
        if( xClass != nullptr )
        {
            return true;
        }
    }
    return false;
}

} // namespace basctl

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