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

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

#include "moduldlg.hxx"
#include <localizationmgr.hxx>
#include <basidesh.hxx>
#include <basobj.hxx>

#include <basic/basmgr.hxx>
#include <com/sun/star/script/XLibraryContainerPassword.hpp>
#include <com/sun/star/script/XLibraryContainer2.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <comphelper/processfactory.hxx>
#include <sfx2/app.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/frame.hxx>
#include <sfx2/request.hxx>
#include <sfx2/sfxsids.hrc>
#include <svl/stritem.hxx>
#include <vcl/transfer.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <tools/debug.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <xmlscript/xmldlg_imexp.hxx>
#include <com/sun/star/uno/XComponentContext.hpp>

namespace basctl
{

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

IMPL_LINK(ObjectPage, EditingEntryHdl, const weld::TreeIter&, rEntry, bool)
{
    bool bRet = false;

    sal_uInt16 nDepth = m_xBasicBox->get_iter_depth(rEntry);
    if (nDepth >= 2)
    {
        EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(&rEntry);
        const ScriptDocument& aDocument( aDesc.GetDocument() );
        const OUString& aLibName( aDesc.GetLibName() );
        Reference< script::XLibraryContainer2 > xModLibContainer( aDocument.getLibraryContainer( E_SCRIPTS ) );
        Reference< script::XLibraryContainer2 > xDlgLibContainer( aDocument.getLibraryContainer( E_DIALOGS ) );
        if ( !( ( xModLibContainer.is() && xModLibContainer->hasByName( aLibName ) && xModLibContainer->isLibraryReadOnly( aLibName ) ) ||
                ( xDlgLibContainer.is() && xDlgLibContainer->hasByName( aLibName ) && xDlgLibContainer->isLibraryReadOnly( aLibName ) ) ) )
        {
            // allow editing only for libraries, which are not readonly
            bRet = true;
        }
    }

    return bRet;
}

IMPL_LINK(ObjectPage, EditedEntryHdl, const IterString&, rIterString, bool)
{
    const weld::TreeIter& rEntry = rIterString.first;
    OUString sNewText = rIterString.second;

    if ( !IsValidSbxName(sNewText) )
    {
        std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog(m_pDialog->getDialog(),
                                                    VclMessageType::Warning, VclButtonsType::Ok, IDEResId(RID_STR_BADSBXNAME)));
        xError->run();
        return false;
    }

    OUString aCurText(m_xBasicBox->get_text(rEntry));
    if ( aCurText == sNewText )
        // nothing to do
        return true;

    EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(&rEntry);
    const ScriptDocument& aDocument( aDesc.GetDocument() );
    DBG_ASSERT( aDocument.isValid(), "ExtTreeListBox::EditedEntry: no document!" );
    if ( !aDocument.isValid() )
        return false;
    const OUString& aLibName( aDesc.GetLibName() );
    EntryType eType = aDesc.GetType();

    bool bSuccess = eType == OBJ_TYPE_MODULE ?
        RenameModule(m_pDialog->getDialog(), aDocument, aLibName, aCurText, sNewText) :
        RenameDialog(m_pDialog->getDialog(), aDocument, aLibName, aCurText, sNewText);

    if ( !bSuccess )
        return false;

    MarkDocumentModified( aDocument );

    if (SfxDispatcher* pDispatcher = GetDispatcher())
    {
        SbxItem aSbxItem(SID_BASICIDE_ARG_SBX, aDocument, aLibName, sNewText, SbTreeListBox::ConvertType(eType));
        pDispatcher->ExecuteList( SID_BASICIDE_SBXRENAMED,
                            SfxCallMode::SYNCHRON, { &aSbxItem });
    }

    // OV-Bug?!
    m_xBasicBox->set_text(rEntry, sNewText);
    m_xBasicBox->set_cursor(rEntry);
    m_xBasicBox->unselect(rEntry);
    m_xBasicBox->select(rEntry); // so that handler is called => update edit

    return true;
}

void Shell::CopyDialogResources(
    Reference< io::XInputStreamProvider >& io_xISP,
    ScriptDocument const& rSourceDoc,
    OUString const& rSourceLibName,
    ScriptDocument const& rDestDoc,
    OUString const& rDestLibName,
    std::u16string_view rDlgName
)
{
    if ( !io_xISP.is() )
        return;

    // Get StringResourceManager
    Reference< container::XNameContainer > xSourceDialogLib( rSourceDoc.getLibrary( E_DIALOGS, rSourceLibName, true ) );
    Reference< XStringResourceManager > xSourceMgr =
        LocalizationMgr::getStringResourceFromDialogLibrary( xSourceDialogLib );
    if( !xSourceMgr.is() )
        return;
    bool bSourceLocalized = xSourceMgr->getLocales().hasElements();

    Reference< container::XNameContainer > xDestDialogLib( rDestDoc.getLibrary( E_DIALOGS, rDestLibName, true ) );
    Reference< XStringResourceManager > xDestMgr =
        LocalizationMgr::getStringResourceFromDialogLibrary( xDestDialogLib );
    if( !xDestMgr.is() )
        return;
    bool bDestLocalized = xDestMgr->getLocales().hasElements();

    if( !bSourceLocalized && !bDestLocalized )
        return;

    // create dialog model
    const Reference< XComponentContext >& xContext = comphelper::getProcessComponentContext();
    Reference< container::XNameContainer > xDialogModel( xContext->getServiceManager()->createInstanceWithContext
        ( u"com.sun.star.awt.UnoControlDialogModel"_ustr, xContext ), UNO_QUERY );
    Reference< io::XInputStream > xInput( io_xISP->createInputStream() );
    ::xmlscript::importDialogModel( xInput, xDialogModel, xContext, rSourceDoc.isDocument() ? rSourceDoc.getDocument() : Reference< frame::XModel >() );

    if( !xDialogModel.is() )
        return;

    if( bSourceLocalized && bDestLocalized )
    {
        LocalizationMgr::copyResourceForDroppedDialog( xDialogModel, rDlgName, xDestMgr, xSourceMgr );
    }
    else if( bSourceLocalized )
    {
        LocalizationMgr::resetResourceForDialog( xDialogModel, xSourceMgr );
    }
    else if( bDestLocalized )
    {
        LocalizationMgr::setResourceIDsForDialog( xDialogModel, xDestMgr );
    }
    io_xISP = ::xmlscript::exportDialogModel( xDialogModel, xContext, rDestDoc.isDocument() ? rDestDoc.getDocument() : Reference< frame::XModel >() );
}

