/* -*- 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 <view.hxx>
#include <wrtsh.hxx>
#include <swundo.hxx>
#include <splargs.hxx>

#include <editeng/langitem.hxx>
#include <editeng/fontitem.hxx>
#include <rtl/ustring.hxx>
#include <com/sun/star/text/RubyAdjust.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <osl/diagnose.h>
#include <hhcwrp.hxx>
#include "sdrhhcwrap.hxx"
#include <doc.hxx>
#include <docsh.hxx>
#include <mdiexp.hxx>
#include <edtwin.hxx>
#include <contentindex.hxx>
#include <pam.hxx>
#include <swcrsr.hxx>
#include <ndtxt.hxx>
#include <fmtruby.hxx>
#include <breakit.hxx>

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

//     Description: Turn off frame/object shell if applicable

static void lcl_ActivateTextShell( SwWrtShell & rWrtSh )
{
    if( rWrtSh.IsSelFrameMode() || rWrtSh.GetSelectedObjCount() )
        rWrtSh.EnterStdMode();
}

namespace {

class SwKeepConversionDirectionStateContext
{
public:
    SwKeepConversionDirectionStateContext()
    {
        //!! hack to transport the current conversion direction state settings
        //!! into the next incarnation that iterates over the drawing objects
        //!! ( see SwHHCWrapper::~SwHHCWrapper() )
        editeng::HangulHanjaConversion::SetUseSavedConversionDirectionState( true );
    }

    ~SwKeepConversionDirectionStateContext()
    {
        editeng::HangulHanjaConversion::SetUseSavedConversionDirectionState( false );
    }
};

}

SwHHCWrapper::SwHHCWrapper(
        SwView& rSwView,
        const uno::Reference< uno::XComponentContext >& rxContext,
        LanguageType nSourceLanguage,
        LanguageType nTargetLanguage,
        const vcl::Font *pTargetFont,
        sal_Int32 nConvOptions,
        bool bIsInteractive,
        bool bStart, bool bOther, bool bSelection )
    : editeng::HangulHanjaConversion(rSwView.GetEditWin().GetFrameWeld(), rxContext,
                                LanguageTag::convertToLocale( nSourceLanguage ),
                                LanguageTag::convertToLocale( nTargetLanguage ),
                                pTargetFont,
                                nConvOptions,
                                bIsInteractive )
    , m_rView( rSwView )
    , m_rWrtShell( rSwView.GetWrtShell() )
    , m_nLastPos( 0 )
    , m_nUnitOffset( 0 )
    , m_nPageCount( 0 )
    , m_nPageStart( 0 )
    , m_bIsDrawObj( false )
    , m_bIsOtherContent( bOther )
    , m_bStartChk( bOther )
    , m_bIsSelection( bSelection )
    , m_bStartDone( bOther || bStart )
    , m_bEndDone( false )
{
}

SwHHCWrapper::~SwHHCWrapper() COVERITY_NOEXCEPT_FALSE
{
    m_pConvArgs.reset();

    SwViewShell::SetCareDialog(nullptr);

    // check for existence of a draw view which means that there are
    // (or previously were) draw objects present in the document.
    // I.e. we like to check those too.
    if ( m_bIsDrawObj /*&& bLastRet*/ && m_rView.GetWrtShell().HasDrawView() )
    {
        vcl::Cursor *pSave = m_rView.GetWindow()->GetCursor();
        {
            SwKeepConversionDirectionStateContext aContext;

            SdrHHCWrapper aSdrConvWrap( m_rView, GetSourceLanguage(),
                    GetTargetLanguage(), GetTargetFont(),
                    GetConversionOptions(), IsInteractive() );
            aSdrConvWrap.StartTextConversion();
        }
        m_rView.GetWindow()->SetCursor( pSave );
    }

    if( m_nPageCount )
        ::EndProgress( m_rView.GetDocShell() );

    // finally for chinese translation we need to change the documents
    // default language and font to the new ones to be used.
    LanguageType nTargetLang = GetTargetLanguage();
    if (!IsChinese( nTargetLang ))
        return;

    SwDoc *pDoc = m_rView.GetDocShell()->GetDoc();

    //!! Note: This also effects the default language of text boxes (EditEngine/EditView) !!
    pDoc->SetDefault( SvxLanguageItem( nTargetLang, RES_CHRATR_CJK_LANGUAGE ) );

    const vcl::Font *pFont = GetTargetFont();
    if (pFont)
    {
        SvxFontItem aFontItem( pFont->GetFamilyType(), pFont->GetFamilyName(),
                pFont->GetStyleName(), pFont->GetPitch(),
                pFont->GetCharSet(), RES_CHRATR_CJK_FONT );
        pDoc->SetDefault( aFontItem );
    }
}

