/* -*- 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 <hintids.hxx>
#include <svl/macitem.hxx>
#include <sfx2/frame.hxx>
#include <svl/urihelper.hxx>
#include <svl/eitem.hxx>
#include <svl/stritem.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/fcontnr.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/linkmgr.hxx>
#include <fmtinfmt.hxx>
#include <frmatr.hxx>
#include <swtypes.hxx>
#include <wrtsh.hxx>
#include <docsh.hxx>
#include <fldbas.hxx>
#include <expfld.hxx>
#include <ddefld.hxx>
#include <docufld.hxx>
#include <reffld.hxx>
#include <swundo.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <viewopt.hxx>
#include <frmfmt.hxx>
#include <fmtfld.hxx>
#include <swtable.hxx>
#include <mdiexp.hxx>
#include <view.hxx>
#include <swevent.hxx>
#include <poolfmt.hxx>
#include <section.hxx>
#include <navicont.hxx>
#include <navipi.hxx>
#include <txtinet.hxx>
#include <cmdid.h>
#include <swabstdlg.hxx>
#include <SwRewriter.hxx>
 
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
 
#include <memory>
 
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <comphelper/lok.hxx>
#include <sfx2/event.hxx>
#include <sal/log.hxx>
 
void SwWrtShell::Insert(SwField const &rField)
{
    ResetCursorStack();
    if(!CanInsert())
        return;
    StartAllAction();
 
    SwRewriter aRewriter;
    aRewriter.AddRule(UndoArg1, rField.GetDescription());
 
    StartUndo(SwUndoId::INSERT, &aRewriter);
 
    bool bDeleted = false;
    std::unique_ptr<SwPaM> pAnnotationTextRange;
    if ( HasSelection() )
    {
        if ( rField.GetTyp()->Which() == SwFieldIds::Postit )
        {
            // for annotation fields:
            // - keep the current selection in order to create a corresponding annotation mark
            // - collapse cursor to its end
            if ( IsTableMode() )
            {
                GetTableCrs()->Normalize( false );
                const SwPosition rStartPos( *(GetTableCrs()->GetMark()->nNode.GetNode().GetContentNode()), 0 );
                KillPams();
                if ( !IsEndOfPara() )
                {
                    EndPara();
                }
                const SwPosition rEndPos( *GetCurrentShellCursor().GetPoint() );
                pAnnotationTextRange.reset(new SwPaM( rStartPos, rEndPos ));
            }
            else
            {
                NormalizePam( false );
                const SwPaM& rCurrPaM = GetCurrentShellCursor();
                pAnnotationTextRange.reset(new SwPaM( *rCurrPaM.GetPoint(), *rCurrPaM.GetMark() ));
                ClearMark();
            }
        }
        else
        {
            bDeleted = DelRight();
        }
    }
 
    SwEditShell::Insert2(rField, bDeleted);
 
    if ( pAnnotationTextRange )
    {
        if ( GetDoc() != nullptr )
        {
            IDocumentMarkAccess* pMarksAccess = GetDoc()->getIDocumentMarkAccess();
            pMarksAccess->makeAnnotationMark( *pAnnotationTextRange, OUString() );
        }
        pAnnotationTextRange.reset();
    }
 
    EndUndo();
    EndAllAction();
}
 
// Start the field update
 
void SwWrtShell::UpdateInputFields( SwInputFieldList* pLst )
{
    // Go through the list of fields and updating
    SwInputFieldList* pTmp = pLst;
    if( !pTmp )
        pTmp = new SwInputFieldList( this );
 
    const size_t nCnt = pTmp->Count();
    if(nCnt)
    {
        pTmp->PushCursor();
 
        bool bCancel = false;
 
        size_t nIndex = 0;
        FieldDialogPressedButton ePressedButton = BTN_NONE;
 
        SwField* pField = GetCurField();
        if (pField)
        {
            for (size_t i = 0; i < nCnt; i++)
            {
                if (pField == pTmp->GetField(i))
                {
                    nIndex = i;
                    break;
                }
            }
        }
 
        while (!bCancel)
        {
            bool bPrev = nIndex > 0;
            bool bNext = nIndex < nCnt - 1;
            pTmp->GotoFieldPos(nIndex);
            pField = pTmp->GetField(nIndex);
            if (pField->GetTyp()->Which() == SwFieldIds::Dropdown)
            {
                bCancel = StartDropDownFieldDlg(pField, bPrev, bNext, GetView().GetFrameWeld(), &ePressedButton);
            }
            else
                bCancel = StartInputFieldDlg(pField, bPrev, bNext, GetView().GetFrameWeld(), &ePressedButton);
 
            if (!bCancel)
            {
                // Otherwise update error at multi-selection:
                pTmp->GetField(nIndex)->GetTyp()->UpdateFields();
 
                if (ePressedButton == BTN_PREV && nIndex > 0)
                    nIndex--;
                else if (ePressedButton == BTN_NEXT && nIndex < nCnt - 1)
                    nIndex++;
                else
                    bCancel = true;
            }
        }
 
        pTmp->PopCursor();
    }
 
    if( !pLst )
        delete pTmp;
}
 
// Listener class: will close InputField dialog if input field(s)
// is(are) deleted (for instance, by an extension) after the dialog shows up.
// Otherwise, the for loop in SwWrtShell::UpdateInputFields will crash when doing:
//         'pTmp->GetField( i )->GetTyp()->UpdateFields();'
// on a deleted field.
class FieldDeletionModify : public SwModify
{
    public:
        FieldDeletionModify(AbstractFieldInputDlg* pInputFieldDlg, SwField* pField)
            : mpInputFieldDlg(pInputFieldDlg)
            , mpFormatField(nullptr)
        {
            SwInputField *const pInputField(dynamic_cast<SwInputField*>(pField));
            SwSetExpField *const pSetExpField(dynamic_cast<SwSetExpField*>(pField));
 
            if (pInputField && pInputField->GetFormatField())
            {
                mpFormatField = pInputField->GetFormatField();
            }
            else if (pSetExpField && pSetExpField->GetFormatField())
            {
                mpFormatField = pSetExpField->GetFormatField();
            }
 
            // Register for possible field deletion while dialog is open
            if (mpFormatField)
                mpFormatField->Add(this);
        }
 
        virtual ~FieldDeletionModify() override
        {
            // Dialog closed, remove modification listener
            EndListeningAll();
        }
 
        void Modify( const SfxPoolItem* pOld, const SfxPoolItem *) override
        {
            // Input field has been deleted: better to close the dialog
            if (pOld)
            {
                switch (pOld->Which())
                {
                case RES_REMOVE_UNO_OBJECT:
                case RES_OBJECTDYING:
                    mpFormatField = nullptr;
                    mpInputFieldDlg->EndDialog(RET_CANCEL);
                    break;
                }
            }
        }
    private:
        VclPtr<AbstractFieldInputDlg> mpInputFieldDlg;
        SwFormatField* mpFormatField;
};
 
// Start input dialog for a specific field
bool SwWrtShell::StartInputFieldDlg(SwField* pField, bool bPrevButton, bool bNextButton,
                                    weld::Window* pParentWin, SwWrtShell::FieldDialogPressedButton* pPressedButton)
{
 
    SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create();
    ScopedVclPtr<AbstractFieldInputDlg> pDlg(pFact->CreateFieldInputDlg(pParentWin, *this, pField, bPrevButton, bNextButton));
 
    bool bRet;
 
    {
        FieldDeletionModify aModify(pDlg.get(), pField);
        bRet = RET_CANCEL == pDlg->Execute();
    }
 
    if (pPressedButton)
    {
        if (pDlg->PrevButtonPressed())
            *pPressedButton = BTN_PREV;
        else if (pDlg->NextButtonPressed())
            *pPressedButton = BTN_NEXT;
    }
 
    pDlg.disposeAndClear();
    GetWin()->Update();
    return bRet;
}
 
bool SwWrtShell::StartDropDownFieldDlg(SwField* pField, bool bPrevButton, bool bNextButton,
                                       weld::Window* pParentWin, SwWrtShell::FieldDialogPressedButton* pPressedButton)
{
    SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create();
    ScopedVclPtr<AbstractDropDownFieldDialog> pDlg(pFact->CreateDropDownFieldDialog(pParentWin, *this, pField, bPrevButton, bNextButton));
    const short nRet = pDlg->Execute();
 
    if (pPressedButton)
    {
        if (pDlg->PrevButtonPressed())
            *pPressedButton = BTN_PREV;
        else if (pDlg->NextButtonPressed())
            *pPressedButton = BTN_NEXT;
    }
 
    pDlg.disposeAndClear();
    bool bRet = RET_CANCEL == nRet;
    GetWin()->Update();
    if(RET_YES == nRet)
    {
        GetView().GetViewFrame()->GetDispatcher()->Execute(FN_EDIT_FIELD, SfxCallMode::SYNCHRON);
    }
    return bRet;
}
 
// Insert directory - remove selection
 
void SwWrtShell::InsertTableOf(const SwTOXBase& rTOX, const SfxItemSet* pSet)
{
    if(!CanInsert())
        return;
 
    if(HasSelection())
        DelRight();
 
    SwEditShell::InsertTableOf(rTOX, pSet);
}
 
// Update directory - remove selection
 
void SwWrtShell::UpdateTableOf(const SwTOXBase& rTOX, const SfxItemSet* pSet)
{
    if(CanInsert())
    {
        SwEditShell::UpdateTableOf(rTOX, pSet);
 
        if (pSet == nullptr)
        {
            SwDoc *const pDoc_ = GetDoc();
            if (pDoc_)
            {
                pDoc_->GetIDocumentUndoRedo().DelAllUndoObj();
            }
        }
    }
}
 
// handler for click on the field given as parameter.
// the cursor is positioned on the field.
 
void SwWrtShell::ClickToField( const SwField& rField )
{
    // cross reference field must not be selected because it moves the cursor
    if (SwFieldIds::GetRef != rField.GetTyp()->Which())
    {
        StartAllAction();
        Right( CRSR_SKIP_CHARS, true, 1, false ); // Select the field.
        NormalizePam();
        EndAllAction();
    }
 
    m_bIsInClickToEdit = true;
    switch( rField.GetTyp()->Which() )
    {
    case SwFieldIds::JumpEdit:
        {
            sal_uInt16 nSlotId = 0;
            switch( rField.GetFormat() )
            {
            case JE_FMT_TABLE:
                nSlotId = FN_INSERT_TABLE;
                break;
 
            case JE_FMT_FRAME:
                nSlotId = FN_INSERT_FRAME;
                break;
 
            case JE_FMT_GRAPHIC:    nSlotId = SID_INSERT_GRAPHIC;       break;
            case JE_FMT_OLE:        nSlotId = SID_INSERT_OBJECT;        break;
 
            }
 
            if( nSlotId )
            {
                StartUndo( SwUndoId::START );
                //#97295# immediately select the right shell
                GetView().StopShellTimer();
                GetView().GetViewFrame()->GetDispatcher()->Execute( nSlotId,
                            SfxCallMode::SYNCHRON|SfxCallMode::RECORD );
                EndUndo( SwUndoId::END );
            }
        }
        break;
 
    case SwFieldIds::Macro:
        {
            const SwMacroField *pField = static_cast<const SwMacroField*>(&rField);
            const OUString sText( rField.GetPar2() );
            OUString sRet( sText );
            ExecMacro( pField->GetSvxMacro(), &sRet );
 
            // return value changed?
            if( sRet != sText )
            {
                StartAllAction();
                const_cast<SwField&>(rField).SetPar2( sRet );
                rField.GetTyp()->UpdateFields();
                EndAllAction();
            }
        }
        break;
 
    case SwFieldIds::GetRef:
        StartAllAction();
        SwCursorShell::GotoRefMark( static_cast<const SwGetRefField&>(rField).GetSetRefName(),
                                    static_cast<const SwGetRefField&>(rField).GetSubType(),
                                    static_cast<const SwGetRefField&>(rField).GetSeqNo() );
        EndAllAction();
        break;
 
    case SwFieldIds::Input:
        {
            const SwInputField* pInputField = dynamic_cast<const SwInputField*>(&rField);
            if ( pInputField == nullptr )
            {
                StartInputFieldDlg(const_cast<SwField*>(&rField), false, false, GetView().GetFrameWeld());
            }
        }
        break;
 
    case SwFieldIds::SetExp:
        if( static_cast<const SwSetExpField&>(rField).GetInputFlag() )
            StartInputFieldDlg(const_cast<SwField*>(&rField), false, false, GetView().GetFrameWeld());
        break;
    case SwFieldIds::Dropdown :
        StartDropDownFieldDlg(const_cast<SwField*>(&rField), false, false, GetView().GetFrameWeld());
    break;
    default:
        SAL_WARN_IF(rField.IsClickable(), "sw", "unhandled clickable field!");
    }
 
    m_bIsInClickToEdit = false;
}
 
void SwWrtShell::ClickToINetAttr( const SwFormatINetFormat& rItem, LoadUrlFlags nFilter )
{
    if( rItem.GetValue().isEmpty() )
        return ;
 
    m_bIsInClickToEdit = true;
 
    // At first run the possibly set ObjectSelect Macro
    const SvxMacro* pMac = rItem.GetMacro( SvMacroItemId::OnClick );
    if( pMac )
    {
        SwCallMouseEvent aCallEvent;
        aCallEvent.Set( &rItem );
        GetDoc()->CallEvent( SvMacroItemId::OnClick, aCallEvent );
    }
 
    // So that the implementation of templates is displayed immediately
    ::LoadURL( *this, rItem.GetValue(), nFilter, rItem.GetTargetFrame() );
    const SwTextINetFormat* pTextAttr = rItem.GetTextINetFormat();
    if( pTextAttr )
    {
        const_cast<SwTextINetFormat*>(pTextAttr)->SetVisited( true );
        const_cast<SwTextINetFormat*>(pTextAttr)->SetVisitedValid( true );
    }
 
    m_bIsInClickToEdit = false;
}
 
bool SwWrtShell::ClickToINetGrf( const Point& rDocPt, LoadUrlFlags nFilter )
{
    bool bRet = false;
    OUString sURL;
    OUString sTargetFrameName;
    const SwFrameFormat* pFnd = IsURLGrfAtPos( rDocPt, &sURL, &sTargetFrameName );
    if( pFnd && !sURL.isEmpty() )
    {
        bRet = true;
        // At first run the possibly set ObjectSelect Macro
        const SvxMacro* pMac = &pFnd->GetMacro().GetMacro( SvMacroItemId::OnClick );
        if( pMac )
        {
            SwCallMouseEvent aCallEvent;
            aCallEvent.Set( EVENT_OBJECT_URLITEM, pFnd );
            GetDoc()->CallEvent( SvMacroItemId::OnClick, aCallEvent );
        }
 
        ::LoadURL(*this, sURL, nFilter, sTargetFrameName);
    }
    return bRet;
}
 
void LoadURL( SwViewShell& rVSh, const OUString& rURL, LoadUrlFlags nFilter,
              const OUString& rTargetFrameName )
{
    OSL_ENSURE( !rURL.isEmpty(), "what should be loaded here?" );
    if( rURL.isEmpty() )
        return ;
 
    // The shell could be 0 also!!!!!
    if ( dynamic_cast<const SwCursorShell*>( &rVSh) ==  nullptr )
        return;
 
    OUString sFileURL = rURL;
    INetURLObject aURL( sFileURL );
    if( aURL.GetProtocol() == INetProtocol::NotValid && !sFileURL.startsWith("#") )
    {
        // May be the relative link -> try to convert to absolute path
        OUString sParentPath =
            rVSh.GetDoc()->GetDocShell()->GetMedium()->GetURLObject().GetPath();
 
        bool bCorrectURL = true;
        aURL = INetURLObject();
        bCorrectURL &= aURL.setFSysPath( sParentPath, FSysStyle::Detect );
        bCorrectURL &= aURL.insertName( sFileURL );
 
        if( bCorrectURL )
            sFileURL = aURL.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous );
    }
 
    // We are doing tiledRendering, let the client handles the URL loading,
    // unless we are jumping to a TOC mark.
    if (comphelper::LibreOfficeKit::isActive() && !rURL.startsWith("#"))
    {
        rVSh.GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED, sFileURL.toUtf8().getStr());
        return;
    }
 
    //A CursorShell is always a WrtShell
    SwWrtShell &rSh = static_cast<SwWrtShell&>(rVSh);
 
    SwDocShell* pDShell = rSh.GetView().GetDocShell();
    OSL_ENSURE( pDShell, "No DocShell?!");
    OUString sTargetFrame(rTargetFrameName);
    if (sTargetFrame.isEmpty() && pDShell)
    {
        using namespace ::com::sun::star;
        uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
            pDShell->GetModel(), uno::UNO_QUERY_THROW);
        uno::Reference<document::XDocumentProperties> xDocProps
            = xDPS->getDocumentProperties();
        sTargetFrame = xDocProps->getDefaultTarget();
    }
 
    OUString sReferer;
    if( pDShell && pDShell->GetMedium() )
        sReferer = pDShell->GetMedium()->GetName();
    SfxViewFrame* pViewFrame = rSh.GetView().GetViewFrame();
    SfxFrameItem aView( SID_DOCFRAME, pViewFrame );
    SfxStringItem aName( SID_FILE_NAME, sFileURL );
    SfxStringItem aTargetFrameName( SID_TARGETNAME, sTargetFrame );
    SfxStringItem aReferer( SID_REFERER, sReferer );
 
    SfxBoolItem aNewView( SID_OPEN_NEW_VIEW, false );
    //#39076# Silent can be removed accordingly to SFX.
    SfxBoolItem aBrowse( SID_BROWSE, true );
 
    if ((nFilter & LoadUrlFlags::NewView) && !comphelper::LibreOfficeKit::isActive())
        aTargetFrameName.SetValue( "_blank" );
 
    const SfxPoolItem* aArr[] = {
                &aName,
                &aNewView, /*&aSilent,*/
                &aReferer,
                &aView, &aTargetFrameName,
                &aBrowse,
                nullptr
    };
 
    pViewFrame->GetDispatcher()->GetBindings()->Execute( SID_OPENDOC, aArr,
            SfxCallMode::ASYNCHRON|SfxCallMode::RECORD );
}
 