void OrganizeDialog::SetCurrentEntry(const css::uno::Reference<css::frame::XFrame>& xDocFrame)
{
    if (!xDocFrame)
        return;
    Reference<css::frame::XController> xController(xDocFrame->getController());
    if (!xController)
        return;
    Reference<css::frame::XModel> xModel(xController->getModel());
    if (!xModel)
        return;
    EntryDescriptor aDesc(ScriptDocument(xModel), LIBRARY_LOCATION_DOCUMENT, OUString(), OUString(), OUString(), OBJ_TYPE_DOCUMENT);
    m_xModulePage->SetCurrentEntry(aDesc);
    m_xDialogPage->SetCurrentEntry(aDesc);
}

// OrganizeDialog
OrganizeDialog::OrganizeDialog(weld::Window* pParent, const css::uno::Reference<css::frame::XFrame>& xDocFrame, sal_Int16 tabId)
    : GenericDialogController(pParent, u"modules/BasicIDE/ui/organizedialog.ui"_ustr, u"OrganizeDialog"_ustr)
    , m_xTabCtrl(m_xBuilder->weld_notebook(u"tabcontrol"_ustr))
    , m_xModulePage(new ObjectPage(m_xTabCtrl->get_page(u"modules"_ustr), u"ModulePage"_ustr, BrowseMode::Modules, this))
    , m_xDialogPage(new ObjectPage(m_xTabCtrl->get_page(u"dialogs"_ustr), u"DialogPage"_ustr, BrowseMode::Dialogs, this))
    , m_xLibPage(new LibPage(m_xTabCtrl->get_page(u"libraries"_ustr), this))
{
    m_xTabCtrl->connect_enter_page(LINK(this, OrganizeDialog, ActivatePageHdl));

    SetCurrentEntry(xDocFrame);

    OUString sPage;
    if (tabId == 0)
        sPage = "modules";
    else if (tabId == 1)
        sPage = "dialogs";
    else
        sPage = "libraries";
    m_xTabCtrl->set_current_page(sPage);
    ActivatePageHdl(sPage);

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

IMPL_LINK(OrganizeDialog, ActivatePageHdl, const OUString&, rPage, void)
{
    if (rPage == "modules")
        m_xModulePage->ActivatePage();
    else if (rPage == "dialogs")
        m_xDialogPage->ActivatePage();
    else if (rPage == "libraries")
        m_xLibPage->ActivatePage();
}

OrganizeDialog::~OrganizeDialog()
{
}

OrganizePage::OrganizePage(weld::Container* pParent, const OUString& rUIFile, const OUString &rName, OrganizeDialog* pDialog)
    : m_pDialog(pDialog)
    , m_xBuilder(Application::CreateBuilder(pParent, rUIFile))
    , m_xContainer(m_xBuilder->weld_container(rName))
{
}

OrganizePage::~OrganizePage()
{
}

class SbTreeListBoxDropTarget : public DropTargetHelper
{
private:
    SbTreeListBox& m_rTreeView;

    virtual sal_Int8 AcceptDrop(const AcceptDropEvent& rEvt) override
    {
        // to enable the autoscroll when we're close to the edges
        weld::TreeView& rWidget = m_rTreeView.get_widget();
        rWidget.get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true);

        weld::TreeView* pSource = rWidget.get_drag_source();
        if (!pSource)
            return DND_ACTION_NONE;

        // tdf#145722 only return a DND_ACTION_MOVE possibility if that
        // is requested as an option
        const bool bCheckForMove = rEvt.mnAction & DND_ACTION_MOVE;

        sal_Int8 nMode = DND_ACTION_NONE;

        std::unique_ptr<weld::TreeIter> xEntry(pSource->make_iterator());
        if (pSource->get_selected(xEntry.get()))
        {
            sal_uInt16 nDepth = pSource->get_iter_depth(*xEntry);
            if (nDepth >= 2)
            {
                nMode = DND_ACTION_COPY;
                if (bCheckForMove)
                {
                    EntryDescriptor aDesc = m_rTreeView.GetEntryDescriptor(xEntry.get());
                    const ScriptDocument& aDocument( aDesc.GetDocument() );
                    const OUString& aLibName( aDesc.GetLibName() );
                    // allow MOVE mode only for libraries, which are not readonly
                    Reference< script::XLibraryContainer2 > xModLibContainer( aDocument.getLibraryContainer( E_SCRIPTS ) );
                    Reference< script::XLibraryContainer2 > xDlgLibContainer( aDocument.getLibraryContainer( E_DIALOGS ) );
                    if ( !( ( xModLibContainer.is() && xModLibContainer->hasByName( aLibName ) && xModLibContainer->isLibraryReadOnly( aLibName ) ) ||
                            ( xDlgLibContainer.is() && xDlgLibContainer->hasByName( aLibName ) && xDlgLibContainer->isLibraryReadOnly( aLibName ) ) ) )
                    {
                        // Only allow copy for localized libraries
                        bool bAllowMove = true;
                        if ( xDlgLibContainer.is() && xDlgLibContainer->hasByName( aLibName ) )
                        {
                            // Get StringResourceManager
                            Reference< container::XNameContainer > xDialogLib( aDocument.getLibrary( E_DIALOGS, aLibName, true ) );
                            Reference< XStringResourceManager > xSourceMgr =
                                LocalizationMgr::getStringResourceFromDialogLibrary( xDialogLib );
                            if( xSourceMgr.is() )
                                bAllowMove = ( xSourceMgr->getLocales().getLength() == 0 );
                        }
                        if( bAllowMove )
                            nMode |= DND_ACTION_MOVE;
                    }
                }
            }
        }
        return nMode;
    }

    virtual sal_Int8 ExecuteDrop(const ExecuteDropEvent& rEvt) override
    {
        weld::TreeView& rWidget = m_rTreeView.get_widget();
        weld::TreeView* pSource = rWidget.get_drag_source();
        if (!pSource)
            return DND_ACTION_NONE;

        std::unique_ptr<weld::TreeIter> xEntry(rWidget.make_iterator());
        bool bEntry = rWidget.get_dest_row_at_pos(rEvt.maPosPixel, xEntry.get(), true);

        // don't drop on a BasicManager (nDepth == 0)
        sal_uInt16 nDepth = bEntry ? m_rTreeView.get_iter_depth(*xEntry) : 0;
        bool bValid = nDepth != 0;
        // don't drop in the same library
        std::unique_ptr<weld::TreeIter> xSelected(pSource->make_iterator());
        bool bSelected = pSource->get_selected(xSelected.get());
        if (!bSelected)
            bValid = false;
        else if (nDepth == 1)
        {
            std::unique_ptr<weld::TreeIter> xSelParent(pSource->make_iterator(xSelected.get()));
            if (pSource->iter_parent(*xSelParent) && pSource->iter_compare(*xEntry, *xSelParent) == 0)
                bValid = false;
        }
        else if (nDepth == 2)
        {
            std::unique_ptr<weld::TreeIter> xParent(pSource->make_iterator(xEntry.get()));
            std::unique_ptr<weld::TreeIter> xSelParent(pSource->make_iterator(xSelected.get()));
            if (pSource->iter_parent(*xParent) && pSource->iter_parent(*xSelParent) && pSource->iter_compare(*xParent, *xSelParent) == 0)
                bValid = false;
        }

        // don't drop on a library, which is not loaded, readonly or password protected
        // or which already has a module/dialog with this name
        if ( bValid && ( nDepth > 0 ) )
        {
            // get source module/dialog name
            EntryDescriptor aSourceDesc = m_rTreeView.GetEntryDescriptor(xSelected.get());
            const OUString& aSourceName = aSourceDesc.GetName();
            EntryType eSourceType = aSourceDesc.GetType();

            // get target shell and target library name
            EntryDescriptor aDestDesc = m_rTreeView.GetEntryDescriptor(xEntry.get());
            ScriptDocument const& rDestDoc = aDestDesc.GetDocument();
            const OUString& aDestLibName = aDestDesc.GetLibName();

            // check if module library is not loaded, readonly or password protected
            Reference< script::XLibraryContainer2 > xModLibContainer( rDestDoc.getLibraryContainer( E_SCRIPTS ) );
            if ( xModLibContainer.is() && xModLibContainer->hasByName( aDestLibName ) )
            {
                if ( !xModLibContainer->isLibraryLoaded( aDestLibName ) )
                    bValid = false;

                if ( xModLibContainer->isLibraryReadOnly( aDestLibName ) )
                    bValid = false;

                Reference< script::XLibraryContainerPassword > xPasswd( xModLibContainer, UNO_QUERY );
                if ( xPasswd.is() && xPasswd->isLibraryPasswordProtected( aDestLibName ) && !xPasswd->isLibraryPasswordVerified( aDestLibName ) )
                    bValid = false;
            }

            // check if dialog library is not loaded or readonly
            Reference< script::XLibraryContainer2 > xDlgLibContainer( rDestDoc.getLibraryContainer( E_DIALOGS ) );
            if ( xDlgLibContainer.is() && xDlgLibContainer->hasByName( aDestLibName ) )
            {
                if ( !xDlgLibContainer->isLibraryLoaded( aDestLibName ) )
                    bValid = false;

                if ( xDlgLibContainer->isLibraryReadOnly( aDestLibName ) )
                    bValid = false;
            }

            // check, if module/dialog with this name is already existing in target library
            if ( ( eSourceType == OBJ_TYPE_MODULE && rDestDoc.hasModule( aDestLibName, aSourceName ) ) ||
                ( eSourceType == OBJ_TYPE_DIALOG && rDestDoc.hasDialog( aDestLibName, aSourceName ) ) )
            {
                bValid = false;
            }
        }

        if (bValid)
            NotifyCopyingMoving(*xEntry, rEvt.mnAction & DND_ACTION_MOVE);

        return DND_ACTION_NONE;
    }

    void NotifyCopyingMoving(const weld::TreeIter& rTarget, bool bMove)
    {
        sal_uInt16 nDepth = m_rTreeView.get_iter_depth(rTarget);
        std::unique_ptr<weld::TreeIter> xNewParent(m_rTreeView.make_iterator(&rTarget));
        int nNewChildPos = 0;
        DBG_ASSERT( nDepth, "Depth?" );
        if ( nDepth >= 2 )
        {
            // Target = module/dialog => put module/dialog under the superordinate Basic
            m_rTreeView.iter_parent(*xNewParent);
            nNewChildPos = m_rTreeView.get_iter_index_in_parent(rTarget) + 1;
        }

        // get target shell and target library name
        EntryDescriptor aDestDesc = m_rTreeView.GetEntryDescriptor(xNewParent.get());
        const ScriptDocument& rDestDoc( aDestDesc.GetDocument() );
        const OUString& aDestLibName( aDestDesc.GetLibName() );

        // get source shell, library name and module/dialog name
        std::unique_ptr<weld::TreeIter> xSelected(m_rTreeView.make_iterator());
        if (!m_rTreeView.get_selected(xSelected.get()))
            return;
        EntryDescriptor aSourceDesc = m_rTreeView.GetEntryDescriptor(xSelected.get());
        const ScriptDocument& rSourceDoc( aSourceDesc.GetDocument() );
        const OUString& aSourceLibName( aSourceDesc.GetLibName() );
        const OUString& aSourceName( aSourceDesc.GetName() );
        EntryType eType = aSourceDesc.GetType();

        // get dispatcher
        SfxDispatcher* pDispatcher = GetDispatcher();

        if ( bMove )    // move
        {
            // remove source module/dialog window
            if ( rSourceDoc != rDestDoc || aSourceLibName != aDestLibName )
            {
                if( pDispatcher )
                {
                    SbxItem aSbxItem( SID_BASICIDE_ARG_SBX, rSourceDoc, aSourceLibName, aSourceName, SbTreeListBox::ConvertType(eType) );
                    pDispatcher->ExecuteList( SID_BASICIDE_SBXDELETED,
                          SfxCallMode::SYNCHRON, { &aSbxItem });
                }
            }

            try
            {
                if ( eType == OBJ_TYPE_MODULE ) // module
                {
                    // get module
                    OUString aModule;
                    if ( rSourceDoc.getModule( aSourceLibName, aSourceName, aModule ) )
                    {
                        // remove module from source library
                        if ( rSourceDoc.removeModule( aSourceLibName, aSourceName ) )
                        {
                            MarkDocumentModified( rSourceDoc );

                            // insert module into target library
                            if ( rDestDoc.insertModule( aDestLibName, aSourceName, aModule ) )
                                MarkDocumentModified( rDestDoc );
                        }
                    }
                }
                else if ( eType == OBJ_TYPE_DIALOG )    // dialog
                {
                    // get dialog
                    Reference< io::XInputStreamProvider > xISP;
                    if ( rSourceDoc.getDialog( aSourceLibName, aSourceName, xISP ) )
                    {
                        Shell::CopyDialogResources( xISP, rSourceDoc,
                            aSourceLibName, rDestDoc, aDestLibName, aSourceName );

                        // remove dialog from source library
                        if (RemoveDialog(rSourceDoc, aSourceLibName, aSourceName))
                        {
                            MarkDocumentModified(rSourceDoc);

                            // insert dialog into target library
                            if ( rDestDoc.insertDialog( aDestLibName, aSourceName, xISP ) )
                                MarkDocumentModified(rDestDoc);
                        }
                    }
                }
            }
            catch (const uno::Exception& )
            {
                DBG_UNHANDLED_EXCEPTION("basctl.basicide");
            }
        }
        else    // copy
        {
            try
            {
                if ( eType == OBJ_TYPE_MODULE ) // module
                {
                    // get module
                    OUString aModule;
                    if ( rSourceDoc.getModule( aSourceLibName, aSourceName, aModule ) )
                    {
                        // insert module into target library
                        if ( rDestDoc.insertModule( aDestLibName, aSourceName, aModule ) )
                            MarkDocumentModified( rDestDoc );
                    }
                }
                else if ( eType == OBJ_TYPE_DIALOG )    // dialog
                {
                    // get dialog
                    Reference< io::XInputStreamProvider > xISP;
                    if ( rSourceDoc.getDialog( aSourceLibName, aSourceName, xISP ) )
                    {
                        Shell::CopyDialogResources( xISP, rSourceDoc,
                            aSourceLibName, rDestDoc, aDestLibName, aSourceName );

                        // insert dialog into target library
                        if ( rDestDoc.insertDialog( aDestLibName, aSourceName, xISP ) )
                            MarkDocumentModified( rDestDoc );
                    }
                }
            }
            catch ( const Exception& )
            {
                DBG_UNHANDLED_EXCEPTION("basctl.basicide");
            }
        }

        OUString sText(m_rTreeView.get_text(*xSelected));
        OUString sId(m_rTreeView.get_id(*xSelected));
        /// if copying then clone the userdata
        if (Entry* pEntry = bMove ? nullptr : weld::fromId<Entry*>(sId))
        {
            assert(pEntry->GetType() != OBJ_TYPE_DOCUMENT);
            std::unique_ptr<Entry> xNewUserData(std::make_unique<Entry>(*pEntry));
            sId = weld::toId(xNewUserData.release());
        }
        std::unique_ptr<weld::TreeIter> xRet(m_rTreeView.make_iterator());
        m_rTreeView.get_widget().insert(xNewParent.get(), nNewChildPos, &sText, &sId, nullptr, nullptr, false, xRet.get());
        if (eType == OBJ_TYPE_MODULE)
            m_rTreeView.get_widget().set_image(*xRet, RID_BMP_MODULE);
        else if (eType == OBJ_TYPE_DIALOG)
            m_rTreeView.get_widget().set_image(*xRet, RID_BMP_DIALOG);
        if (!m_rTreeView.get_row_expanded(*xNewParent))
            m_rTreeView.expand_row(*xNewParent);
        m_rTreeView.select(*xRet);

        if (bMove)
            m_rTreeView.remove(*xSelected);

        // create target module/dialog window
        if ( rSourceDoc != rDestDoc || aSourceLibName != aDestLibName )
        {
            if( pDispatcher )
            {
                SbxItem aSbxItem( SID_BASICIDE_ARG_SBX, rDestDoc, aDestLibName, aSourceName, SbTreeListBox::ConvertType(eType) );
                pDispatcher->ExecuteList( SID_BASICIDE_SBXINSERTED,
                                      SfxCallMode::SYNCHRON, { &aSbxItem });
            }
        }
    }

public:
    SbTreeListBoxDropTarget(SbTreeListBox& rTreeView)
        : DropTargetHelper(rTreeView.get_widget().get_drop_target())
        , m_rTreeView(rTreeView)
    {
    }
};