void SwHHCWrapper::GetNextPortion(
        OUString&           rNextPortion,
        LanguageType&       rLangOfPortion,
        bool bAllowChanges )
{
    m_pConvArgs->bAllowImplicitChangesForNotConvertibleText = bAllowChanges;

    FindConvText_impl();
    rNextPortion    = m_pConvArgs->aConvText;
    rLangOfPortion  = m_pConvArgs->nConvTextLang;

    m_nUnitOffset  = 0;

    // build last pos from currently selected text
    SwPaM* pCursor = m_rWrtShell.GetCursor();
    m_nLastPos =  pCursor->Start()->GetContentIndex();
}

void SwHHCWrapper::SelectNewUnit_impl( sal_Int32 nUnitStart, sal_Int32 nUnitEnd )
{
    SwPaM *pCursor = m_rWrtShell.GetCursor();
    pCursor->GetPoint()->SetContent( m_nLastPos );
    pCursor->DeleteMark();

    m_rWrtShell.Right( SwCursorSkipMode::Chars, /*bExpand*/ false,
                  o3tl::narrowing<sal_uInt16>(m_nUnitOffset + nUnitStart), true );
    pCursor->SetMark();
    m_rWrtShell.Right( SwCursorSkipMode::Chars, /*bExpand*/ true,
                  o3tl::narrowing<sal_uInt16>(nUnitEnd - nUnitStart), true );
    // end selection now. Otherwise SHIFT+HOME (extending the selection)
    // won't work when the dialog is closed without any replacement.
    // (see #116346#)
    m_rWrtShell.EndSelect();
}

void SwHHCWrapper::HandleNewUnit(
        const sal_Int32 nUnitStart, const sal_Int32 nUnitEnd )
{
    OSL_ENSURE( nUnitStart >= 0 && nUnitEnd >= nUnitStart, "wrong arguments" );
    if (0 > nUnitStart || nUnitStart > nUnitEnd)
        return;

    lcl_ActivateTextShell( m_rWrtShell );

    m_rWrtShell.StartAllAction();

    // select current unit
    SelectNewUnit_impl( nUnitStart, nUnitEnd );

    m_rWrtShell.EndAllAction();
}

