/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include "macrodlg.hxx"
#include <basidesh.hxx>
#include <strings.hrc>
#include <iderid.hxx>

#include <iderdll.hxx>
#include "iderdll2.hxx"

#include "moduldlg.hxx"
#include <basic/basmgr.hxx>
#include <basic/sbmeth.hxx>
#include <basic/sbmod.hxx>
#include <com/sun/star/script/XLibraryContainer2.hpp>
#include <sal/log.hxx>
#include <sfx2/app.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/frame.hxx>
#include <sfx2/minfitem.hxx>
#include <sfx2/request.hxx>
#include <sfx2/sfxsids.hrc>
#include <tools/debug.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <osl/diagnose.h>

namespace basctl
{

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

MacroChooser::MacroChooser(weld::Window* pParnt, const Reference< frame::XFrame >& xDocFrame)
    : SfxDialogController(pParnt, u"modules/BasicIDE/ui/basicmacrodialog.ui"_ustr, u"BasicMacroDialog"_ustr)
    , m_xDocumentFrame(xDocFrame)
    // the Sfx doesn't ask the BasicManager whether modified or not
    // => start saving in case of a change without a into the BasicIDE.
    , bForceStoreBasic(false)
    , nMode(All)
    , m_xMacroNameEdit(m_xBuilder->weld_entry(u"macronameedit"_ustr))
    , m_xMacroFromTxT(m_xBuilder->weld_label(u"macrofromft"_ustr))
    , m_xMacrosSaveInTxt(m_xBuilder->weld_label(u"macrotoft"_ustr))
    , m_xBasicBox(new SbTreeListBox(m_xBuilder->weld_tree_view(u"libraries"_ustr), m_xDialog.get()))
    , m_xBasicBoxIter(m_xBasicBox->make_iterator())
    , m_xMacrosInTxt(m_xBuilder->weld_label(u"existingmacrosft"_ustr))
    , m_xMacroBox(m_xBuilder->weld_tree_view(u"macros"_ustr))
    , m_xMacroBoxIter(m_xMacroBox->make_iterator())
    , m_xRunButton(m_xBuilder->weld_button(u"ok"_ustr))
    , m_xCloseButton(m_xBuilder->weld_button(u"close"_ustr))
    , m_xAssignButton(m_xBuilder->weld_button(u"assign"_ustr))
    , m_xEditButton(m_xBuilder->weld_button(u"edit"_ustr))
    , m_xDelButton(m_xBuilder->weld_button(u"delete"_ustr))
    , m_xNewButton(m_xBuilder->weld_button(u"new"_ustr))
    , m_xOrganizeButton(m_xBuilder->weld_button(u"organize"_ustr))
    , m_xNewLibButton(m_xBuilder->weld_button(u"newlibrary"_ustr))
    , m_xNewModButton(m_xBuilder->weld_button(u"newmodule"_ustr))
{
    m_xBasicBox->set_size_request(m_xBasicBox->get_approximate_digit_width() * 30, m_xBasicBox->get_height_rows(18));
    m_xMacroBox->set_size_request(m_xMacroBox->get_approximate_digit_width() * 30, m_xMacroBox->get_height_rows(18));

    m_aMacrosInTxtBaseStr = m_xMacrosInTxt->get_label();

    m_xRunButton->connect_clicked( LINK( this, MacroChooser, ButtonHdl ) );
    m_xCloseButton->connect_clicked( LINK( this, MacroChooser, ButtonHdl ) );
    m_xAssignButton->connect_clicked( LINK( this, MacroChooser, ButtonHdl ) );
    m_xEditButton->connect_clicked( LINK( this, MacroChooser, ButtonHdl ) );
    m_xDelButton->connect_clicked( LINK( this, MacroChooser, ButtonHdl ) );
    m_xNewButton->connect_clicked( LINK( this, MacroChooser, ButtonHdl ) );
    m_xOrganizeButton->connect_clicked( LINK( this, MacroChooser, ButtonHdl ) );

    // Buttons only for MacroChooser::Recording
    m_xNewLibButton->connect_clicked( LINK( this, MacroChooser, ButtonHdl ) );
    m_xNewModButton->connect_clicked( LINK( this, MacroChooser, ButtonHdl ) );
    m_xNewLibButton->hide();       // default
    m_xNewModButton->hide();       // default
    m_xMacrosSaveInTxt->hide();    // default

    m_xMacroNameEdit->connect_changed( LINK( this, MacroChooser, EditModifyHdl ) );

    m_xBasicBox->connect_changed( LINK( this, MacroChooser, BasicSelectHdl ) );

    m_xMacroBox->connect_row_activated( LINK( this, MacroChooser, MacroDoubleClickHdl ) );
    m_xMacroBox->connect_changed( LINK( this, MacroChooser, MacroSelectHdl ) );
    m_xMacroBox->connect_popup_menu( LINK( this, MacroChooser, ContextMenuHdl ) );

    m_xBasicBox->SetMode( BrowseMode::Modules );

    if (SfxDispatcher* pDispatcher = GetDispatcher())
        pDispatcher->Execute( SID_BASICIDE_STOREALLMODULESOURCES );

    m_xBasicBox->ScanAllEntries();
}

MacroChooser::~MacroChooser()
{
    if (bForceStoreBasic)
    {
        SfxGetpApp()->SaveBasicAndDialogContainer();
        bForceStoreBasic = false;
    }
}

void MacroChooser::StoreMacroDescription()
{
    if (!m_xBasicBox->get_selected(m_xBasicBoxIter.get()))
        return;
    EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(m_xBasicBoxIter.get());
    OUString aMethodName;
    if (m_xMacroBox->get_selected(m_xMacroBoxIter.get()))
        aMethodName = m_xMacroBox->get_text(*m_xMacroBoxIter);
    else
        aMethodName = m_xMacroNameEdit->get_text();
    if ( !aMethodName.isEmpty() )
    {
        aDesc.SetMethodName( aMethodName );
        aDesc.SetType( OBJ_TYPE_METHOD );
    }

    if (ExtraData* pData = basctl::GetExtraData())
        pData->SetLastEntryDescriptor( aDesc );
}

void MacroChooser::RestoreMacroDescription()
{
    // The following call is a workaround to ensure the last used macro is scrolled to in kf5
    m_xDialog->resize_to_request();

    EntryDescriptor aDesc;
    if (Shell* pShell = GetShell())
    {
        if (BaseWindow* pCurWin = pShell->GetCurWindow())
            aDesc = pCurWin->CreateEntryDescriptor();
    }
    else
    {
        if (ExtraData* pData = basctl::GetExtraData())
            aDesc = pData->GetLastEntryDescriptor();
    }

    m_xBasicBox->SetCurrentEntry(aDesc);
    BasicSelectHdl(m_xBasicBox->get_widget());

    OUString aLastMacro( aDesc.GetMethodName() );
    if (!aLastMacro.isEmpty())
    {
        // find entry in macro box
        auto nIndex = m_xMacroBox->find_text(aLastMacro);
        if (nIndex != -1)
            m_xMacroBox->select(nIndex);
        else
        {
            m_xMacroNameEdit->set_text(aLastMacro);
            m_xMacroNameEdit->select_region(0, 0);
        }
    }
}

short MacroChooser::run()
{
    RestoreMacroDescription();

    // #104198 Check if "wrong" document is active
    bool bSelectedEntry = m_xBasicBox->get_cursor(m_xBasicBoxIter.get());
    EntryDescriptor aDesc(m_xBasicBox->GetEntryDescriptor(bSelectedEntry ? m_xBasicBoxIter.get() : nullptr));
    const ScriptDocument& rSelectedDoc(aDesc.GetDocument());

    // App Basic is always ok, so only check if shell was found
    if( rSelectedDoc.isDocument() && !rSelectedDoc.isActive() )
    {
        // Search for the right entry
        bool bValidIter = m_xBasicBox->get_iter_first(*m_xBasicBoxIter);
        while (bValidIter)
        {
            EntryDescriptor aCmpDesc(m_xBasicBox->GetEntryDescriptor(m_xBasicBoxIter.get()));
            const ScriptDocument& rCmpDoc( aCmpDesc.GetDocument() );
            if (rCmpDoc.isDocument() && rCmpDoc.isActive())
            {
                std::unique_ptr<weld::TreeIter> xEntry(m_xBasicBox->make_iterator());
                m_xBasicBox->copy_iterator(*m_xBasicBoxIter, *xEntry);
                std::unique_ptr<weld::TreeIter> xLastValid(m_xBasicBox->make_iterator());
                bool bValidEntryIter = true;
                do
                {
                    m_xBasicBox->copy_iterator(*xEntry, *xLastValid);
                    bValidEntryIter = m_xBasicBox->iter_children(*xEntry);
                }
                while (bValidEntryIter);
                m_xBasicBox->set_cursor(*xLastValid);
            }
            bValidIter = m_xBasicBox->iter_next_sibling(*m_xBasicBoxIter);
        }
    }

    CheckButtons();
    UpdateFields();

    // tdf#62955 - Allow searching a name with typing the first letter
    m_xBasicBox->get_widget().grab_focus();

    if ( StarBASIC::IsRunning() )
        m_xCloseButton->grab_focus();

    return SfxDialogController::run();
}

void MacroChooser::EnableButton(weld::Button& rButton, bool bEnable)
{
    if ( bEnable )
    {
        if (nMode == ChooseOnly || nMode == Recording)
            rButton.set_sensitive(&rButton == m_xRunButton.get());
        else
            rButton.set_sensitive(true);
    }
    else
        rButton.set_sensitive(false);
}

SbMethod* MacroChooser::GetMacro()
{
    if (!m_xBasicBox->get_cursor(m_xBasicBoxIter.get()))
        return nullptr;
    SbModule* pModule = m_xBasicBox->FindModule(m_xBasicBoxIter.get());
    if (!pModule)
        return nullptr;
    if (!m_xMacroBox->get_selected(m_xMacroBoxIter.get()))
        return nullptr;
    OUString aMacroName(m_xMacroBox->get_text(*m_xMacroBoxIter));
    return pModule->FindMethod(aMacroName, SbxClassType::Method);
}

void MacroChooser::DeleteMacro()
{
    SbMethod* pMethod = GetMacro();
    DBG_ASSERT( pMethod, "DeleteMacro: No Macro !" );
    if (!(pMethod && QueryDelMacro(pMethod->GetName(), m_xDialog.get())))
        return;

    if (SfxDispatcher* pDispatcher = GetDispatcher())
        pDispatcher->Execute( SID_BASICIDE_STOREALLMODULESOURCES );

    // mark current doc as modified:
    StarBASIC* pBasic = FindBasic(pMethod);
    assert(pBasic && "Basic?!");
    BasicManager* pBasMgr = FindBasicManager( pBasic );
    DBG_ASSERT( pBasMgr, "BasMgr?" );
    ScriptDocument aDocument( ScriptDocument::getDocumentForBasicManager( pBasMgr ) );
    if ( aDocument.isDocument() )
    {
        aDocument.setDocumentModified();
        if (SfxBindings* pBindings = GetBindingsPtr())
            pBindings->Invalidate( SID_SAVEDOC );
    }

    SbModule* pModule = pMethod->GetModule();
    assert(pModule && "DeleteMacro: No Module?!");
    OUString aSource( pModule->GetSource32() );
    sal_uInt16 nStart, nEnd;
    pMethod->GetLineRange( nStart, nEnd );
    pModule->GetMethods()->Remove( pMethod );
    CutLines( aSource, nStart-1, nEnd-nStart+1 );
    pModule->SetSource32( aSource );

    // update module in library
    OUString aLibName = pBasic->GetName();
    OUString aModName = pModule->GetName();
    OSL_VERIFY( aDocument.updateModule( aLibName, aModName, aSource ) );

    bool bSelected = m_xMacroBox->get_selected(m_xMacroBoxIter.get());
    DBG_ASSERT(bSelected, "DeleteMacro: Entry ?!");
    m_xMacroBox->remove(*m_xMacroBoxIter);
    bForceStoreBasic = true;
}

SbMethod* MacroChooser::CreateMacro()
{
    SbMethod* pMethod = nullptr;
    if (!m_xBasicBox->get_cursor(m_xBasicBoxIter.get()) && !m_xBasicBox->get_iter_first(*m_xBasicBoxIter))
    {
        SAL_WARN("basctl.basicide", "neither cursor set nor root entry to use as fallback");
        return nullptr;
    }
    EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(m_xBasicBoxIter.get());
    const ScriptDocument& aDocument( aDesc.GetDocument() );
    OSL_ENSURE( aDocument.isAlive(), "MacroChooser::CreateMacro: no document!" );
    if ( !aDocument.isAlive() )
        return nullptr;

    OUString aLibName( aDesc.GetLibName() );

    if ( aLibName.isEmpty() )
        aLibName = "Standard" ;

    aDocument.getOrCreateLibrary( E_SCRIPTS, aLibName );

    OUString aOULibName( aLibName );
    Reference< script::XLibraryContainer > xModLibContainer( aDocument.getLibraryContainer( E_SCRIPTS ) );
    if ( xModLibContainer.is() && xModLibContainer->hasByName( aOULibName ) && !xModLibContainer->isLibraryLoaded( aOULibName ) )
        xModLibContainer->loadLibrary( aOULibName );
    Reference< script::XLibraryContainer > xDlgLibContainer( aDocument.getLibraryContainer( E_DIALOGS ) );
    if ( xDlgLibContainer.is() && xDlgLibContainer->hasByName( aOULibName ) && !xDlgLibContainer->isLibraryLoaded( aOULibName ) )
        xDlgLibContainer->loadLibrary( aOULibName );

    BasicManager* pBasMgr = aDocument.getBasicManager();
    StarBASIC* pBasic = pBasMgr ? pBasMgr->GetLib( aLibName ) : nullptr;
    if ( pBasic )
    {
        SbModule* pModule = nullptr;
        OUString aModName( aDesc.GetName() );
        if ( !aModName.isEmpty() )
        {
            // extract the module name from the string like "Sheet1 (Example1)"
            if( aDesc.GetLibSubName() == IDEResId(RID_STR_DOCUMENT_OBJECTS) )
            {
                aModName = aModName.getToken( 0, ' ' );
            }
            pModule = pBasic->FindModule( aModName );
        }
        else if ( !pBasic->GetModules().empty() )
            pModule = pBasic->GetModules().front().get();

        // Retain the desired macro name before the macro dialog box is forced to close
        // by opening the module name dialog window when no module exists in the current library.
        OUString aSubName = m_xMacroNameEdit->get_text();

        if ( !pModule )
        {
            pModule = createModImpl(m_xDialog.get(), aDocument, *m_xBasicBox, aLibName, aModName, false);
        }

        DBG_ASSERT( !pModule || !pModule->FindMethod( aSubName, SbxClassType::Method ), "Macro exists already!" );
        pMethod = pModule ? basctl::CreateMacro( pModule, aSubName ) : nullptr;
    }

    return pMethod;
}

void MacroChooser::SaveSetCurEntry(weld::TreeView& rBox, const weld::TreeIter& rEntry)
{
    // the edit would be killed by the highlight otherwise:

    OUString aSaveText(m_xMacroNameEdit->get_text());
    int nStartPos, nEndPos;
    m_xMacroNameEdit->get_selection_bounds(nStartPos, nEndPos);

    rBox.set_cursor(rEntry);

    m_xMacroNameEdit->set_text(aSaveText);
    m_xMacroNameEdit->select_region(nStartPos, nEndPos);
}

void MacroChooser::CheckButtons()
{
    const bool bCurEntry = m_xBasicBox->get_cursor(m_xBasicBoxIter.get());
    EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(bCurEntry ? m_xBasicBoxIter.get() : nullptr);
    const bool bMacroEntry = m_xMacroBox->get_selected(nullptr);
    SbMethod* pMethod = GetMacro();

    // check, if corresponding libraries are readonly
    bool bReadOnly = false;
    sal_uInt16 nDepth = bCurEntry ? m_xBasicBox->get_iter_depth(*m_xBasicBoxIter) : 0;
    if ( nDepth == 1 || nDepth == 2 )
    {
        const ScriptDocument& aDocument( aDesc.GetDocument() );
        const OUString& aOULibName( aDesc.GetLibName() );
        Reference< script::XLibraryContainer2 > xModLibContainer( aDocument.getLibraryContainer( E_SCRIPTS ), UNO_QUERY );
        Reference< script::XLibraryContainer2 > xDlgLibContainer( aDocument.getLibraryContainer( E_DIALOGS ), UNO_QUERY );
        if ( ( xModLibContainer.is() && xModLibContainer->hasByName( aOULibName ) && xModLibContainer->isLibraryReadOnly( aOULibName ) ) ||
                ( xDlgLibContainer.is() && xDlgLibContainer->hasByName( aOULibName ) && xDlgLibContainer->isLibraryReadOnly( aOULibName ) ) )
        {
            bReadOnly = true;
        }
    }

    if (nMode != Recording)
    {
        // Run...
        bool bEnable = pMethod != nullptr;
        if (nMode != ChooseOnly && StarBASIC::IsRunning())
            bEnable = false;
        EnableButton(*m_xRunButton, bEnable);
    }

    // organising still possible?

    // Assign...
    EnableButton(*m_xAssignButton, pMethod != nullptr);

    // Edit...
    EnableButton(*m_xEditButton, bMacroEntry);

    // Organizer...
    EnableButton(*m_xOrganizeButton, !StarBASIC::IsRunning() && nMode == All);

    // m_xDelButton/m_xNewButton ->...
    bool bProtected = bCurEntry && m_xBasicBox->IsEntryProtected(m_xBasicBoxIter.get());
    bool bShare = ( aDesc.GetLocation() == LIBRARY_LOCATION_SHARE );
    bool bEnable = !StarBASIC::IsRunning() && nMode == All && !bProtected && !bReadOnly && !bShare;
    EnableButton(*m_xDelButton, bEnable);
    EnableButton(*m_xNewButton, bEnable);
    if (nMode == All)
    {
        if (pMethod)
        {
            m_xDelButton->show();
            m_xNewButton->hide();
        }
        else
        {
            m_xNewButton->show();
            m_xDelButton->hide();
        }
    }

    if (nMode == Recording)
    {
        // save button
        m_xRunButton->set_sensitive(!bProtected && !bReadOnly && !bShare);
        // new library button
        m_xNewLibButton->set_sensitive(!bShare);
        // new module button
        m_xNewModButton->set_sensitive(!bProtected && !bReadOnly && !bShare);
    }
}

IMPL_LINK_NOARG(MacroChooser, MacroDoubleClickHdl, weld::TreeView&, bool)
{
    SbMethod* pMethod = GetMacro();
    SbModule* pModule = pMethod ? pMethod->GetModule() : nullptr;
    StarBASIC* pBasic = pModule ? static_cast<StarBASIC*>(pModule->GetParent()) : nullptr;
    BasicManager* pBasMgr = pBasic ? FindBasicManager(pBasic) : nullptr;
    ScriptDocument aDocument(ScriptDocument::getDocumentForBasicManager(pBasMgr));
    if (aDocument.isDocument() && !aDocument.allowMacros())
    {
        std::unique_ptr<weld::MessageDialog> xError(
            Application::CreateMessageDialog(m_xDialog.get(), VclMessageType::Warning,
                                             VclButtonsType::Ok, IDEResId(RID_STR_CANNOTRUNMACRO)));
        xError->run();
        return true;
    }

    StoreMacroDescription();
    if (nMode == Recording)
    {
        if (pMethod && !QueryReplaceMacro(pMethod->GetName(), m_xDialog.get()))
            return true;
    }

    m_xDialog->response(Macro_OkRun);
    return true;
}

IMPL_LINK_NOARG(MacroChooser, MacroSelectHdl, weld::TreeView&, void)
{
    UpdateFields();
    CheckButtons();
}

IMPL_LINK_NOARG(MacroChooser, BasicSelectHdl, weld::TreeView&, void)
{
    SbModule* pModule = nullptr;
    if (m_xBasicBox->get_cursor(m_xBasicBoxIter.get()))
        pModule = m_xBasicBox->FindModule(m_xBasicBoxIter.get());
    m_xMacroBox->clear();
    if (pModule)
    {
        m_xMacrosInTxt->set_label(m_aMacrosInTxtBaseStr + " " + pModule->GetName());

        m_xMacroBox->freeze();

        sal_uInt32 nMacroCount = pModule->GetMethods()->Count();
        for ( sal_uInt32 iMeth = 0; iMeth  < nMacroCount; iMeth++ )
        {
            SbMethod* pMethod = static_cast<SbMethod*>(pModule->GetMethods()->Get(iMeth));
            assert(pMethod && "Method not found!");
            if (pMethod->IsHidden())
                continue;
            m_xMacroBox->append_text(pMethod->GetName());
        }

        m_xMacroBox->thaw();

        if (m_xMacroBox->get_iter_first(*m_xMacroBoxIter))
            m_xMacroBox->set_cursor(*m_xMacroBoxIter);
    }

    UpdateFields();
    CheckButtons();
}

IMPL_LINK_NOARG(MacroChooser, EditModifyHdl, weld::Entry&, void)
{
    // select the module in which the macro is put at "new",
    // if BasicManager or Lib is selecting
    if (m_xBasicBox->get_cursor(m_xBasicBoxIter.get()))
    {
        sal_uInt16 nDepth = m_xBasicBox->get_iter_depth(*m_xBasicBoxIter);
        if (nDepth == 1 && m_xBasicBox->IsEntryProtected(m_xBasicBoxIter.get()))
        {
            // then put to the respective Std-Lib...
            m_xBasicBox->iter_parent(*m_xBasicBoxIter);
            m_xBasicBox->iter_children(*m_xBasicBoxIter);
        }
        if (nDepth < 2)
        {
            std::unique_ptr<weld::TreeIter> xNewEntry(m_xBasicBox->make_iterator());
            m_xBasicBox->copy_iterator(*m_xBasicBoxIter, *xNewEntry);
            bool bCurEntry = true;
            do
            {
                bCurEntry = m_xBasicBox->iter_children(*m_xBasicBoxIter);
                if (bCurEntry)
                {
                    m_xBasicBox->copy_iterator(*m_xBasicBoxIter, *xNewEntry);
                    nDepth = m_xBasicBox->get_iter_depth(*m_xBasicBoxIter);
                }
            }
            while (bCurEntry && (nDepth < 2));
            SaveSetCurEntry(m_xBasicBox->get_widget(), *xNewEntry);
        }
        auto nCount = m_xMacroBox->n_children();
        if (nCount)
        {
            OUString aEdtText(m_xMacroNameEdit->get_text());
            bool bFound = false;
            bool bValidIter = m_xMacroBox->get_iter_first(*m_xMacroBoxIter);
            while (bValidIter)
            {
                if (m_xMacroBox->get_text(*m_xMacroBoxIter).equalsIgnoreAsciiCase(aEdtText))
                {
                    SaveSetCurEntry(*m_xMacroBox, *m_xMacroBoxIter);
                    bFound = true;
                    break;
                }
                bValidIter = m_xMacroBox->iter_next_sibling(*m_xMacroBoxIter);
            }
            if (!bFound)
            {
                bValidIter = m_xMacroBox->get_selected(m_xMacroBoxIter.get());
                // if the entry exists ->Select ->Description...
                if (bValidIter)
                    m_xMacroBox->unselect(*m_xMacroBoxIter);
            }
        }
    }

    CheckButtons();
}

IMPL_LINK(MacroChooser, ButtonHdl, weld::Button&, rButton, void)
{
    // apart from New/Record the Description is done by LoseFocus
    if (&rButton == m_xRunButton.get())
    {
        StoreMacroDescription();

        // #116444# check security settings before macro execution
        if (nMode == All)
        {
            SbMethod* pMethod = GetMacro();
            SbModule* pModule = pMethod ? pMethod->GetModule() : nullptr;
            StarBASIC* pBasic = pModule ? static_cast<StarBASIC*>(pModule->GetParent()) : nullptr;
            BasicManager* pBasMgr = pBasic ? FindBasicManager(pBasic) : nullptr;
            if ( pBasMgr )
            {
                ScriptDocument aDocument( ScriptDocument::getDocumentForBasicManager( pBasMgr ) );
                if ( aDocument.isDocument() && !aDocument.allowMacros() )
                {
                    std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog(m_xDialog.get(),
                                                                VclMessageType::Warning, VclButtonsType::Ok, IDEResId(RID_STR_CANNOTRUNMACRO)));
                    xError->run();
                    return;
                }
            }
        }
        else if (nMode == Recording )
        {
            if ( !IsValidSbxName(m_xMacroNameEdit->get_text()) )
            {
                std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog(m_xDialog.get(),
                                                            VclMessageType::Warning, VclButtonsType::Ok, IDEResId(RID_STR_BADSBXNAME)));
                xError->run();
                m_xMacroNameEdit->select_region(0, -1);
                m_xMacroNameEdit->grab_focus();
                return;
            }

            SbMethod* pMethod = GetMacro();
            if (pMethod && !QueryReplaceMacro(pMethod->GetName(), m_xDialog.get()))
                return;
        }