// ObjectPage
ObjectPage::ObjectPage(weld::Container* pParent, const OUString &rName, BrowseMode nMode, OrganizeDialog* pDialog)
    : OrganizePage(pParent, "modules/BasicIDE/ui/" + rName.toAsciiLowerCase() + ".ui",
        rName, pDialog)
    , m_xBasicBox(new SbTreeListBox(m_xBuilder->weld_tree_view(u"library"_ustr), pDialog->getDialog()))
    , m_xEditButton(m_xBuilder->weld_button(u"edit"_ustr))
    , m_xNewModButton(m_xBuilder->weld_button(u"newmodule"_ustr))
    , m_xNewDlgButton(m_xBuilder->weld_button(u"newdialog"_ustr))
    , m_xDelButton(m_xBuilder->weld_button(u"delete"_ustr))
{
    Size aSize(m_xBasicBox->get_approximate_digit_width() * 40,
               m_xBasicBox->get_height_rows(14));
    m_xBasicBox->set_size_request(aSize.Width(), aSize.Height());

    // tdf#93476 The dialogs should be listed alphabetically
    m_xBasicBox->make_sorted();

    m_xEditButton->connect_clicked( LINK( this, ObjectPage, ButtonHdl ) );
    m_xDelButton->connect_clicked( LINK( this, ObjectPage, ButtonHdl ) );
    m_xBasicBox->connect_changed( LINK( this, ObjectPage, BasicBoxHighlightHdl ) );

    if( nMode & BrowseMode::Modules )
    {
        m_xNewModButton->connect_clicked( LINK( this, ObjectPage, ButtonHdl ) );
        m_xNewDlgButton->hide();
    }
    else if ( nMode & BrowseMode::Dialogs )
    {
        m_xNewDlgButton->connect_clicked( LINK( this, ObjectPage, ButtonHdl ) );
        m_xNewModButton->hide();
    }

    m_xDropTarget.reset(new SbTreeListBoxDropTarget(*m_xBasicBox));
    // tdf#145722 explicitly claim COPY and MOVE are options
    rtl::Reference<TransferDataContainer> xHelper(new TransferDataContainer);
    m_xBasicBox->get_widget().enable_drag_source(xHelper, DND_ACTION_COPYMOVE);

    m_xBasicBox->connect_editing(LINK(this, ObjectPage, EditingEntryHdl),
                                 LINK(this, ObjectPage, EditedEntryHdl));

    m_xBasicBox->SetMode( nMode );
    m_xBasicBox->ScanAllEntries();

    m_xEditButton->grab_focus();
    CheckButtons();
}