void SwHHCWrapper::ChangeText( const OUString &rNewText,
        std::u16string_view aOrigText,
        const uno::Sequence< sal_Int32 > *pOffsets,
        SwPaM *pCursor )
{
    //!! please see also TextConvWrapper::ChangeText with is a modified
    //!! copy of this code

    OSL_ENSURE( !rNewText.isEmpty(), "unexpected empty string" );
    if (rNewText.isEmpty())
        return;

    if (pOffsets && pCursor)  // try to keep as much attributation as possible ?
    {
        // remember cursor start position for later setting of the cursor
        const SwPosition *pStart = pCursor->Start();
        const sal_Int32 nStartIndex = pStart->GetContentIndex();
        SwTextNode *pStartTextNode = pStart->GetNode().GetTextNode();

        const sal_Int32  nIndices = pOffsets->getLength();
        const sal_Int32 *pIndices = pOffsets->getConstArray();
        sal_Int32 nConvTextLen = rNewText.getLength();
        sal_Int32 nPos = 0;
        sal_Int32 nChgPos = -1;
        sal_Int32 nChgLen = 0;
        sal_Int32 nConvChgPos = -1;
        sal_Int32 nConvChgLen = 0;

        // offset to calculate the position in the text taking into
        // account that text may have been replaced with new text of
        // different length. Negative values allowed!
        tools::Long nCorrectionOffset = 0;

        OSL_ENSURE(nIndices == 0 || nIndices == nConvTextLen,
                "mismatch between string length and sequence length!" );

        // find all substrings that need to be replaced (and only those)
        while (true)
        {
            // get index in original text that matches nPos in new text
            sal_Int32 nIndex;
            if (nPos < nConvTextLen)
                nIndex = nPos < nIndices ? pIndices[nPos] : nPos;
            else
            {
                nPos   = nConvTextLen;
                nIndex = aOrigText.size();
            }

            if (nPos == nConvTextLen || /* end of string also terminates non-matching char sequence */
                aOrigText[nIndex] == rNewText[nPos])
            {
                // substring that needs to be replaced found?
                if (nChgPos != -1 && nConvChgPos != -1)
                {
                    nChgLen = nIndex - nChgPos;
                    nConvChgLen = nPos - nConvChgPos;
                    OUString aInNew( rNewText.copy( nConvChgPos, nConvChgLen ) );

                    // set selection to sub string to be replaced in original text
                    sal_Int32 nChgInNodeStartIndex = nStartIndex + nCorrectionOffset + nChgPos;
                    OSL_ENSURE( m_rWrtShell.GetCursor()->HasMark(), "cursor misplaced (nothing selected)" );
                    m_rWrtShell.GetCursor()->GetMark()->Assign( *pStartTextNode, nChgInNodeStartIndex );
                    m_rWrtShell.GetCursor()->GetPoint()->Assign( *pStartTextNode, nChgInNodeStartIndex + nChgLen );

                    // replace selected sub string with the corresponding
                    // sub string from the new text while keeping as
                    // much from the attributes as possible
                    ChangeText_impl( aInNew, true );

                    nCorrectionOffset += nConvChgLen - nChgLen;

                    nChgPos = -1;
                    nConvChgPos = -1;
                }
            }
            else
            {
                // begin of non-matching char sequence found ?
                if (nChgPos == -1 && nConvChgPos == -1)
                {
                    nChgPos = nIndex;
                    nConvChgPos = nPos;
                }
            }
            if (nPos >= nConvTextLen)
                break;
            ++nPos;
        }

        // set cursor to the end of all the new text
        // (as it would happen after ChangeText_impl (Delete and Insert)
        // of the whole text in the 'else' branch below)
        m_rWrtShell.ClearMark();
        m_rWrtShell.GetCursor()->Start()->Assign( *pStartTextNode, nStartIndex + nConvTextLen );
    }
    else
    {
        ChangeText_impl( rNewText, false );
    }
}

void SwHHCWrapper::ChangeText_impl( const OUString &rNewText, bool bKeepAttributes )
{
    if (bKeepAttributes)
    {
        // get item set with all relevant attributes
        SfxItemSet aItemSet(SfxItemSet::makeFixedSfxItemSet<RES_CHRATR_BEGIN, RES_FRMATR_END>(m_rWrtShell.GetAttrPool()));
        // get all attributes spanning the whole selection in order to
        // restore those for the new text
        m_rWrtShell.GetCurAttr( aItemSet );

        m_rWrtShell.Delete(true);
        m_rWrtShell.Insert( rNewText );

        // select new inserted text (currently the Point is right after the new text)
        if (!m_rWrtShell.GetCursor()->HasMark())
            m_rWrtShell.GetCursor()->SetMark();
        SwPosition *pMark = m_rWrtShell.GetCursor()->GetMark();
        pMark->SetContent( pMark->GetContentIndex() - rNewText.getLength() );

        // since 'SetAttr' below functions like merging with the attributes
        // from the itemset with any existing ones we have to get rid of all
        // all attributes now. (Those attributes that may take effect left
        // to the position where the new text gets inserted after the old text
        // was deleted)
        m_rWrtShell.ResetAttr();
        // apply previously saved attributes to new text
        m_rWrtShell.SetAttrSet( aItemSet );
    }
    else
    {
        m_rWrtShell.Delete(true);
        m_rWrtShell.Insert( rNewText );
    }
}