        m_xDialog->response(Macro_OkRun);
    }
    else if (&rButton == m_xCloseButton.get())
    {
        StoreMacroDescription();
        m_xDialog->response(Macro_Close);
    }
    else if (&rButton == m_xEditButton.get() || &rButton == m_xDelButton.get() || &rButton == m_xNewButton.get())
    {
        if (!m_xBasicBox->get_cursor(m_xBasicBoxIter.get()) && !m_xBasicBox->get_iter_first(*m_xBasicBoxIter))
        {
            SAL_WARN("basctl.basicide", "neither cursor set nor root entry to use as fallback");
            return;
        }
        EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(m_xBasicBoxIter.get());
        const ScriptDocument& aDocument( aDesc.GetDocument() );
        DBG_ASSERT( aDocument.isAlive(), "MacroChooser::ButtonHdl: no document, or document is dead!" );
        if ( !aDocument.isAlive() )
            return;
        BasicManager* pBasMgr = aDocument.getBasicManager();
        const OUString& aLib( aDesc.GetLibName() );
        OUString aMod( aDesc.GetName() );
        // extract the module name from the string like "Sheet1 (Example1)"
        if( aDesc.GetLibSubName() == IDEResId(RID_STR_DOCUMENT_OBJECTS) )
        {
            aMod = aMod.getToken( 0, ' ' );
        }
        const OUString& aSub( aDesc.GetMethodName() );
        SfxMacroInfoItem aInfoItem( SID_BASICIDE_ARG_MACROINFO, pBasMgr, aLib, aMod, aSub, OUString() );
        if (&rButton == m_xEditButton.get())
        {
            if (m_xMacroBox->get_selected(m_xMacroBoxIter.get()))
                aInfoItem.SetMethod(m_xMacroBox->get_text(*m_xMacroBoxIter));
            StoreMacroDescription();
            m_xDialog->hide(); // tdf#126828 dismiss dialog before opening new window

            SfxAllItemSet aArgs( SfxGetpApp()->GetPool() );
            SfxRequest aRequest( SID_BASICIDE_APPEAR, SfxCallMode::SYNCHRON, aArgs );
            SfxGetpApp()->ExecuteSlot( aRequest );

            if (SfxDispatcher* pDispatcher = GetDispatcher())
            {
                pDispatcher->ExecuteList(SID_BASICIDE_EDITMACRO,
                        SfxCallMode::ASYNCHRON, { &aInfoItem });
            }
            m_xDialog->response(Macro_Edit);
        }
        else
        {
            if (&rButton == m_xDelButton.get())
            {
                DeleteMacro();
                if (SfxDispatcher* pDispatcher = GetDispatcher())
                {
                    pDispatcher->ExecuteList( SID_BASICIDE_UPDATEMODULESOURCE,
                                  SfxCallMode::SYNCHRON, { &aInfoItem });
                }
                CheckButtons();
                UpdateFields();
                //if ( m_xMacroBox->GetCurEntry() )    // OV-Bug ?
                //  m_xMacroBox->Select( m_xMacroBox->GetCurEntry() );
            }
            else
            {
                if ( !IsValidSbxName(m_xMacroNameEdit->get_text()) )
                {
                    std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog(m_xDialog.get(),
                                                                VclMessageType::Warning, VclButtonsType::Ok, IDEResId(RID_STR_BADSBXNAME)));
                    xError->run();
                    m_xMacroNameEdit->select_region(0, -1);
                    m_xMacroNameEdit->grab_focus();
                    return;
                }
                SbMethod* pMethod = CreateMacro();
                if ( pMethod )
                {
                    aInfoItem.SetMethod( pMethod->GetName() );
                    aInfoItem.SetModule( pMethod->GetModule()->GetName() );
                    aInfoItem.SetLib( pMethod->GetModule()->GetParent()->GetName() );
                    SfxAllItemSet aArgs( SfxGetpApp()->GetPool() );
                    SfxRequest aRequest( SID_BASICIDE_APPEAR, SfxCallMode::SYNCHRON, aArgs );
                    SfxGetpApp()->ExecuteSlot( aRequest );

                    if (SfxDispatcher* pDispatcher = GetDispatcher())
                    {
                        pDispatcher->ExecuteList(SID_BASICIDE_EDITMACRO,
                                SfxCallMode::ASYNCHRON, { &aInfoItem });
                    }
                    StoreMacroDescription();
                    m_xDialog->response(Macro_New);
                }
            }
        }
    }
    else if (&rButton == m_xAssignButton.get())
    {
        if (!m_xBasicBox->get_cursor(m_xBasicBoxIter.get()) && !m_xBasicBox->get_iter_first(*m_xBasicBoxIter))
        {
            SAL_WARN("basctl.basicide", "neither cursor set nor root entry to use as fallback");
            return;
        }
        EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(m_xBasicBoxIter.get());
        const ScriptDocument& aDocument( aDesc.GetDocument() );
        DBG_ASSERT( aDocument.isAlive(), "MacroChooser::ButtonHdl: no document, or document is dead!" );
        if ( !aDocument.isAlive() )
            return;
        BasicManager* pBasMgr = aDocument.getBasicManager();
        const OUString& aLib( aDesc.GetLibName() );
        const OUString& aMod( aDesc.GetName() );
        OUString aSub( m_xMacroNameEdit->get_text() );
        SbMethod* pMethod = GetMacro();
        DBG_ASSERT( pBasMgr, "BasMgr?" );
        DBG_ASSERT( pMethod, "Method?" );
        OUString aComment( GetInfo( pMethod ) );
        SfxMacroInfoItem aItem( SID_MACROINFO, pBasMgr, aLib, aMod, aSub, aComment );
        SfxAllItemSet Args( SfxGetpApp()->GetPool() );

        SfxAllItemSet aInternalSet(SfxGetpApp()->GetPool());
        if (m_xDocumentFrame.is())
            aInternalSet.Put(SfxUnoFrameItem(SID_FILLFRAME, m_xDocumentFrame));

        SfxRequest aRequest(SID_CONFIGACCEL, SfxCallMode::SYNCHRON, Args, aInternalSet);
        aRequest.AppendItem( aItem );
        SfxGetpApp()->ExecuteSlot( aRequest );
    }
    else if (&rButton == m_xNewLibButton.get())
    {
        if (!m_xBasicBox->get_cursor(m_xBasicBoxIter.get()) && !m_xBasicBox->get_iter_first(*m_xBasicBoxIter))
        {
            SAL_WARN("basctl.basicide", "neither cursor set nor root entry to use as fallback");
            return;
        }
        EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(m_xBasicBoxIter.get());
        const ScriptDocument& aDocument( aDesc.GetDocument() );
        createLibImpl(m_xDialog.get(), aDocument, nullptr, m_xBasicBox.get());
    }
    else if (&rButton == m_xNewModButton.get())
    {
        if (!m_xBasicBox->get_cursor(m_xBasicBoxIter.get()) && !m_xBasicBox->get_iter_first(*m_xBasicBoxIter))
        {
            SAL_WARN("basctl.basicide", "neither cursor set nor root entry to use as fallback");
            return;
        }
        EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(m_xBasicBoxIter.get());
        const ScriptDocument& aDocument( aDesc.GetDocument() );
        const OUString& aLibName( aDesc.GetLibName() );
        createModImpl(m_xDialog.get(), aDocument, *m_xBasicBox, aLibName, OUString(), true);
    }
    else if (&rButton == m_xOrganizeButton.get())
    {
        StoreMacroDescription();

        m_xBasicBox->get_selected(m_xBasicBoxIter.get());
        auto xDlg(std::make_shared<OrganizeDialog>(m_xDialog.get(), nullptr, 0));
        weld::DialogController::runAsync(xDlg, [this](sal_Int32 nRet) {
            if (nRet == RET_OK) // not only closed
            {
                m_xDialog->response(Macro_Edit);
                return;
            }

            Shell* pShell = GetShell();
            if ( pShell && pShell->IsAppBasicModified() )
                bForceStoreBasic = true;

            m_xBasicBox->UpdateEntries();
        });
    }
}