ObjectPage::~ObjectPage()
{
}

void ObjectPage::ActivatePage()
{
    m_xBasicBox->UpdateEntries();
    CheckButtons();
}

void ObjectPage::CheckButtons()
{
    // enable/disable edit button
    std::unique_ptr<weld::TreeIter> xCurEntry(m_xBasicBox->make_iterator());
    if (!m_xBasicBox->get_cursor(xCurEntry.get()))
        xCurEntry.reset();
    EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(xCurEntry.get());
    const ScriptDocument& aDocument( aDesc.GetDocument() );
    const OUString& aLibName( aDesc.GetLibName() );
    const OUString& aLibSubName( aDesc.GetLibSubName() );
    bool bVBAEnabled = aDocument.isInVBAMode();
    BrowseMode nMode = m_xBasicBox->GetMode();

    sal_uInt16 nDepth = xCurEntry ? m_xBasicBox->get_iter_depth(*xCurEntry) : 0;
    if ( nDepth >= 2 )
    {
        if( bVBAEnabled && ( nMode & BrowseMode::Modules ) && ( nDepth == 2 ) )
            m_xEditButton->set_sensitive(false);
        else
            m_xEditButton->set_sensitive(true);
    }
    else
        m_xEditButton->set_sensitive(false);

    // enable/disable new module/dialog buttons
    LibraryLocation eLocation( aDesc.GetLocation() );
    bool bReadOnly = false;
    if ( nDepth > 0 )
    {
        Reference< script::XLibraryContainer2 > xModLibContainer( aDocument.getLibraryContainer( E_SCRIPTS ) );
        Reference< script::XLibraryContainer2 > xDlgLibContainer( aDocument.getLibraryContainer( E_DIALOGS ) );
        if ( ( xModLibContainer.is() && xModLibContainer->hasByName( aLibName ) && xModLibContainer->isLibraryReadOnly( aLibName ) ) ||
             ( xDlgLibContainer.is() && xDlgLibContainer->hasByName( aLibName ) && xDlgLibContainer->isLibraryReadOnly( aLibName ) ) )
        {
            bReadOnly = true;
        }
    }
    if ( bReadOnly || eLocation == LIBRARY_LOCATION_SHARE )
    {
        m_xNewModButton->set_sensitive(false);
        m_xNewDlgButton->set_sensitive(false);
    }
    else
    {
        m_xNewModButton->set_sensitive(true);
        m_xNewDlgButton->set_sensitive(true);
    }

    // enable/disable delete button
    if ( nDepth >= 2 && !bReadOnly && eLocation != LIBRARY_LOCATION_SHARE )
    {
        if( bVBAEnabled && ( nMode & BrowseMode::Modules ) && ( ( nDepth == 2 ) || aLibSubName == IDEResId(RID_STR_DOCUMENT_OBJECTS) ) )
            m_xDelButton->set_sensitive(false);
        else
            m_xDelButton->set_sensitive(true);
    }
    else
        m_xDelButton->set_sensitive(false);
}