void SwHHCWrapper::ReplaceUnit(
         const sal_Int32 nUnitStart, const sal_Int32 nUnitEnd,
         const OUString& rOrigText,
         const OUString& rReplaceWith,
         const uno::Sequence< sal_Int32 > &rOffsets,
         ReplacementAction eAction,
         LanguageType *pNewUnitLanguage )
{
    OSL_ENSURE( nUnitStart >= 0 && nUnitEnd >= nUnitStart, "wrong arguments" );
    if (nUnitStart < 0 || nUnitEnd < nUnitStart)
        return;

    lcl_ActivateTextShell( m_rWrtShell );

    // replace the current word
    m_rWrtShell.StartAllAction();

    // select current unit
    SelectNewUnit_impl( nUnitStart, nUnitEnd );

    OUString aOrigText( m_rWrtShell.GetSelText() );
    OUString aNewText( rReplaceWith );
    OSL_ENSURE( aOrigText == rOrigText, "!! text mismatch !!" );
    std::unique_ptr<SwFormatRuby> pRuby;
    bool bRubyBelow = false;
    OUString  aNewOrigText;
    switch (eAction)
    {
        case eExchange :
        break;
        case eReplacementBracketed :
        {
            aNewText = aOrigText + "(" + rReplaceWith + ")";
        }
        break;
        case eOriginalBracketed :
        {
            aNewText = rReplaceWith + "(" + aOrigText + ")";
        }
        break;
        case eReplacementAbove  :
        {
            pRuby.reset(new SwFormatRuby( rReplaceWith ));
        }
        break;
        case eOriginalAbove :
        {
            pRuby.reset(new SwFormatRuby( aOrigText ));
            aNewOrigText = rReplaceWith;
        }
        break;
        case eReplacementBelow :
        {
            pRuby.reset(new SwFormatRuby( rReplaceWith ));
            bRubyBelow = true;
        }
        break;
        case eOriginalBelow :
        {
            pRuby.reset(new SwFormatRuby( aOrigText ));
            aNewOrigText = rReplaceWith;
            bRubyBelow = true;
        }
        break;
        default:
            OSL_FAIL("unexpected case" );
    }
    m_nUnitOffset += nUnitStart + aNewText.getLength();

    if (pRuby)
    {
        m_rWrtShell.StartUndo( SwUndoId::SETRUBYATTR );
        if (!aNewOrigText.isEmpty())
        {
            // according to FT we currently should not bother about keeping
            // attributes in Hangul/Hanja conversion
            ChangeText( aNewOrigText, rOrigText, nullptr, nullptr );

            //!! since Delete, Insert in 'ChangeText' do not set the WrtShells
            //!! bInSelect flag
            //!! back to false we do it now manually in order for the selection
            //!! to be done properly in the following call to Left.
            // We didn't fix it in Delete and Insert since it is currently
            // unclear if someone depends on this incorrect behaviour
            // of the flag.
            m_rWrtShell.EndSelect();

            m_rWrtShell.Left( SwCursorSkipMode::Chars, true, aNewOrigText.getLength(), true, true );
        }

        pRuby->SetPosition( o3tl::narrowing<sal_uInt16>(bRubyBelow) );
        pRuby->SetAdjustment( RubyAdjust_CENTER );

        m_rWrtShell.SetAttrItem(*pRuby);
        pRuby.reset();
        m_rWrtShell.EndUndo( SwUndoId::SETRUBYATTR );
    }
    else
    {
        m_rWrtShell.StartUndo( SwUndoId::OVERWRITE );

        // according to FT we should currently not bother about keeping
        // attributes in Hangul/Hanja conversion and leave that untouched.
        // Thus we do this only for Chinese translation...
        const bool bIsChineseConversion = IsChinese( GetSourceLanguage() );
        if (bIsChineseConversion)
            ChangeText( aNewText, rOrigText, &rOffsets, m_rWrtShell.GetCursor() );
        else
            ChangeText( aNewText, rOrigText, nullptr, nullptr );

        // change language and font if necessary
        if (bIsChineseConversion)
        {
            m_rWrtShell.SetMark();
            m_rWrtShell.GetCursor()->GetMark()->AdjustContent( -aNewText.getLength() );

            OSL_ENSURE( GetTargetLanguage() == LANGUAGE_CHINESE_SIMPLIFIED || GetTargetLanguage() == LANGUAGE_CHINESE_TRADITIONAL,
                    "SwHHCWrapper::ReplaceUnit : unexpected target language" );

            SfxItemSet aSet(SfxItemSet::makeFixedSfxItemSet<
                            RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONT,
                            RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CJK_LANGUAGE>(m_rWrtShell.GetAttrPool()));
            if (pNewUnitLanguage)
            {
                aSet.Put( SvxLanguageItem( *pNewUnitLanguage, RES_CHRATR_CJK_LANGUAGE ) );
            }

            const vcl::Font *pTargetFont = GetTargetFont();
            OSL_ENSURE( pTargetFont, "target font missing?" );
            if (pTargetFont && pNewUnitLanguage)
            {
                SvxFontItem aFontItem( aSet.Get( RES_CHRATR_CJK_FONT ) );
                aFontItem.SetFamilyName(    pTargetFont->GetFamilyName());
                aFontItem.SetFamily(        pTargetFont->GetFamilyType());
                aFontItem.SetStyleName(     pTargetFont->GetStyleName());
                aFontItem.SetPitch(         pTargetFont->GetPitch());
                aFontItem.SetCharSet( pTargetFont->GetCharSet() );
                aSet.Put( aFontItem );
            }

            m_rWrtShell.SetAttrSet( aSet );

            m_rWrtShell.ClearMark();
        }

        m_rWrtShell.EndUndo( SwUndoId::OVERWRITE );
    }

    m_rWrtShell.EndAllAction();
}