IMPL_LINK(MacroChooser, ContextMenuHdl, const CommandEvent&, rCEvt, bool)
{
    if (rCEvt.GetCommand() != CommandEventId::ContextMenu || !m_xMacroBox->n_children())
         return false;

    std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(m_xMacroBox.get(), u"modules/BasicIDE/ui/sortmenu.ui"_ustr));
    std::unique_ptr<weld::Menu> xPopup(xBuilder->weld_menu(u"sortmenu"_ustr));
    std::unique_ptr<weld::Menu> xDropMenu(xBuilder->weld_menu(u"sortsubmenu"_ustr));
    xDropMenu->set_active(u"alphabetically"_ustr, m_xMacroBox->get_sort_order());
    xDropMenu->set_active(u"properorder"_ustr, !m_xMacroBox->get_sort_order());

    OUString sCommand(xPopup->popup_at_rect(m_xMacroBox.get(), tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1))));
    if (sCommand == "alphabetically")
    {
        m_xMacroBox->make_sorted();
    }
    else if (sCommand == "properorder")
    {
        m_xMacroBox->make_unsorted();
        BasicSelectHdl(m_xBasicBox->get_widget());
    }
    else if (!sCommand.isEmpty())
    {
         SAL_WARN("basctl.basicide", "Unknown context menu action: " << sCommand );
    }

    return true;
}