IMPL_LINK_NOARG(ObjectPage, BasicBoxHighlightHdl, weld::TreeView&, void)
{
    CheckButtons();
}

IMPL_LINK(ObjectPage, ButtonHdl, weld::Button&, rButton, void)
{
    if (&rButton == m_xEditButton.get())
    {
        SfxAllItemSet aArgs( SfxGetpApp()->GetPool() );
        SfxRequest aRequest( SID_BASICIDE_APPEAR, SfxCallMode::SYNCHRON, aArgs );
        SfxGetpApp()->ExecuteSlot( aRequest );

        SfxDispatcher* pDispatcher = GetDispatcher();

        std::unique_ptr<weld::TreeIter> xCurEntry(m_xBasicBox->make_iterator());
        if (!m_xBasicBox->get_cursor(xCurEntry.get()))
            return;
        if (m_xBasicBox->get_iter_depth(*xCurEntry) >= 2)
        {
            EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(xCurEntry.get());
            if ( pDispatcher )
            {
                OUString aModName( aDesc.GetName() );
                // extract the module name from the string like "Sheet1 (Example1)"
                if( aDesc.GetLibSubName() == IDEResId(RID_STR_DOCUMENT_OBJECTS) )
                {
                    aModName = aModName.getToken( 0, ' ' );
                }
                SbxItem aSbxItem( SID_BASICIDE_ARG_SBX, aDesc.GetDocument(), aDesc.GetLibName(),
                                  aModName, SbTreeListBox::ConvertType( aDesc.GetType() ) );
                pDispatcher->ExecuteList(SID_BASICIDE_SHOWSBX,
                        SfxCallMode::SYNCHRON, { &aSbxItem });
            }
        }
        else    // only Lib selected
        {
            DBG_ASSERT( m_xBasicBox->get_iter_depth(*xCurEntry) == 1, "No LibEntry?!" );
            ScriptDocument aDocument( ScriptDocument::getApplicationScriptDocument() );
            std::unique_ptr<weld::TreeIter> xParentEntry(m_xBasicBox->make_iterator(xCurEntry.get()));
            if (m_xBasicBox->iter_parent(*xParentEntry))
            {
                DocumentEntry* pDocumentEntry = weld::fromId<DocumentEntry*>(m_xBasicBox->get_id(*xParentEntry));
                if (pDocumentEntry)
                    aDocument = pDocumentEntry->GetDocument();
            }
            SfxUnoAnyItem aDocItem( SID_BASICIDE_ARG_DOCUMENT_MODEL, Any( aDocument.getDocumentOrNull() ) );
            OUString aLibName(m_xBasicBox->get_text(*xCurEntry));
            SfxStringItem aLibNameItem( SID_BASICIDE_ARG_LIBNAME, aLibName );
            if ( pDispatcher )
            {
                pDispatcher->ExecuteList(SID_BASICIDE_LIBSELECTED,
                    SfxCallMode::ASYNCHRON, { &aDocItem, &aLibNameItem });
            }
        }
        EndTabDialog();
    }
    else if (&rButton == m_xNewModButton.get())
        NewModule();
    else if (&rButton == m_xNewDlgButton.get())
        NewDialog();
    else if (&rButton == m_xDelButton.get())
        DeleteCurrent();
}