bool SwHHCWrapper::HasRubySupport() const
{
    return true;
}

void SwHHCWrapper::Convert()
{
    OSL_ENSURE( m_pConvArgs == nullptr, "NULL pointer expected" );
    {
        SwPaM *pCursor = m_rView.GetWrtShell().GetCursor();
        auto [pSttPos, pEndPos] = pCursor->StartEnd(); // SwPosition*

        if (pSttPos->GetNode().IsTextNode() &&
            pEndPos->GetNode().IsTextNode())
        {
            m_pConvArgs.reset( new SwConversionArgs( GetSourceLanguage(), *pSttPos, *pEndPos ) );
        }
        else    // we are not in the text (maybe a graphic or OLE object is selected) let's start from the top
        {
            // get PaM that points to the start of the document
            SwNode& rNode = m_rView.GetDocShell()->GetDoc()->GetNodes().GetEndOfContent();
            SwPaM aPam(rNode);
            aPam.Move( fnMoveBackward, GoInDoc ); // move to start of document

            pSttPos = aPam.GetPoint();  //! using a PaM here makes sure we will get only text nodes
            SwTextNode *pTextNode = pSttPos->GetNode().GetTextNode();
            // just in case we check anyway...
            if (!pTextNode || !pTextNode->IsTextNode())
                return;
            m_pConvArgs.reset( new SwConversionArgs( GetSourceLanguage(), *pSttPos, *pSttPos ) );
        }
        OSL_ENSURE( m_pConvArgs->pStartPos && m_pConvArgs->pStartPos->GetNode().IsTextNode(),
                "failed to get proper start text node" );
        OSL_ENSURE( m_pConvArgs->pEndPos && m_pConvArgs->pEndPos->GetNode().IsTextNode(),
                "failed to get proper end text node" );

        // chinese conversion specific settings
        OSL_ENSURE( IsChinese( GetSourceLanguage() ) == IsChinese( GetTargetLanguage() ),
                "source and target language mismatch?" );
        if (IsChinese( GetTargetLanguage() ))
        {
            m_pConvArgs->nConvTargetLang = GetTargetLanguage();
            m_pConvArgs->pTargetFont = GetTargetFont();
            m_pConvArgs->bAllowImplicitChangesForNotConvertibleText = true;
        }

        // if it is not just a selection and we are about to begin
        // with the current conversion for the very first time
        // we need to find the start of the current (initial)
        // convertible unit in order for the text conversion to give
        // the correct result for that. Since it is easier to obtain
        // the start of the word we use that though.
        if (!pCursor->HasMark())   // is not a selection?
        {
            // since #118246 / #117803 still occurs if the cursor is placed
            // between the two chinese characters to be converted (because both
            // of them are words on their own!) using the word boundary here does
            // not work. Thus since chinese conversion is not interactive we start
            // at the begin of the paragraph to solve the problem, i.e. have the
            // TextConversion service get those characters together in the same call.
            sal_Int32 nStartIdx = -1;
            if (editeng::HangulHanjaConversion::IsChinese( GetSourceLanguage() ) )
                nStartIdx = 0;
            else
            {
                OUString aText( m_pConvArgs->pStartPos->GetNode().GetTextNode()->GetText() );
                const sal_Int32 nPos = m_pConvArgs->pStartPos->GetContentIndex();
                Boundary aBoundary( g_pBreakIt->GetBreakIter()->
                        getWordBoundary( aText, nPos, g_pBreakIt->GetLocale( m_pConvArgs->nConvSrcLang ),
                                WordType::DICTIONARY_WORD, true ) );

                // valid result found?
                if (aBoundary.startPos < aText.getLength() &&
                    aBoundary.startPos != aBoundary.endPos)
                {
                    nStartIdx = aBoundary.startPos;
                }
            }

            if (nStartIdx != -1)
                m_pConvArgs->pStartPos->SetContent( nStartIdx );
        }
    }

    if ( m_bIsOtherContent )
        ConvStart_impl( m_pConvArgs.get(), SvxSpellArea::Other );
    else
    {
        m_bStartChk = false;
        ConvStart_impl( m_pConvArgs.get(), SvxSpellArea::BodyEnd );
    }

    ConvertDocument();

    ConvEnd_impl( m_pConvArgs.get() );
}