void SwWrtShell::NavigatorPaste( const NaviContentBookmark& rBkmk,
                                    const sal_uInt16 nAction )
{
    if( EXCHG_IN_ACTION_COPY == nAction )
    {
        // Insert
        OUString sURL = rBkmk.GetURL();
        // Is this is a jump within the current Doc?
        const SwDocShell* pDocShell = GetView().GetDocShell();
        if(pDocShell->HasName())
        {
            const OUString rName = pDocShell->GetMedium()->GetURLObject().GetURLNoMark();
 
            if (sURL.startsWith(rName))
            {
                if (sURL.getLength()>rName.getLength())
                {
                    sURL = sURL.copy(rName.getLength());
                }
                else
                {
                    sURL.clear();
                }
            }
        }
        SwFormatINetFormat aFormat( sURL, OUString() );
        InsertURL( aFormat, rBkmk.GetDescription() );
    }
    else
    {
        SwSectionData aSection( FILE_LINK_SECTION, GetUniqueSectionName() );
        OUString aLinkFile = rBkmk.GetURL().getToken(0, '#')
            + OUStringLiteral1(sfx2::cTokenSeparator)
            + OUStringLiteral1(sfx2::cTokenSeparator)
            + rBkmk.GetURL().getToken(1, '#');
        aSection.SetLinkFileName( aLinkFile );
        aSection.SetProtectFlag( true );
        const SwSection* pIns = InsertSection( aSection );
        if( EXCHG_IN_ACTION_MOVE == nAction && pIns )
        {
            aSection = SwSectionData(*pIns);
            aSection.SetLinkFileName( OUString() );
            aSection.SetType( CONTENT_SECTION );
            aSection.SetProtectFlag( false );
 
            // the update of content from linked section at time delete
            // the undostack. Then the change of the section don't create
            // any undoobject. -  BUG 69145
            bool bDoesUndo = DoesUndo();
            SwUndoId nLastUndoId(SwUndoId::EMPTY);
            if (GetLastUndoInfo(nullptr, & nLastUndoId))
            {
                if (SwUndoId::INSSECTION != nLastUndoId)
                {
                    DoUndo(false);
                }
            }
            UpdateSection( GetSectionFormatPos( *pIns->GetFormat() ), aSection );
            DoUndo( bDoesUndo );
        }
    }
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V773 Visibility scope of the 'pTmp' pointer was exited without releasing the memory. A memory leak is possible.

V547 Expression 'pMac' is always true.