bool ObjectPage::GetSelection( ScriptDocument& rDocument, OUString& rLibName )
{
    bool bRet = false;

    std::unique_ptr<weld::TreeIter> xCurEntry(m_xBasicBox->make_iterator());
    if (!m_xBasicBox->get_cursor(xCurEntry.get()))
        xCurEntry.reset();
    EntryDescriptor aDesc = m_xBasicBox->GetEntryDescriptor(xCurEntry.get());
    rDocument = aDesc.GetDocument();
    rLibName = aDesc.GetLibName();
    if ( rLibName.isEmpty() )
        rLibName = "Standard" ;

    DBG_ASSERT( rDocument.isAlive(), "ObjectPage::GetSelection: no or dead ScriptDocument in the selection!" );
    if ( !rDocument.isAlive() )
        return false;

    // check if the module library is loaded
    bool bOK = true;
    OUString aLibName( rLibName );
    Reference< script::XLibraryContainer > xModLibContainer( rDocument.getLibraryContainer( E_SCRIPTS  ) );
    if ( xModLibContainer.is() && xModLibContainer->hasByName( aLibName ) && !xModLibContainer->isLibraryLoaded( aLibName ) )
    {
        // check password
        Reference< script::XLibraryContainerPassword > xPasswd( xModLibContainer, UNO_QUERY );
        if ( xPasswd.is() && xPasswd->isLibraryPasswordProtected( aLibName ) && !xPasswd->isLibraryPasswordVerified( aLibName ) )
        {
            OUString aPassword;
            bOK = QueryPassword(m_pDialog->getDialog(), xModLibContainer, rLibName, aPassword);
        }

        // load library
        if ( bOK )
            xModLibContainer->loadLibrary( aLibName );
    }

    // check if the dialog library is loaded
    Reference< script::XLibraryContainer > xDlgLibContainer( rDocument.getLibraryContainer( E_DIALOGS ) );
    if ( xDlgLibContainer.is() && xDlgLibContainer->hasByName( aLibName ) && !xDlgLibContainer->isLibraryLoaded( aLibName ) )
    {
        // load library
        if ( bOK )
            xDlgLibContainer->loadLibrary( aLibName );
    }

    if ( bOK )
        bRet = true;

    return bRet;
}