bool SwHHCWrapper::ConvNext_impl( )
{
    //! modified version of SvxSpellWrapper::SpellNext

    // no change of direction so the desired region is fully processed
    if( m_bStartChk )
        m_bStartDone = true;
    else
        m_bEndDone = true;

    if( m_bIsOtherContent && m_bStartDone && m_bEndDone ) // document completely checked?
    {
        return false;
    }

    bool bGoOn = false;

    if ( m_bIsOtherContent )
    {
        m_bStartChk = false;
        ConvStart_impl( m_pConvArgs.get(), SvxSpellArea::Body );
        bGoOn = true;
    }
    else if ( m_bStartDone && m_bEndDone )
    {
        // body region done, ask about special region
        if( !m_bIsSelection && m_rWrtShell.HasOtherCnt() )
        {
            ConvStart_impl( m_pConvArgs.get(), SvxSpellArea::Other );
            m_bIsOtherContent = bGoOn = true;
        }
    }
    else
    {
            m_bStartChk = !m_bStartDone;
            ConvStart_impl( m_pConvArgs.get(), m_bStartChk ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd );
            bGoOn = true;
    }
    return bGoOn;
}

void SwHHCWrapper::FindConvText_impl()
{
    //! modified version of SvxSpellWrapper::FindSpellError

    bool bFound = false;

    weld::WaitObject aWait(GetUIParent());
    bool bConv = true;

    while ( bConv )
    {
        bFound = ConvContinue_impl( m_pConvArgs.get() );
        if (bFound)
        {
            bConv = false;
        }
        else
        {
            ConvEnd_impl( m_pConvArgs.get() );
            bConv = ConvNext_impl();
        }
    }
}

void SwHHCWrapper::ConvStart_impl( SwConversionArgs /* [out] */ *pConversionArgs, SvxSpellArea eArea )
{
    m_bIsDrawObj = SvxSpellArea::Other == eArea;
    m_rView.SpellStart( eArea, m_bStartDone, m_bEndDone, /* [out] */ pConversionArgs );
}

void SwHHCWrapper::ConvEnd_impl( SwConversionArgs const *pConversionArgs )
{
    m_rView.SpellEnd( pConversionArgs );
}

bool SwHHCWrapper::ConvContinue_impl( SwConversionArgs *pConversionArgs )
{
    bool bProgress = !m_bIsDrawObj && !m_bIsSelection;
    pConversionArgs->aConvText.clear();
    pConversionArgs->nConvTextLang = LANGUAGE_NONE;
    m_rView.GetWrtShell().SpellContinue( &m_nPageCount, bProgress ? &m_nPageStart : nullptr, pConversionArgs );
    return !pConversionArgs->aConvText.isEmpty();
}

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