void MacroChooser::UpdateFields()
{
    auto nMacroEntry = m_xMacroBox->get_selected_index();
    m_xMacroNameEdit->set_text(u""_ustr);
    if (nMacroEntry != -1)
        m_xMacroNameEdit->set_text(m_xMacroBox->get_text(nMacroEntry));
}

void MacroChooser::SetMode (Mode nM)
{
    nMode = nM;
    switch (nMode)
    {
        case All:
        {
            m_xRunButton->set_label(IDEResId(RID_STR_RUN));
            EnableButton(*m_xDelButton, true);
            EnableButton(*m_xNewButton, true);
            EnableButton(*m_xOrganizeButton, true);
            break;
        }

        case ChooseOnly:
        {
            m_xRunButton->set_label(IDEResId(RID_STR_CHOOSE));
            EnableButton(*m_xDelButton, false);
            EnableButton(*m_xNewButton, false);
            EnableButton(*m_xOrganizeButton, false);
            break;
        }

        case Recording:
        {
            m_xRunButton->set_label(IDEResId(RID_STR_RECORD));
            EnableButton(*m_xDelButton, false);
            EnableButton(*m_xNewButton, false);
            EnableButton(*m_xOrganizeButton, false);

            m_xAssignButton->hide();
            m_xEditButton->hide();
            m_xDelButton->hide();
            m_xNewButton->hide();
            m_xOrganizeButton->hide();
            m_xMacroFromTxT->hide();

            m_xNewLibButton->show();
            m_xNewModButton->show();
            m_xMacrosSaveInTxt->show();

            break;
        }
    }
    CheckButtons();
}

OUString MacroChooser::GetInfo( SbxVariable* pVar )
{
    OUString aComment;
    SbxInfoRef xInfo = pVar->GetInfo();
    if ( xInfo.is() )
        aComment = xInfo->GetComment();
    return aComment;
}


} // namespace basctl

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