void ObjectPage::NewModule()
{
    ScriptDocument aDocument( ScriptDocument::getApplicationScriptDocument() );
    OUString aLibName;

    if ( GetSelection( aDocument, aLibName ) )
    {
        createModImpl(m_pDialog->getDialog(), aDocument,
                      *m_xBasicBox, aLibName, OUString(), true);
    }
}

void ObjectPage::NewDialog()
{
    ScriptDocument aDocument( ScriptDocument::getApplicationScriptDocument() );
    OUString aLibName;

    if ( !GetSelection( aDocument, aLibName ) )
        return;

    aDocument.getOrCreateLibrary( E_DIALOGS, aLibName );

    NewObjectDialog aNewDlg(m_pDialog->getDialog(), ObjectMode::Dialog, true);
    aNewDlg.SetObjectName(aDocument.createObjectName(E_DIALOGS, aLibName));

    if (aNewDlg.run() == RET_CANCEL)
        return;

    OUString aDlgName = aNewDlg.GetObjectName();
    if (aDlgName.isEmpty())
        aDlgName = aDocument.createObjectName( E_DIALOGS, aLibName);

    if ( aDocument.hasDialog( aLibName, aDlgName ) )
    {
        std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog(m_pDialog->getDialog(),
                                                    VclMessageType::Warning, VclButtonsType::Ok, IDEResId(RID_STR_SBXNAMEALLREADYUSED2)));
        xError->run();
    }
    else
    {
        Reference< io::XInputStreamProvider > xISP;
        if ( !aDocument.createDialog( aLibName, aDlgName, xISP ) )
            return;

        SbxItem aSbxItem( SID_BASICIDE_ARG_SBX, aDocument, aLibName, aDlgName, SBX_TYPE_DIALOG );
        if (SfxDispatcher* pDispatcher = GetDispatcher())
        {
            pDispatcher->ExecuteList( SID_BASICIDE_SBXINSERTED,
                SfxCallMode::SYNCHRON, { &aSbxItem });
        }
        LibraryLocation eLocation = aDocument.getLibraryLocation( aLibName );
        std::unique_ptr<weld::TreeIter> xIter(m_xBasicBox->make_iterator());
        bool bRootEntry = m_xBasicBox->FindRootEntry(aDocument, eLocation, *xIter);
        if (bRootEntry)
        {
            if (!m_xBasicBox->get_row_expanded(*xIter))
                m_xBasicBox->expand_row(*xIter);
            bool bLibEntry = m_xBasicBox->FindEntry(aLibName, OBJ_TYPE_LIBRARY, *xIter);
            DBG_ASSERT( bLibEntry, "LibEntry not found!" );
            if (bLibEntry)
            {
                if (!m_xBasicBox->get_row_expanded(*xIter))
                    m_xBasicBox->expand_row(*xIter);
                std::unique_ptr<weld::TreeIter> xSubRootEntry(m_xBasicBox->make_iterator(xIter.get()));
                bool bDlgEntry = m_xBasicBox->FindEntry(aDlgName, OBJ_TYPE_DIALOG, *xIter);
                if (!bDlgEntry)
                {
                    m_xBasicBox->AddEntry(aDlgName, RID_BMP_DIALOG, xSubRootEntry.get(), false,
                                       std::make_unique<Entry>(OBJ_TYPE_DIALOG), xIter.get());
                    assert(xIter && "Insert entry failed!");
                }
                m_xBasicBox->set_cursor(*xIter);
                m_xBasicBox->select(*xIter);
            }
        }
    }
}

void ObjectPage::DeleteCurrent()
{
    std::unique_ptr<weld::TreeIter> xCurEntry(m_xBasicBox->make_iterator());
    if (!m_xBasicBox->get_cursor(xCurEntry.get()))
        xCurEntry.reset();
    DBG_ASSERT( xCurEntry, "No current entry!" );
    if (!xCurEntry)
        return;
    EntryDescriptor aDesc( m_xBasicBox->GetEntryDescriptor( xCurEntry.get() ) );
    const ScriptDocument& aDocument( aDesc.GetDocument() );
    DBG_ASSERT( aDocument.isAlive(), "ObjectPage::DeleteCurrent: no document!" );
    if ( !aDocument.isAlive() )
        return;
    const OUString& aLibName( aDesc.GetLibName() );
    const OUString& aName( aDesc.GetName() );
    EntryType eType = aDesc.GetType();

    if ( !(( eType == OBJ_TYPE_MODULE && QueryDelModule(aName, m_pDialog->getDialog()) ) ||
         ( eType == OBJ_TYPE_DIALOG && QueryDelDialog(aName, m_pDialog->getDialog()) )) )
        return;

    m_xBasicBox->remove(*xCurEntry);
    if (m_xBasicBox->get_cursor(xCurEntry.get()))
        m_xBasicBox->select(*xCurEntry);
    if (SfxDispatcher* pDispatcher = GetDispatcher())
    {
        SbxItem aSbxItem( SID_BASICIDE_ARG_SBX, aDocument, aLibName, aName, SbTreeListBox::ConvertType( eType ) );
        pDispatcher->ExecuteList( SID_BASICIDE_SBXDELETED,
                              SfxCallMode::SYNCHRON, { &aSbxItem });
    }

    try
    {
        bool bSuccess = false;
        if ( eType == OBJ_TYPE_MODULE )
            bSuccess = aDocument.removeModule( aLibName, aName );
        else if ( eType == OBJ_TYPE_DIALOG )
            bSuccess = RemoveDialog( aDocument, aLibName, aName );

        if ( bSuccess )
            MarkDocumentModified( aDocument );
    }
    catch (const container::NoSuchElementException& )
    {
        DBG_UNHANDLED_EXCEPTION("basctl.basicide");
    }
}

void ObjectPage::EndTabDialog()
{
    m_pDialog->response(RET_OK);
}

LibDialog::LibDialog(weld::Window* pParent)
    : GenericDialogController(pParent, u"modules/BasicIDE/ui/importlibdialog.ui"_ustr, u"ImportLibDialog"_ustr)
    , m_xStorageFrame(m_xBuilder->weld_frame(u"storageframe"_ustr))
    , m_xLibBox(m_xBuilder->weld_tree_view(u"entries"_ustr))
    , m_xReferenceBox(m_xBuilder->weld_check_button(u"ref"_ustr))
    , m_xReplaceBox(m_xBuilder->weld_check_button(u"replace"_ustr))
{
    m_xLibBox->set_size_request(m_xLibBox->get_approximate_digit_width() * 28,
                                m_xLibBox->get_height_rows(8));
    m_xLibBox->enable_toggle_buttons(weld::ColumnToggleType::Check);
    // tdf#93476 The libraries should be listed alphabetically
    m_xLibBox->make_sorted();
}

LibDialog::~LibDialog()
{
}

void LibDialog::SetStorageName( std::u16string_view rName )
{
    OUString aName = IDEResId(RID_STR_FILENAME) + rName;
    m_xStorageFrame->set_label(aName);
}

// Helper function
SbModule* createModImpl(weld::Window* pWin, const ScriptDocument& rDocument,
    SbTreeListBox& rBasicBox, const OUString& rLibName, const OUString& _aModName, bool bMain )
{
    OSL_ENSURE( rDocument.isAlive(), "createModImpl: invalid document!" );
    if ( !rDocument.isAlive() )
        return nullptr;

    SbModule* pModule = nullptr;

    OUString aLibName( rLibName );
    if ( aLibName.isEmpty() )
        aLibName = "Standard" ;
    rDocument.getOrCreateLibrary( E_SCRIPTS, aLibName );
    OUString aModName = _aModName;
    if ( aModName.isEmpty() )
        aModName = rDocument.createObjectName( E_SCRIPTS, aLibName );

    NewObjectDialog aNewDlg(pWin, ObjectMode::Module, true);
    aNewDlg.SetObjectName(aModName);

    if (aNewDlg.run() != RET_CANCEL)
    {
        if (!aNewDlg.GetObjectName().isEmpty())
            aModName = aNewDlg.GetObjectName();

        try
        {
            OUString sModuleCode;
            // the module has existed
            if( rDocument.hasModule( aLibName, aModName ) )
                return nullptr;
            if (!rDocument.createModule(aLibName, aModName, bMain, sModuleCode))
                return nullptr;
            BasicManager* pBasMgr = rDocument.getBasicManager();
            StarBASIC* pBasic = pBasMgr? pBasMgr->GetLib( aLibName ) : nullptr;
            if ( pBasic )
                pModule = pBasic->FindModule( aModName );
            SbxItem aSbxItem( SID_BASICIDE_ARG_SBX, rDocument, aLibName, aModName, SBX_TYPE_MODULE );
            if (SfxDispatcher* pDispatcher = GetDispatcher())
            {
                pDispatcher->ExecuteList( SID_BASICIDE_SBXINSERTED,
                      SfxCallMode::SYNCHRON, { &aSbxItem });
            }
            LibraryLocation eLocation = rDocument.getLibraryLocation( aLibName );
            std::unique_ptr<weld::TreeIter> xIter(rBasicBox.make_iterator());
            bool bRootEntry = rBasicBox.FindRootEntry(rDocument, eLocation, *xIter);
            if (bRootEntry)
            {
                if (!rBasicBox.get_row_expanded(*xIter))
                    rBasicBox.expand_row(*xIter);
                bool bLibEntry = rBasicBox.FindEntry(aLibName, OBJ_TYPE_LIBRARY, *xIter);
                DBG_ASSERT( bLibEntry, "LibEntry not found!" );
                if (bLibEntry)
                {
                    if (!rBasicBox.get_row_expanded(*xIter))
                        rBasicBox.expand_row(*xIter);
                    std::unique_ptr<weld::TreeIter> xSubRootEntry(rBasicBox.make_iterator(xIter.get()));
                    if (pBasic && rDocument.isInVBAMode())
                    {
                        // add the new module in the "Modules" entry
                        std::unique_ptr<weld::TreeIter> xLibSubEntry(rBasicBox.make_iterator(xIter.get()));
                        bool bLibSubEntry = rBasicBox.FindEntry(IDEResId(RID_STR_NORMAL_MODULES) , OBJ_TYPE_NORMAL_MODULES, *xLibSubEntry);
                        if (bLibSubEntry)
                        {
                            if (!rBasicBox.get_row_expanded(*xLibSubEntry))
                                rBasicBox.expand_row(*xLibSubEntry);
                            rBasicBox.copy_iterator(*xLibSubEntry, *xSubRootEntry);
                        }
                    }

                    std::unique_ptr<weld::TreeIter> xEntry(rBasicBox.make_iterator(xSubRootEntry.get()));
                    bool bEntry = rBasicBox.FindEntry(aModName, OBJ_TYPE_MODULE, *xEntry);
                    if (!bEntry)
                    {
                        rBasicBox.AddEntry(aModName, RID_BMP_MODULE, xSubRootEntry.get(), false,
                                           std::make_unique<Entry>(OBJ_TYPE_MODULE), xEntry.get());
                    }
                    rBasicBox.set_cursor(*xEntry);
                    rBasicBox.select(*xEntry);
                }
            }
        }
        catch (const container::ElementExistException& )
        {
            std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog(pWin,
                                                        VclMessageType::Warning, VclButtonsType::Ok, IDEResId(RID_STR_SBXNAMEALLREADYUSED2)));
            xError->run();
        }
        catch (const container::NoSuchElementException& )
        {
            DBG_UNHANDLED_EXCEPTION("basctl.basicide");
        }
    }
    return pModule;
}


} // namespace basctl

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