/* -*- 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 "hangulhanjadlg.hxx"
#include <dialmgr.hxx>

#include <cuires.hrc>
#include "helpid.hrc"

#include <algorithm>
#include <vcl/controllayout.hxx>
#include <vcl/msgbox.hxx>
#include <vcl/builderfactory.hxx>
#include <vcl/decoview.hxx>
#include <unotools/lingucfg.hxx>
#include <unotools/linguprops.hxx>
#include <com/sun/star/linguistic2/ConversionDictionaryType.hpp>
#include <com/sun/star/linguistic2/ConversionDirection.hpp>
#include <com/sun/star/linguistic2/ConversionDictionaryList.hpp>
#include <com/sun/star/i18n/TextConversionOption.hpp>
#include <com/sun/star/util/XFlushable.hpp>

#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include "svtools/treelistentry.hxx"

#define HHC editeng::HangulHanjaConversion
#define LINE_CNT        static_cast< sal_uInt16 >(2)
#define MAXNUM_SUGGESTIONS 50


namespace svx
{

    using namespace ::com::sun::star;
    using namespace css::uno;
    using namespace css::linguistic2;
    using namespace css::lang;
    using namespace css::container;


    namespace
    {
        class FontSwitch
        {
        private:
            OutputDevice& m_rDev;

        public:
            inline FontSwitch( OutputDevice& _rDev, const vcl::Font& _rTemporaryFont )
                :m_rDev( _rDev )
            {
                m_rDev.Push( PushFlags::FONT );
                m_rDev.SetFont( _rTemporaryFont );
            }
            inline ~FontSwitch( )
            {
                m_rDev.Pop( );
            }
        };
    }

    /** a class which allows to draw two texts in a pseudo-ruby way (which basically
        means one text above or below the other, and a little bit smaller)
    */
    class PseudoRubyText
    {
    public:
        enum RubyPosition
        {
            eAbove, eBelow
        };

    protected:
        OUString      m_sPrimaryText;
        OUString      m_sSecondaryText;
        RubyPosition  m_ePosition;

    public:
        PseudoRubyText();
        void init( const OUString& rPrimaryText, const OUString& rSecondaryText, const RubyPosition& rPosition );
        const OUString& getPrimaryText() const { return m_sPrimaryText; }
        const OUString& getSecondaryText() const { return m_sSecondaryText; }

    public:
        void Paint( OutputDevice& _rDevice, const Rectangle& _rRect, DrawTextFlags _nTextStyle,
            Rectangle* _pPrimaryLocation = nullptr, Rectangle* _pSecondaryLocation = nullptr,
            vcl::ControlLayoutData* _pLayoutData = nullptr );
    };

    PseudoRubyText::PseudoRubyText()
        : m_ePosition(eAbove)
    {
    }

    void PseudoRubyText::init( const OUString& rPrimaryText, const OUString& rSecondaryText, const RubyPosition& rPosition )
    {
        m_sPrimaryText = rPrimaryText;
        m_sSecondaryText = rSecondaryText;
        m_ePosition = rPosition;
    }


    void PseudoRubyText::Paint(vcl::RenderContext& rRenderContext, const Rectangle& _rRect, DrawTextFlags _nTextStyle,
                               Rectangle* _pPrimaryLocation, Rectangle* _pSecondaryLocation,
                               vcl::ControlLayoutData* _pLayoutData )
    {
        bool bLayoutOnly  = (nullptr != _pLayoutData);
        MetricVector* pTextMetrics = bLayoutOnly ? &_pLayoutData->m_aUnicodeBoundRects : nullptr;
        OUString* pDisplayText = bLayoutOnly ? &_pLayoutData->m_aDisplayText       : nullptr;

        Size aPlaygroundSize(_rRect.GetSize());

        // the font for the secondary text:
        vcl::Font aSmallerFont(rRenderContext.GetFont());
        // heuristic: 80% of the original size
        aSmallerFont.SetHeight( (long)( 0.8 * aSmallerFont.GetHeight() ) );

        // let's calculate the size of our two texts
        Rectangle aPrimaryRect = rRenderContext.GetTextRect( _rRect, m_sPrimaryText, _nTextStyle );
        Rectangle aSecondaryRect;
        {
            FontSwitch aFontRestore(rRenderContext, aSmallerFont);
            aSecondaryRect = rRenderContext.GetTextRect(_rRect, m_sSecondaryText, _nTextStyle);
        }

        // position these rectangles properly
        // x-axis:
        sal_Int32 nCombinedWidth = ::std::max( aSecondaryRect.GetWidth(), aPrimaryRect.GetWidth() );
            // the rectangle where both texts will reside is as high as possible, and as wide as the
            // widest of both text rects
        aPrimaryRect.Left() = aSecondaryRect.Left() = _rRect.Left();
        aPrimaryRect.Right() = aSecondaryRect.Right() = _rRect.Left() + nCombinedWidth;
        if (DrawTextFlags::Right & _nTextStyle)
        {
            // move the rectangles to the right
            aPrimaryRect.Move( aPlaygroundSize.Width() - nCombinedWidth, 0 );
            aSecondaryRect.Move( aPlaygroundSize.Width() - nCombinedWidth, 0 );
        }
        else if (DrawTextFlags::Center & _nTextStyle)
        {
            // center the rectangles
            aPrimaryRect.Move( ( aPlaygroundSize.Width() - nCombinedWidth ) / 2, 0 );
            aSecondaryRect.Move( ( aPlaygroundSize.Width() - nCombinedWidth ) / 2, 0 );
        }

        // y-axis:
        sal_Int32 nCombinedHeight = aPrimaryRect.GetHeight() + aSecondaryRect.GetHeight();
        // align to the top, for the moment
        aPrimaryRect.Move( 0, _rRect.Top() - aPrimaryRect.Top() );
        aSecondaryRect.Move( 0, aPrimaryRect.Top() + aPrimaryRect.GetHeight() - aSecondaryRect.Top() );
        if (DrawTextFlags::Bottom & _nTextStyle)
        {
            // move the rects to the bottom
            aPrimaryRect.Move( 0, aPlaygroundSize.Height() - nCombinedHeight );
            aSecondaryRect.Move( 0, aPlaygroundSize.Height() - nCombinedHeight );
        }
        else if (DrawTextFlags::VCenter & _nTextStyle)
        {
            // move the rects to the bottom
            aPrimaryRect.Move( 0, ( aPlaygroundSize.Height() - nCombinedHeight ) / 2 );
            aSecondaryRect.Move( 0, ( aPlaygroundSize.Height() - nCombinedHeight ) / 2 );
        }

        // 'til here, everything we did assumes that the secondary text is painted _below_ the primary
        // text. If this isn't the case, we need to correct the rectangles
        if (eAbove == m_ePosition)
        {
            sal_Int32 nVertDistance = aSecondaryRect.Top() - aPrimaryRect.Top();
            aSecondaryRect.Move( 0, -nVertDistance );
            aPrimaryRect.Move( 0, nCombinedHeight - nVertDistance );
        }

        // now draw the texts
        // as we already calculated the precise rectangles for the texts, we don't want to
        // use the alignment flags given - within it's rect, every text is centered
        DrawTextFlags nDrawTextStyle( _nTextStyle );
        nDrawTextStyle &= ~DrawTextFlags( DrawTextFlags::Right | DrawTextFlags::Left | DrawTextFlags::Bottom | DrawTextFlags::Top );
        nDrawTextStyle |= DrawTextFlags::Center | DrawTextFlags::VCenter;

        rRenderContext.DrawText( aPrimaryRect, m_sPrimaryText, nDrawTextStyle, pTextMetrics, pDisplayText );
        {
            FontSwitch aFontRestore(rRenderContext, aSmallerFont);
            rRenderContext.DrawText( aSecondaryRect, m_sSecondaryText, nDrawTextStyle, pTextMetrics, pDisplayText );
        }

        // outta here
        if (_pPrimaryLocation)
            *_pPrimaryLocation = aPrimaryRect;
        if (_pSecondaryLocation)
            *_pSecondaryLocation = aSecondaryRect;
    }

    class RubyRadioButton : public RadioButton
    {

    public:
        RubyRadioButton( vcl::Window* _pParent, WinBits nBits );
        void init( const OUString& rPrimaryText, const OUString& rSecondaryText, const PseudoRubyText::RubyPosition& rPosition );
        virtual Size    GetOptimalSize() const override;

    protected:
        virtual void    Paint( vcl::RenderContext& /*rRenderContext*/, const Rectangle& _rRect ) override;

    private:
        PseudoRubyText m_aRubyText;
    };

    RubyRadioButton::RubyRadioButton( vcl::Window* _pParent, WinBits nBits )
        :RadioButton( _pParent, nBits )
    {
    }

    void RubyRadioButton::init( const OUString& rPrimaryText, const OUString& rSecondaryText, const PseudoRubyText::RubyPosition& rPosition )
    {
        m_aRubyText.init( rPrimaryText, rSecondaryText, rPosition );
    }


    void RubyRadioButton::Paint(vcl::RenderContext& rRenderContext, const Rectangle&)
    {
        HideFocus();

        // calculate the size of the radio image - we're to paint our text _after_ this image
        DBG_ASSERT( !GetModeRadioImage(), "RubyRadioButton::Paint: images not supported!" );
        Size aImageSize = GetRadioImage(rRenderContext.GetSettings(), DrawButtonFlags::NONE).GetSizePixel();
        aImageSize.Width() = CalcZoom( aImageSize.Width() ) + 2;   // + 2 because otherwise the radiobuttons
        aImageSize.Height() = CalcZoom( aImageSize.Height() ) + 2; // appear a bit cut from right and top.

        Rectangle aOverallRect( Point( 0, 0 ), GetOutputSizePixel() );
        aOverallRect.Left() += aImageSize.Width() + 4;  // 4 is the separator between the image and the text
        // inflate the rect a little bit (because the VCL radio button does the same)
        Rectangle aTextRect( aOverallRect );
        ++aTextRect.Left(); --aTextRect.Right();
        ++aTextRect.Top(); --aTextRect.Bottom();

        // calculate the text flags for the painting
        DrawTextFlags nTextStyle = DrawTextFlags::Mnemonic;
        WinBits nStyle = GetStyle( );

        // the horizontal alignment
        if ( nStyle & WB_RIGHT )
            nTextStyle |= DrawTextFlags::Right;
        else if ( nStyle & WB_CENTER )
            nTextStyle |= DrawTextFlags::Center;
        else
            nTextStyle |= DrawTextFlags::Left;
        // the vertical alignment
        if ( nStyle & WB_BOTTOM )
            nTextStyle |= DrawTextFlags::Bottom;
        else if ( nStyle & WB_VCENTER )
            nTextStyle |= DrawTextFlags::VCenter;
        else
            nTextStyle |= DrawTextFlags::Top;
        // mnemonics
        if ( 0 == ( nStyle & WB_NOLABEL ) )
            nTextStyle |= DrawTextFlags::Mnemonic;

        // paint the ruby text
        Rectangle aPrimaryTextLocation;
        Rectangle aSecondaryTextLocation;

        m_aRubyText.Paint(rRenderContext, aTextRect, nTextStyle, &aPrimaryTextLocation, &aSecondaryTextLocation);

        // the focus rectangle is to be painted around both texts
        Rectangle aCombinedRect(aPrimaryTextLocation);
        aCombinedRect.Union(aSecondaryTextLocation);
        SetFocusRect(aCombinedRect);

        // let the base class paint the radio button
        // for this, give it the proper location to paint the image (vertically centered, relative to our text)
        Rectangle aImageLocation( Point( 0, 0 ), aImageSize );
        sal_Int32 nTextHeight = aSecondaryTextLocation.Bottom() - aPrimaryTextLocation.Top();
        aImageLocation.Top() = aPrimaryTextLocation.Top() + ( nTextHeight - aImageSize.Height() ) / 2;
        aImageLocation.Bottom() = aImageLocation.Top() + aImageSize.Height();
        SetStateRect( aImageLocation );
        DrawRadioButtonState(rRenderContext);

        // mouse clicks should be recognized in a rect which is one pixel larger in each direction, plus
        // includes the image
        aCombinedRect.Left() = aImageLocation.Left();
        ++aCombinedRect.Right();
        --aCombinedRect.Top();
        ++aCombinedRect.Bottom();

        SetMouseRect(aCombinedRect);

        // paint the focus rect, if necessary
        if (HasFocus())
            ShowFocus(aTextRect);
    }

    Size RubyRadioButton::GetOptimalSize() const
    {
        vcl::Font aSmallerFont( GetFont() );
        aSmallerFont.SetHeight( static_cast<long>( 0.8 * aSmallerFont.GetHeight() ) );
        Rectangle rect( Point(), Size( SAL_MAX_INT32, SAL_MAX_INT32 ) );

        Size aPrimarySize = GetTextRect( rect, m_aRubyText.getPrimaryText() ).GetSize();
        Size aSecondarySize;
        {
            FontSwitch aFontRestore( const_cast<RubyRadioButton&>(*this), aSmallerFont );
            aSecondarySize = GetTextRect( rect, m_aRubyText.getSecondaryText() ).GetSize();
        }

        Size minimumSize =  CalcMinimumSize();
        minimumSize.Height() = aPrimarySize.Height() + aSecondarySize.Height() + 5;
        minimumSize.Width() = aPrimarySize.Width() + aSecondarySize.Width() + 5;
        return minimumSize;
    }

    VCL_BUILDER_FACTORY_ARGS(RubyRadioButton, WB_LEFT|WB_VCENTER)

    SuggestionSet::SuggestionSet( vcl::Window* pParent )
                    : ValueSet( pParent, pParent->GetStyle() | WB_BORDER )

    {
    }

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

    void SuggestionSet::dispose()
    {
        ClearSet();
        ValueSet::dispose();
    }

    void SuggestionSet::UserDraw( const UserDrawEvent& rUDEvt )
    {
        vcl::RenderContext* pDev = rUDEvt.GetRenderContext();
        Rectangle aRect = rUDEvt.GetRect();
        sal_uInt16  nItemId = rUDEvt.GetItemId();

        OUString sText = *static_cast< OUString* >( GetItemData( nItemId ) );
        pDev->DrawText( aRect, sText, DrawTextFlags::Center | DrawTextFlags::VCenter );
    }

    void SuggestionSet::ClearSet()
    {
        sal_uInt16 i, nCount = GetItemCount();
        for ( i = 0; i < nCount; ++i )
            delete static_cast< OUString* >( GetItemData(i) );
        Clear();
    }

    SuggestionDisplay::SuggestionDisplay( vcl::Window* pParent, WinBits nBits )
        : Control( pParent, nBits )
        , m_bDisplayListBox( true )
        , m_aValueSet( VclPtr<SuggestionSet>::Create(this) )
        , m_aListBox( VclPtr<ListBox>::Create(this,GetStyle() | WB_BORDER) )
        , m_bInSelectionUpdate( false )
    {
        m_aValueSet->SetSelectHdl( LINK( this, SuggestionDisplay, SelectSuggestionValueSetHdl ) );
        m_aListBox->SetSelectHdl( LINK( this, SuggestionDisplay, SelectSuggestionListBoxHdl ) );

        m_aValueSet->SetLineCount( LINE_CNT );
        m_aValueSet->SetStyle( m_aValueSet->GetStyle() | WB_ITEMBORDER | WB_FLATVALUESET | WB_VSCROLL );
        m_aValueSet->SetBorderStyle( WindowBorderStyle::MONO );
        OUString aOneCharacter("AU");
        long nItemWidth = 2*GetTextWidth( aOneCharacter );
        m_aValueSet->SetItemWidth( nItemWidth );

        Size aSize( approximate_char_width() * 48, GetTextHeight() * 5 );
        m_aValueSet->SetSizePixel( aSize );
        m_aListBox->SetSizePixel( aSize );

        implUpdateDisplay();
    }

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

    void SuggestionDisplay::dispose()
    {
        m_aValueSet.disposeAndClear();
        m_aListBox.disposeAndClear();
        Control::dispose();
    }

    void SuggestionDisplay::implUpdateDisplay()
    {
        bool bShowBox = IsVisible() && m_bDisplayListBox;
        bool bShowSet = IsVisible() && !m_bDisplayListBox;

        m_aListBox->Show( bShowBox );
        m_aValueSet->Show( bShowSet );
    }

    void SuggestionDisplay::StateChanged( StateChangedType nStateChange )
    {
        if( StateChangedType::Visible == nStateChange )
            implUpdateDisplay();
    }

    bool SuggestionDisplay::hasCurrentControl()
    {
        return m_bDisplayListBox || m_aValueSet;
    }

    Control& SuggestionDisplay::implGetCurrentControl()
    {
        if( m_bDisplayListBox )
            return *m_aListBox.get();
        return *m_aValueSet.get();
    }

    void SuggestionDisplay::KeyInput( const KeyEvent& rKEvt )
    {
        implGetCurrentControl().KeyInput( rKEvt );
    }
    void SuggestionDisplay::KeyUp( const KeyEvent& rKEvt )
    {
        implGetCurrentControl().KeyUp( rKEvt );
    }
    void SuggestionDisplay::Activate()
    {
        implGetCurrentControl().Activate();
    }
    void SuggestionDisplay::Deactivate()
    {
        implGetCurrentControl().Deactivate();
    }
    void SuggestionDisplay::GetFocus()
    {
        if (hasCurrentControl())
            implGetCurrentControl().GetFocus();
        else
            Control::LoseFocus();
    }
    void SuggestionDisplay::LoseFocus()
    {
        if (hasCurrentControl())
            implGetCurrentControl().LoseFocus();
        else
            Control::LoseFocus();
    }
    void SuggestionDisplay::Command( const CommandEvent& rCEvt )
    {
        implGetCurrentControl().Command( rCEvt );
    }

    void SuggestionDisplay::DisplayListBox( bool bDisplayListBox )
    {
        if( m_bDisplayListBox != bDisplayListBox )
        {
            Control& rOldControl = implGetCurrentControl();
            bool bHasFocus = rOldControl.HasFocus();

            m_bDisplayListBox = bDisplayListBox;

            if( bHasFocus )
            {
                Control& rNewControl = implGetCurrentControl();
                rNewControl.GrabFocus();
            }

            implUpdateDisplay();
        }
    }

    IMPL_LINK_TYPED( SuggestionDisplay, SelectSuggestionValueSetHdl, ValueSet*, pControl, void )
    {
        SelectSuggestionHdl(pControl);
    }
    IMPL_LINK_TYPED( SuggestionDisplay, SelectSuggestionListBoxHdl, ListBox&, rControl, void )
    {
        SelectSuggestionHdl(&rControl);
    }
    void SuggestionDisplay::SelectSuggestionHdl( Control* pControl )
    {
        if( m_bInSelectionUpdate )
            return;

        m_bInSelectionUpdate = true;
        if( pControl == m_aListBox.get() )
        {
            sal_uInt16 nPos = m_aListBox->GetSelectEntryPos();
            m_aValueSet->SelectItem( nPos+1 ); //itemid == pos+1 (id 0 has special meaning)
        }
        else
        {
            sal_uInt16 nPos = m_aValueSet->GetSelectItemId()-1; //itemid == pos+1 (id 0 has special meaning)
            m_aListBox->SelectEntryPos( nPos );
        }
        m_bInSelectionUpdate = false;
        m_aSelectLink.Call( *this );
    }

    void SuggestionDisplay::SetSelectHdl( const Link<SuggestionDisplay&,void>& rLink )
    {
        m_aSelectLink = rLink;
    }
    void SuggestionDisplay::Clear()
    {
        m_aListBox->Clear();
        m_aValueSet->Clear();
    }
    void SuggestionDisplay::InsertEntry( const OUString& rStr )
    {
        sal_uInt16 nItemId = m_aListBox->InsertEntry( rStr ) + 1; //itemid == pos+1 (id 0 has special meaning)
        m_aValueSet->InsertItem( nItemId );
        OUString* pItemData = new OUString( rStr );
        m_aValueSet->SetItemData( nItemId, pItemData );
    }
    void SuggestionDisplay::SelectEntryPos( sal_uInt16 nPos )
    {
        m_aListBox->SelectEntryPos( nPos );
        m_aValueSet->SelectItem( nPos+1 ); //itemid == pos+1 (id 0 has special meaning)
    }
    sal_uInt16 SuggestionDisplay::GetEntryCount() const
    {
        return m_aListBox->GetEntryCount();
    }
    OUString SuggestionDisplay::GetEntry( sal_uInt16 nPos ) const
    {
        return m_aListBox->GetEntry( nPos );
    }
    OUString SuggestionDisplay::GetSelectEntry() const
    {
        return m_aListBox->GetSelectEntry();
    }
    void SuggestionDisplay::SetHelpIds()
    {
        this->SetHelpId( HID_HANGULDLG_SUGGESTIONS );
        m_aValueSet->SetHelpId( HID_HANGULDLG_SUGGESTIONS_GRID );
        m_aListBox->SetHelpId( HID_HANGULDLG_SUGGESTIONS_LIST );
    }

    VCL_BUILDER_FACTORY_ARGS( SuggestionDisplay, WB_ITEMBORDER | WB_FLATVALUESET | WB_VSCROLL );

    HangulHanjaConversionDialog::HangulHanjaConversionDialog( vcl::Window* _pParent, HHC::ConversionDirection _ePrimaryDirection )
        :ModalDialog( _pParent, "HangulHanjaConversionDialog", "cui/ui/hangulhanjaconversiondialog.ui" )
        ,m_pIgnoreNonPrimary( nullptr )
        ,m_bDocumentMode( true )
    {
        get( m_pFind, "find" );
        get( m_pIgnore, "ignore" );
        get( m_pSuggestions, "suggestions" );
        get( m_pSimpleConversion, "simpleconversion" );
        get( m_pHangulBracketed, "hangulbracket" );
        get( m_pHanjaBracketed, "hanjabracket" );
        get( m_pHangulOnly, "hangulonly" );
        get( m_pHanjaOnly, "hanjaonly" );
        get( m_pReplaceByChar, "replacebychar" );
        get( m_pOptions, "options" );
        get( m_pIgnore, "ignore" );
        get( m_pIgnoreAll, "ignoreall" );
        get( m_pReplace, "replace" );
        get( m_pReplaceAll, "replaceall" );
        get( m_pWordInput, "wordinput" );
        get( m_pOriginalWord, "originalword" );
        get( m_pHanjaAbove, "hanja_above" );
        get( m_pHanjaBelow, "hanja_below" );
        get( m_pHangulAbove, "hangul_above" );
        get( m_pHangulBelow, "hangul_below" );

        m_pSuggestions->set_height_request( m_pSuggestions->GetTextHeight() * 5 );
        m_pSuggestions->set_width_request( m_pSuggestions->approximate_char_width() * 48 );

        const OUString sHangul(CUI_RESSTR(RID_SVXSTR_HANGUL));
        const OUString sHanja(CUI_RESSTR(RID_SVXSTR_HANJA));
        m_pHanjaAbove->init( sHangul, sHanja, PseudoRubyText::eAbove );
        m_pHanjaBelow->init( sHangul, sHanja, PseudoRubyText::eBelow );
        m_pHangulAbove->init( sHanja, sHangul, PseudoRubyText::eAbove );
        m_pHangulBelow->init( sHanja, sHangul, PseudoRubyText::eBelow );

        m_pWordInput->SetModifyHdl( LINK( this,  HangulHanjaConversionDialog, OnSuggestionModified ) );
        m_pSuggestions->SetSelectHdl( LINK( this,  HangulHanjaConversionDialog, OnSuggestionSelected ) );
        m_pReplaceByChar->SetClickHdl( LINK( this, HangulHanjaConversionDialog, ClickByCharacterHdl ) );
        m_pHangulOnly->SetClickHdl( LINK( this,  HangulHanjaConversionDialog, OnConversionDirectionClicked ) );
        m_pHanjaOnly->SetClickHdl(  LINK( this,  HangulHanjaConversionDialog, OnConversionDirectionClicked ) );
        m_pOptions->SetClickHdl( LINK( this, HangulHanjaConversionDialog, OnOption ) );

        if ( editeng::HangulHanjaConversion::eHangulToHanja == _ePrimaryDirection )
        {
            m_pIgnoreNonPrimary = m_pHangulOnly;
        }
        else
        {
            m_pIgnoreNonPrimary = m_pHanjaOnly;
        }

        // initial focus
        FocusSuggestion( );

        // initial control values
        m_pSimpleConversion->Check();

        m_pSuggestions->SetHelpIds();
    }

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

    void HangulHanjaConversionDialog::dispose()
    {
        m_pFind.clear();
        m_pIgnore.clear();
        m_pIgnoreAll.clear();
        m_pReplace.clear();
        m_pReplaceAll.clear();
        m_pOptions.clear();
        m_pSuggestions.clear();
        m_pSimpleConversion.clear();
        m_pHangulBracketed.clear();
        m_pHanjaBracketed.clear();
        m_pWordInput.clear();
        m_pOriginalWord.clear();
        m_pHanjaAbove.clear();
        m_pHanjaBelow.clear();
        m_pHangulAbove.clear();
        m_pHangulBelow.clear();
        m_pHangulOnly.clear();
        m_pHanjaOnly.clear();
        m_pReplaceByChar.clear();
        m_pIgnoreNonPrimary.clear();
        ModalDialog::dispose();
    }

    void HangulHanjaConversionDialog::FillSuggestions( const css::uno::Sequence< OUString >& _rSuggestions )
    {
        m_pSuggestions->Clear();
        const OUString* pSuggestions = _rSuggestions.getConstArray();
        const OUString* pSuggestionsEnd = _rSuggestions.getConstArray() + _rSuggestions.getLength();
        while ( pSuggestions != pSuggestionsEnd )
            m_pSuggestions->InsertEntry( *pSuggestions++ );

        // select the first suggestion, and fill in the suggestion edit field
        OUString sFirstSuggestion;
        if ( m_pSuggestions->GetEntryCount() )
        {
            sFirstSuggestion = m_pSuggestions->GetEntry( 0 );
            m_pSuggestions->SelectEntryPos( 0 );
        }
        m_pWordInput->SetText( sFirstSuggestion );
        m_pWordInput->SaveValue();
        OnSuggestionModified( *m_pWordInput );
    }


    void HangulHanjaConversionDialog::SetOptionsChangedHdl( const Link<LinkParamNone*,void>& _rHdl )
    {
        m_aOptionsChangedLink = _rHdl;
    }


    void HangulHanjaConversionDialog::SetIgnoreHdl( const Link<Button*,void>& _rHdl )
    {
        m_pIgnore->SetClickHdl( _rHdl );
    }


    void HangulHanjaConversionDialog::SetIgnoreAllHdl( const Link<Button*,void>& _rHdl )
    {
        m_pIgnoreAll->SetClickHdl( _rHdl );
    }


    void HangulHanjaConversionDialog::SetChangeHdl( const Link<Button*,void>& _rHdl )
    {
        m_pReplace->SetClickHdl( _rHdl );
    }


    void HangulHanjaConversionDialog::SetChangeAllHdl( const Link<Button*,void>& _rHdl )
    {
        m_pReplaceAll->SetClickHdl( _rHdl );
    }


    void HangulHanjaConversionDialog::SetFindHdl( const Link<Button*,void>& _rHdl )
    {
        m_pFind->SetClickHdl( _rHdl );
    }


    void HangulHanjaConversionDialog::SetConversionFormatChangedHdl( const Link<Button*,void>& _rHdl )
    {
        m_pSimpleConversion->SetClickHdl( _rHdl );
        m_pHangulBracketed->SetClickHdl( _rHdl );
        m_pHanjaBracketed->SetClickHdl( _rHdl );
        m_pHanjaAbove->SetClickHdl( _rHdl );
        m_pHanjaBelow->SetClickHdl( _rHdl );
        m_pHangulAbove->SetClickHdl( _rHdl );
        m_pHangulBelow->SetClickHdl( _rHdl );
    }


    void HangulHanjaConversionDialog::SetClickByCharacterHdl( const Link<CheckBox*,void>& _rHdl )
    {
        m_aClickByCharacterLink = _rHdl;
    }


    IMPL_LINK_NOARG_TYPED( HangulHanjaConversionDialog, OnSuggestionSelected, SuggestionDisplay&, void )
    {
        m_pWordInput->SetText( m_pSuggestions->GetSelectEntry() );
        OnSuggestionModified( *m_pWordInput );
    }


    IMPL_LINK_NOARG_TYPED( HangulHanjaConversionDialog, OnSuggestionModified, Edit&, void )
    {
        m_pFind->Enable( m_pWordInput->IsValueChangedFromSaved() );

        bool bSameLen = m_pWordInput->GetText().getLength() == m_pOriginalWord->GetText().getLength();
        m_pReplace->Enable( m_bDocumentMode && bSameLen );
        m_pReplaceAll->Enable( m_bDocumentMode && bSameLen );
    }


    IMPL_LINK_TYPED( HangulHanjaConversionDialog, ClickByCharacterHdl, Button*, pBox, void )
    {
        m_aClickByCharacterLink.Call( static_cast<CheckBox*>(pBox) );

        bool bByCharacter = static_cast<CheckBox*>(pBox)->IsChecked();
        m_pSuggestions->DisplayListBox( !bByCharacter );
    }


    IMPL_LINK_TYPED( HangulHanjaConversionDialog, OnConversionDirectionClicked, Button *, pBox, void )
    {
        CheckBox *pOtherBox = nullptr;
        if ( pBox == m_pHangulOnly )
            pOtherBox = m_pHanjaOnly;
        else if ( pBox == m_pHanjaOnly )
            pOtherBox = m_pHangulOnly;
        if ( pBox && pOtherBox )
        {
            bool bBoxChecked = static_cast<CheckBox*>(pBox)->IsChecked();
            if ( bBoxChecked )
                pOtherBox->Check( false );
            pOtherBox->Enable( !bBoxChecked );
        }
    }

    IMPL_LINK_NOARG_TYPED( HangulHanjaConversionDialog, OnOption, Button*, void )
    {
        ScopedVclPtrInstance< HangulHanjaOptionsDialog > aOptDlg(this);
        aOptDlg->Execute();
        m_aOptionsChangedLink.Call( nullptr );
    }


    OUString HangulHanjaConversionDialog::GetCurrentString( ) const
    {
        return m_pOriginalWord->GetText( );
    }


    void HangulHanjaConversionDialog::FocusSuggestion( )
    {
        m_pWordInput->GrabFocus();
    }


    namespace
    {
        void lcl_modifyWindowStyle( vcl::Window* _pWin, WinBits _nSet, WinBits _nReset )
        {
            DBG_ASSERT( 0 == ( _nSet & _nReset ), "lcl_modifyWindowStyle: set _and_ reset the same bit?" );
            if ( _pWin )
                _pWin->SetStyle( ( _pWin->GetStyle() | _nSet ) & ~_nReset );
        }
    }


    void HangulHanjaConversionDialog::SetCurrentString( const OUString& _rNewString,
        const Sequence< OUString >& _rSuggestions, bool _bOriginatesFromDocument )
    {
        m_pOriginalWord->SetText( _rNewString );

        bool bOldDocumentMode = m_bDocumentMode;
        m_bDocumentMode = _bOriginatesFromDocument; // before FillSuggestions!
        FillSuggestions( _rSuggestions );

        m_pIgnoreAll->Enable( m_bDocumentMode );

        // switch the def button depending if we're working for document text
        if ( bOldDocumentMode != m_bDocumentMode )
        {
            vcl::Window* pOldDefButton = nullptr;
            vcl::Window* pNewDefButton = nullptr;
            if ( m_bDocumentMode )
            {
                pOldDefButton = m_pFind;
                pNewDefButton = m_pReplace;
            }
            else
            {
                pOldDefButton = m_pReplace;
                pNewDefButton = m_pFind;
            }

            DBG_ASSERT( WB_DEFBUTTON == ( pOldDefButton->GetStyle( ) & WB_DEFBUTTON ),
                "HangulHanjaConversionDialog::SetCurrentString: wrong previous default button (1)!" );
            DBG_ASSERT( 0 == ( pNewDefButton->GetStyle( ) & WB_DEFBUTTON ),
                "HangulHanjaConversionDialog::SetCurrentString: wrong previous default button (2)!" );

            lcl_modifyWindowStyle( pOldDefButton, 0, WB_DEFBUTTON );
            lcl_modifyWindowStyle( pNewDefButton, WB_DEFBUTTON, 0 );

            // give the focus to the new def button temporarily - VCL is somewhat peculiar
            // in recognizing a new default button
            VclPtr<vcl::Window> xSaveFocusId = Window::SaveFocus();
            pNewDefButton->GrabFocus();
            Window::EndSaveFocus( xSaveFocusId );
        }
    }


    OUString HangulHanjaConversionDialog::GetCurrentSuggestion( ) const
    {
        return m_pWordInput->GetText();
    }


    void HangulHanjaConversionDialog::SetByCharacter( bool _bByCharacter )
    {
        m_pReplaceByChar->Check( _bByCharacter );
        m_pSuggestions->DisplayListBox( !_bByCharacter );
    }


    void HangulHanjaConversionDialog::SetConversionDirectionState(
            bool _bTryBothDirections,
            HHC::ConversionDirection _ePrimaryConversionDirection )
    {
        // default state: try both direction
        m_pHangulOnly->Check( false );
        m_pHangulOnly->Enable();
        m_pHanjaOnly->Check( false );
        m_pHanjaOnly->Enable();

        if (!_bTryBothDirections)
        {
            CheckBox *pBox = _ePrimaryConversionDirection == HHC::eHangulToHanja?
                                    m_pHangulOnly : m_pHanjaOnly;
            pBox->Check();
            OnConversionDirectionClicked( pBox );
        }
    }


    bool HangulHanjaConversionDialog::GetUseBothDirections( ) const
    {
        return !m_pHangulOnly->IsChecked() && !m_pHanjaOnly->IsChecked();
    }


    HHC::ConversionDirection HangulHanjaConversionDialog::GetDirection(
            HHC::ConversionDirection eDefaultDirection ) const
    {
        HHC::ConversionDirection eDirection = eDefaultDirection;
        if ( m_pHangulOnly->IsChecked() && !m_pHanjaOnly->IsChecked() )
            eDirection = HHC::eHangulToHanja;
        else if ( !m_pHangulOnly->IsChecked() && m_pHanjaOnly->IsChecked() )
            eDirection = HHC::eHanjaToHangul;
        return eDirection;
    }


    void HangulHanjaConversionDialog::SetConversionFormat( HHC::ConversionFormat _eType )
    {
        switch ( _eType )
        {
            case HHC::eSimpleConversion: m_pSimpleConversion->Check(); break;
            case HHC::eHangulBracketed: m_pHangulBracketed->Check(); break;
            case HHC::eHanjaBracketed:  m_pHanjaBracketed->Check(); break;
            case HHC::eRubyHanjaAbove:  m_pHanjaAbove->Check(); break;
            case HHC::eRubyHanjaBelow:  m_pHanjaBelow->Check(); break;
            case HHC::eRubyHangulAbove: m_pHangulAbove->Check(); break;
            case HHC::eRubyHangulBelow: m_pHangulBelow->Check(); break;
        default:
            OSL_FAIL( "HangulHanjaConversionDialog::SetConversionFormat: unknown type!" );
        }
    }


    HHC::ConversionFormat HangulHanjaConversionDialog::GetConversionFormat( ) const
    {
        if ( m_pSimpleConversion->IsChecked() )
            return HHC::eSimpleConversion;
        if ( m_pHangulBracketed->IsChecked() )
            return HHC::eHangulBracketed;
        if ( m_pHanjaBracketed->IsChecked() )
            return HHC::eHanjaBracketed;
        if ( m_pHanjaAbove->IsChecked() )
            return HHC::eRubyHanjaAbove;
        if ( m_pHanjaBelow->IsChecked() )
            return HHC::eRubyHanjaBelow;
        if ( m_pHangulAbove->IsChecked() )
            return HHC::eRubyHangulAbove;
        if ( m_pHangulBelow->IsChecked() )
            return HHC::eRubyHangulBelow;

        OSL_FAIL( "HangulHanjaConversionDialog::GetConversionFormat: no radio checked?" );
        return HHC::eSimpleConversion;
    }


    void HangulHanjaConversionDialog::EnableRubySupport( bool bVal )
    {
        m_pHanjaAbove->Enable( bVal );
        m_pHanjaBelow->Enable( bVal );
        m_pHangulAbove->Enable( bVal );
        m_pHangulBelow->Enable( bVal );
    }


    void HangulHanjaOptionsDialog::Init()
    {
        if( !m_xConversionDictionaryList.is() )
        {
            m_xConversionDictionaryList = ConversionDictionaryList::create( ::comphelper::getProcessComponentContext() );
        }

        m_aDictList.clear();
        m_pDictsLB->Clear();

        Reference< XNameContainer > xNameCont = m_xConversionDictionaryList->getDictionaryContainer();
        if( xNameCont.is() )
        {
            Sequence< OUString >     aDictNames( xNameCont->getElementNames() );

            const OUString*          pDic = aDictNames.getConstArray();
            sal_Int32                       nCount = aDictNames.getLength();

            sal_Int32                       i;
            for( i = 0 ; i < nCount ; ++i )
            {
                Any                                 aAny( xNameCont->getByName( pDic[ i ] ) );
                Reference< XConversionDictionary >  xDic;
                if( ( aAny >>= xDic ) && xDic.is() )
                {
                    if( LANGUAGE_KOREAN == LanguageTag( xDic->getLocale() ).getLanguageType() )
                    {
                        m_aDictList.push_back( xDic );
                        AddDict( xDic->getName(), xDic->isActive() );
                    }
                }
            }
        }
    }

    IMPL_LINK_NOARG_TYPED(HangulHanjaOptionsDialog, OkHdl, Button*, void)
    {
        sal_uInt32              nCnt = m_aDictList.size();
        sal_uInt32              n = 0;
        sal_uInt32              nActiveDics = 0;
        Sequence< OUString >    aActiveDics;

        aActiveDics.realloc( nCnt );
        OUString*               pActActiveDic = aActiveDics.getArray();

        while( nCnt )
        {
            Reference< XConversionDictionary >  xDict = m_aDictList[ n ];
            SvTreeListEntry*                        pEntry = m_pDictsLB->SvTreeListBox::GetEntry( n );

            DBG_ASSERT( xDict.is(), "-HangulHanjaOptionsDialog::OkHdl(): someone is evaporated..." );
            DBG_ASSERT( pEntry, "-HangulHanjaOptionsDialog::OkHdl(): no one there in list?" );

            bool    bActive = m_pDictsLB->GetCheckButtonState( pEntry ) == SV_BUTTON_CHECKED;
            xDict->setActive( bActive );
            Reference< util::XFlushable > xFlush( xDict, uno::UNO_QUERY );
            if( xFlush.is() )
                xFlush->flush();

            if( bActive )
            {
                pActActiveDic[ nActiveDics ] = xDict->getName();
                ++nActiveDics;
            }

            ++n;
            --nCnt;
        }

        // save configuration
        aActiveDics.realloc( nActiveDics );
        Any             aTmp;
        SvtLinguConfig  aLngCfg;
        aTmp <<= aActiveDics;
        aLngCfg.SetProperty( UPH_ACTIVE_CONVERSION_DICTIONARIES, aTmp );

        aTmp <<= bool( m_pIgnorepostCB->IsChecked() );
        aLngCfg.SetProperty( UPH_IS_IGNORE_POST_POSITIONAL_WORD, aTmp );

        aTmp <<= bool( m_pShowrecentlyfirstCB->IsChecked() );
        aLngCfg.SetProperty( UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST, aTmp );

        aTmp <<= bool( m_pAutoreplaceuniqueCB->IsChecked() );
        aLngCfg.SetProperty( UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES, aTmp );

        EndDialog( RET_OK );
    }

    IMPL_LINK_NOARG_TYPED(HangulHanjaOptionsDialog, DictsLB_SelectHdl, SvTreeListBox*, void)
    {
        bool bSel = m_pDictsLB->FirstSelected() != nullptr;

        m_pEditPB->Enable(bSel);
        m_pDeletePB->Enable(bSel);
    }

    IMPL_LINK_NOARG_TYPED(HangulHanjaOptionsDialog, NewDictHdl, Button*, void)
    {
        OUString                    aName;
        ScopedVclPtrInstance< HangulHanjaNewDictDialog > aNewDlg(this);
        aNewDlg->Execute();
        if( aNewDlg->GetName( aName ) )
        {
            if( m_xConversionDictionaryList.is() )
            {
                try
                {
                    Reference< XConversionDictionary >  xDic =
                        m_xConversionDictionaryList->addNewDictionary( aName, LanguageTag::convertToLocale( LANGUAGE_KOREAN ), ConversionDictionaryType::HANGUL_HANJA );

                    if( xDic.is() )
                    {
                        //adapt local caches:
                        m_aDictList.push_back( xDic );
                        AddDict( xDic->getName(), xDic->isActive() );
                    }
                }
                catch( const ElementExistException& )
                {
                }
                catch( const NoSupportException& )
                {
                }
            }
        }
    }

    IMPL_LINK_NOARG_TYPED(HangulHanjaOptionsDialog, EditDictHdl, Button*, void)
    {
        SvTreeListEntry*    pEntry = m_pDictsLB->FirstSelected();
        DBG_ASSERT( pEntry, "+HangulHanjaEditDictDialog::EditDictHdl(): call of edit should not be possible with no selection!" );
        if( pEntry )
        {
            ScopedVclPtrInstance< HangulHanjaEditDictDialog > aEdDlg(this, m_aDictList, m_pDictsLB->GetSelectEntryPos());
            aEdDlg->Execute();
        }
    }

    IMPL_LINK_NOARG_TYPED(HangulHanjaOptionsDialog, DeleteDictHdl, Button*, void)
    {
        sal_uLong nSelPos = m_pDictsLB->GetSelectEntryPos();
        if( nSelPos != TREELIST_ENTRY_NOTFOUND )
        {
            Reference< XConversionDictionary >  xDic( m_aDictList[ nSelPos ] );
            if( m_xConversionDictionaryList.is() && xDic.is() )
            {
                Reference< XNameContainer >     xNameCont = m_xConversionDictionaryList->getDictionaryContainer();
                if( xNameCont.is() )
                {
                    try
                    {
                        xNameCont->removeByName( xDic->getName() );

                        //adapt local caches:
                        m_aDictList.erase(m_aDictList.begin()+nSelPos );
                        m_pDictsLB->RemoveEntry(nSelPos);
                    }
                    catch( const ElementExistException& )
                    {
                    }
                    catch( const NoSupportException& )
                    {
                    }
                }
            }
        }
    }

    HangulHanjaOptionsDialog::HangulHanjaOptionsDialog(vcl::Window* _pParent)
        : ModalDialog( _pParent, "HangulHanjaOptDialog",
            "cui/ui/hangulhanjaoptdialog.ui" )
        , m_pCheckButtonData(nullptr)
        , m_xConversionDictionaryList(nullptr)
    {
        get(m_pDictsLB, "dicts");
        get(m_pIgnorepostCB, "ignorepost");
        get(m_pShowrecentlyfirstCB, "showrecentfirst");
        get(m_pAutoreplaceuniqueCB, "autoreplaceunique");
        get(m_pNewPB, "new");
        get(m_pEditPB, "edit");
        get(m_pDeletePB, "delete");
        get(m_pOkPB, "ok");

        m_pDictsLB->set_height_request(m_pDictsLB->GetTextHeight() * 5);
        m_pDictsLB->set_width_request(m_pDictsLB->approximate_char_width() * 32);
        m_pDictsLB->SetStyle( m_pDictsLB->GetStyle() | WB_CLIPCHILDREN | WB_HSCROLL | WB_FORCE_MAKEVISIBLE );
        m_pDictsLB->SetSelectionMode( SINGLE_SELECTION );
        m_pDictsLB->SetHighlightRange();
        m_pDictsLB->SetSelectHdl( LINK( this, HangulHanjaOptionsDialog, DictsLB_SelectHdl ) );
        m_pDictsLB->SetDeselectHdl( LINK( this, HangulHanjaOptionsDialog, DictsLB_SelectHdl ) );

        m_pOkPB->SetClickHdl( LINK( this, HangulHanjaOptionsDialog, OkHdl ) );
        m_pNewPB->SetClickHdl( LINK( this, HangulHanjaOptionsDialog, NewDictHdl ) );
        m_pEditPB->SetClickHdl( LINK( this, HangulHanjaOptionsDialog, EditDictHdl ) );
        m_pDeletePB->SetClickHdl( LINK( this, HangulHanjaOptionsDialog, DeleteDictHdl ) );

        SvtLinguConfig  aLngCfg;
        Any             aTmp;
        bool            bVal = bool();
        aTmp = aLngCfg.GetProperty( UPH_IS_IGNORE_POST_POSITIONAL_WORD );
        if( aTmp >>= bVal )
            m_pIgnorepostCB->Check( bVal );

        aTmp = aLngCfg.GetProperty( UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST );
        if( aTmp >>= bVal )
            m_pShowrecentlyfirstCB->Check( bVal );

        aTmp = aLngCfg.GetProperty( UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES );
        if( aTmp >>= bVal )
            m_pAutoreplaceuniqueCB->Check( bVal );

        Init();
    }

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

    void HangulHanjaOptionsDialog::dispose()
    {
        if (m_pDictsLB)
        {
            SvTreeListEntry* pEntry = m_pDictsLB->First();
            while( pEntry )
            {
                delete static_cast<OUString const *>(pEntry->GetUserData());
                pEntry->SetUserData( nullptr );
                pEntry = m_pDictsLB->Next( pEntry );
            }
        }

        delete m_pCheckButtonData;
        m_pCheckButtonData = nullptr;

        m_pDictsLB.clear();
        m_pIgnorepostCB.clear();
        m_pShowrecentlyfirstCB.clear();
        m_pAutoreplaceuniqueCB.clear();
        m_pNewPB.clear();
        m_pEditPB.clear();
        m_pDeletePB.clear();
        m_pOkPB.clear();
        ModalDialog::dispose();
    }

    void HangulHanjaOptionsDialog::AddDict( const OUString& _rName, bool _bChecked )
    {
        SvTreeListEntry*    pEntry = m_pDictsLB->SvTreeListBox::InsertEntry( _rName );
        m_pDictsLB->SetCheckButtonState( pEntry, _bChecked? SV_BUTTON_CHECKED : SV_BUTTON_UNCHECKED );
        pEntry->SetUserData( new OUString( _rName ) );
    }

    IMPL_LINK_NOARG_TYPED(HangulHanjaNewDictDialog, OKHdl, Button*, void)
    {
        OUString  aName(comphelper::string::stripEnd(m_pDictNameED->GetText(), ' '));

        m_bEntered = !aName.isEmpty();
        if( m_bEntered )
            m_pDictNameED->SetText( aName );     // do this in case of trailing chars have been deleted

        EndDialog( RET_OK );
    }

    IMPL_LINK_NOARG_TYPED(HangulHanjaNewDictDialog, ModifyHdl, Edit&, void)
    {
        OUString aName(comphelper::string::stripEnd(m_pDictNameED->GetText(), ' '));

        m_pOkBtn->Enable( !aName.isEmpty() );
    }

    HangulHanjaNewDictDialog::HangulHanjaNewDictDialog(vcl::Window* pParent)
        : ModalDialog(pParent, "HangulHanjaAddDialog", "cui/ui/hangulhanjaadddialog.ui")
        , m_bEntered(false)
    {
        get(m_pOkBtn, "ok");
        get(m_pDictNameED, "entry");

        m_pOkBtn->SetClickHdl( LINK( this, HangulHanjaNewDictDialog, OKHdl ) );
        m_pDictNameED->SetModifyHdl( LINK( this, HangulHanjaNewDictDialog, ModifyHdl ) );
    }

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

    void HangulHanjaNewDictDialog::dispose()
    {
        m_pDictNameED.clear();
        m_pOkBtn.clear();
        ModalDialog::dispose();
    }

    bool HangulHanjaNewDictDialog::GetName( OUString& _rRetName ) const
    {
        if( m_bEntered )
            _rRetName = comphelper::string::stripEnd(m_pDictNameED->GetText(), ' ');

        return m_bEntered;
    }

    class SuggestionList
    {
    private:
    protected:
        std::vector<OUString*> m_vElements;
        sal_uInt16          m_nNumOfEntries;
        // index of the internal iterator, used for First() and Next() methods
        sal_uInt16          m_nAct;

        const OUString*       _Next();
    public:
                            SuggestionList();
                            ~SuggestionList();

        void                Set( const OUString& _rElement, sal_uInt16 _nNumOfElement );
        bool                Reset( sal_uInt16 _nNumOfElement );
        const OUString*     Get( sal_uInt16 _nNumOfElement ) const;
        void                Clear();

        const OUString*     First();
        const OUString*     Next();

        inline sal_uInt16   GetCount() const { return m_nNumOfEntries; }
    };

    SuggestionList::SuggestionList() :
        m_vElements(MAXNUM_SUGGESTIONS, static_cast<OUString*>(nullptr))
    {
        m_nAct = m_nNumOfEntries = 0;
    }

    SuggestionList::~SuggestionList()
    {
        Clear();
    }

    void SuggestionList::Set( const OUString& _rElement, sal_uInt16 _nNumOfElement )
    {
        bool    bRet = _nNumOfElement < m_vElements.size();
        if( bRet )
        {
            if( m_vElements[_nNumOfElement] != nullptr )
                *(m_vElements[_nNumOfElement]) = _rElement;
            else
            {
                m_vElements[_nNumOfElement] = new OUString( _rElement );
                ++m_nNumOfEntries;
            }
        }
    }

    bool SuggestionList::Reset( sal_uInt16 _nNumOfElement )
    {
        bool    bRet = _nNumOfElement < m_vElements.size();
        if( bRet )
        {
            if( m_vElements[_nNumOfElement] != nullptr )
            {
                delete m_vElements[_nNumOfElement];
                m_vElements[_nNumOfElement] = nullptr;
                --m_nNumOfEntries;
            }
        }

        return bRet;
    }

    const OUString* SuggestionList::Get( sal_uInt16 _nNumOfElement ) const
    {
        if( _nNumOfElement < m_vElements.size())
            return m_vElements[_nNumOfElement];
        return nullptr;
    }

    void SuggestionList::Clear()
    {
        if( m_nNumOfEntries )
        {
            for( std::vector<OUString*>::iterator it = m_vElements.begin(); it != m_vElements.end(); ++it )
                if( *it != nullptr )
                {
                    delete *it;
                    *it = nullptr;
                 }

            m_nNumOfEntries = m_nAct = 0;
        }
    }

    const OUString* SuggestionList::_Next()
    {
        const OUString*   pRet = nullptr;
        while( m_nAct < m_vElements.size() && !pRet )
        {
            pRet = m_vElements[ m_nAct ];
            if( !pRet )
                ++m_nAct;
        }

        return pRet;
    }

    const OUString* SuggestionList::First()
    {
        m_nAct = 0;
        return _Next();
    }

    const OUString* SuggestionList::Next()
    {
        const OUString*   pRet;

        if( m_nAct < m_nNumOfEntries )
        {
            ++m_nAct;
            pRet = _Next();
        }
        else
            pRet = nullptr;

        return pRet;
    }


    bool SuggestionEdit::ShouldScroll( bool _bUp ) const
    {
        bool    bRet = false;

        if( _bUp )
        {
            if( !m_pPrev )
                bRet = m_pScrollBar->GetThumbPos() > m_pScrollBar->GetRangeMin();
        }
        else
        {
            if( !m_pNext )
                bRet = m_pScrollBar->GetThumbPos() < ( m_pScrollBar->GetRangeMax() - 4 );
        }

        return bRet;
    }

    void SuggestionEdit::DoJump( bool _bUp )
    {
        const Link<Control&,void>& rLoseFocusHdl = GetLoseFocusHdl();
        rLoseFocusHdl.Call( *this );
        m_pScrollBar->SetThumbPos( m_pScrollBar->GetThumbPos() + ( _bUp? -1 : 1 ) );

        ( static_cast< HangulHanjaEditDictDialog* >( GetParentDialog() ) )->UpdateScrollbar();
    }

    SuggestionEdit::SuggestionEdit( vcl::Window* pParent, WinBits nBits )
        : Edit(pParent, nBits)
        , m_pPrev(nullptr)
        , m_pNext(nullptr)
        , m_pScrollBar(nullptr)
    {
    }

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

    void SuggestionEdit::dispose()
    {
        m_pPrev.clear();
        m_pNext.clear();
        m_pScrollBar.clear();
        Edit::dispose();
    }

    bool SuggestionEdit::PreNotify( NotifyEvent& rNEvt )
    {
        bool bHandled = false;
        if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
        {
            const KeyEvent*     pKEvt = rNEvt.GetKeyEvent();
            const vcl::KeyCode& rKeyCode = pKEvt->GetKeyCode();
            sal_uInt16          nMod = rKeyCode.GetModifier();
            sal_uInt16          nCode = rKeyCode.GetCode();
            if( nCode == KEY_TAB && ( !nMod || KEY_SHIFT == nMod ) )
            {
                bool        bUp = KEY_SHIFT == nMod;
                if( ShouldScroll( bUp ) )
                {
                    DoJump( bUp );
                    SetSelection( Selection( 0, SELECTION_MAX ) );
                        // Tab-travel doesn't really happen, so emulate it by setting a selection manually
                    bHandled = true;
                }
            }
            else if( KEY_UP == nCode || KEY_DOWN == nCode )
            {
                bool        bUp = KEY_UP == nCode;
                if( ShouldScroll( bUp ) )
                {
                    DoJump( bUp );
                    bHandled = true;
                }
                else if( bUp )
                {
                    if( m_pPrev )
                    {
                        m_pPrev->GrabFocus();
                        bHandled = true;
                    }
                }
                else if( m_pNext )
                {
                    m_pNext->GrabFocus();
                    bHandled = true;
                }
            }
        }

        if( !bHandled )
            bHandled = Edit::PreNotify( rNEvt );
        return bHandled;
    }

    void SuggestionEdit::init( ScrollBar* pScrollBar, SuggestionEdit* pPrev, SuggestionEdit* pNext)
    {
        m_pScrollBar = pScrollBar;
        m_pPrev = pPrev;
        m_pNext = pNext;
    }

    VCL_BUILDER_FACTORY_ARGS(SuggestionEdit, WB_LEFT|WB_VCENTER|WB_BORDER)

    namespace
    {
        bool GetConversions(    Reference< XConversionDictionary >  _xDict,
                                const OUString& _rOrg,
                                Sequence< OUString >& _rEntries )
        {
            bool    bRet = false;
            if( _xDict.is() && !_rOrg.isEmpty() )
            {
                try
                {
                    _rEntries = _xDict->getConversions( _rOrg,
                                                        0,
                                                        _rOrg.getLength(),
                                                        ConversionDirection_FROM_LEFT,
                                                        css::i18n::TextConversionOption::NONE );
                    bRet = _rEntries.getLength() > 0;
                }
                catch( const IllegalArgumentException& )
                {
                }
            }

            return bRet;
        }
    }


    IMPL_LINK_NOARG_TYPED( HangulHanjaEditDictDialog, ScrollHdl, ScrollBar*, void )
    {
        UpdateScrollbar();
    }

    IMPL_LINK_NOARG_TYPED( HangulHanjaEditDictDialog, OriginalModifyHdl, Edit&, void )
    {
        m_bModifiedOriginal = true;
        m_aOriginal = comphelper::string::stripEnd( m_aOriginalLB->GetText(), ' ' );

        UpdateSuggestions();
        UpdateButtonStates();
    }

    IMPL_LINK_TYPED( HangulHanjaEditDictDialog, EditModifyHdl1, Edit&, rEdit, void )
    {
        EditModify( &rEdit, 0 );
    }

    IMPL_LINK_TYPED( HangulHanjaEditDictDialog, EditModifyHdl2, Edit&, rEdit, void )
    {
        EditModify( &rEdit, 1 );
    }

    IMPL_LINK_TYPED( HangulHanjaEditDictDialog, EditModifyHdl3, Edit&, rEdit, void )
    {
        EditModify( &rEdit, 2 );
    }

    IMPL_LINK_TYPED( HangulHanjaEditDictDialog, EditModifyHdl4, Edit&, rEdit, void )
    {
        EditModify( &rEdit, 3 );
    }

    IMPL_LINK_NOARG_TYPED( HangulHanjaEditDictDialog, BookLBSelectHdl, ListBox&, void )
    {
        InitEditDictDialog( m_aBookLB->GetSelectEntryPos() );
    }

    IMPL_LINK_NOARG_TYPED( HangulHanjaEditDictDialog, NewPBPushHdl, Button*, void )
    {
        DBG_ASSERT( m_pSuggestions, "-HangulHanjaEditDictDialog::NewPBPushHdl(): no suggestions... search in hell..." );
        Reference< XConversionDictionary >  xDict = m_rDictList[ m_nCurrentDict ];
        if( xDict.is() && m_pSuggestions )
        {
            //delete old entry
            bool bRemovedSomething = DeleteEntryFromDictionary( m_aOriginal, xDict );

            OUString                aLeft( m_aOriginal );
            const OUString*           pRight = m_pSuggestions->First();
            bool bAddedSomething = false;
            while( pRight )
            {
                try
                {
                    //add new entry
                    xDict->addEntry( aLeft, *pRight );
                    bAddedSomething = true;
                }
                catch( const IllegalArgumentException& )
                {
                }
                catch( const ElementExistException& )
                {
                }

                pRight = m_pSuggestions->Next();
            }

            if( bAddedSomething || bRemovedSomething )
                InitEditDictDialog( m_nCurrentDict );
        }
        else
        {
            SAL_INFO( "cui.dialogs", "dictionary faded away..." );
        }
    }

    bool HangulHanjaEditDictDialog::DeleteEntryFromDictionary( const OUString&, const Reference< XConversionDictionary >& xDict  )
    {
        bool bRemovedSomething = false;
        if( xDict.is() )
        {
            OUString                aOrg( m_aOriginal );
            Sequence< OUString >    aEntries;
            GetConversions( xDict, m_aOriginal, aEntries );

            sal_uInt32  n = aEntries.getLength();
            OUString*   pEntry = aEntries.getArray();
            while( n )
            {
                try
                {
                    xDict->removeEntry( aOrg, *pEntry );
                    bRemovedSomething = true;
                }
                catch( const NoSuchElementException& )
                {   // can not be...
                }

                ++pEntry;
                --n;
            }
        }
        return bRemovedSomething;
    }

    IMPL_LINK_NOARG_TYPED( HangulHanjaEditDictDialog, DeletePBPushHdl, Button*, void )
    {
        if( DeleteEntryFromDictionary( m_aOriginal, m_rDictList[ m_nCurrentDict ] ) )
        {
            m_aOriginal.clear();
            m_bModifiedOriginal = true;
            InitEditDictDialog( m_nCurrentDict );
        }
    }

    void HangulHanjaEditDictDialog::InitEditDictDialog( sal_uInt32 _nSelDict )
    {
        if( m_pSuggestions )
            m_pSuggestions->Clear();

        if( m_nCurrentDict != _nSelDict )
        {
            m_nCurrentDict = _nSelDict;
            m_aOriginal.clear();
            m_bModifiedOriginal = true;
        }

        UpdateOriginalLB();

        m_aOriginalLB->SetText( !m_aOriginal.isEmpty() ? m_aOriginal : m_aEditHintText, Selection( 0, SELECTION_MAX ) );
        m_aOriginalLB->GrabFocus();

        UpdateSuggestions();
        UpdateButtonStates();
    }

    void HangulHanjaEditDictDialog::UpdateOriginalLB()
    {
        m_aOriginalLB->Clear();
        Reference< XConversionDictionary >  xDict = m_rDictList[ m_nCurrentDict ];
        if( xDict.is() )
        {
            Sequence< OUString >    aEntries = xDict->getConversionEntries( ConversionDirection_FROM_LEFT );
            sal_uInt32              n = aEntries.getLength();
            OUString*               pEntry = aEntries.getArray();
            while( n )
            {
                m_aOriginalLB->InsertEntry( *pEntry );

                ++pEntry;
                --n;
            }
        }
        else
        {
            SAL_INFO( "cui.dialogs", "dictionary faded away..." );
        }
    }

    void HangulHanjaEditDictDialog::UpdateButtonStates()
    {
        bool bHaveValidOriginalString = !m_aOriginal.isEmpty() && m_aOriginal != m_aEditHintText;
        bool bNew = bHaveValidOriginalString && m_pSuggestions && m_pSuggestions->GetCount() > 0;
        bNew = bNew && ( m_bModifiedSuggestions || m_bModifiedOriginal );

        m_aNewPB->Enable( bNew );
        m_aDeletePB->Enable(!m_bModifiedOriginal && bHaveValidOriginalString);
    }

    void HangulHanjaEditDictDialog::UpdateSuggestions()
    {
        Sequence< OUString > aEntries;
        bool bFound = GetConversions( m_rDictList[ m_nCurrentDict ], m_aOriginal, aEntries );
        if( bFound )
        {
            m_bModifiedOriginal = false;

            if( m_pSuggestions )
                m_pSuggestions->Clear();

            //fill found entries into boxes
            sal_uInt32 nCnt = aEntries.getLength();
            if( nCnt )
            {
                if( !m_pSuggestions )
                    m_pSuggestions = new SuggestionList;

                const OUString* pSugg = aEntries.getConstArray();
                sal_uInt32 n = 0;
                while( nCnt )
                {
                    m_pSuggestions->Set( pSugg[ n ], sal_uInt16( n ) );
                    ++n;
                    --nCnt;
                }
            }
            m_bModifiedSuggestions = false;
        }

        m_aScrollSB->SetThumbPos( 0 );
        UpdateScrollbar();              // will force edits to be filled new
    }

    void HangulHanjaEditDictDialog::SetEditText( Edit& _rEdit, sal_uInt16 _nEntryNum )
    {
        OUString  aStr;
        if( m_pSuggestions )
        {
            const OUString*   p = m_pSuggestions->Get( _nEntryNum );
            if( p )
                aStr = *p;
        }

        _rEdit.SetText( aStr );
    }

    void HangulHanjaEditDictDialog::EditModify( Edit* _pEdit, sal_uInt8 _nEntryOffset )
    {
        m_bModifiedSuggestions = true;

        OUString  aTxt( _pEdit->GetText() );
        sal_uInt16 nEntryNum = m_nTopPos + _nEntryOffset;
        if( aTxt.isEmpty() )
        {
            //reset suggestion
            if( m_pSuggestions )
                m_pSuggestions->Reset( nEntryNum );
        }
        else
        {
            //set suggestion
            if( !m_pSuggestions )
                m_pSuggestions = new SuggestionList;
            m_pSuggestions->Set( aTxt, nEntryNum );
        }

        UpdateButtonStates();
    }

    HangulHanjaEditDictDialog::HangulHanjaEditDictDialog( vcl::Window* _pParent, HHDictList& _rDictList, sal_uInt32 _nSelDict )
        :ModalDialog            ( _pParent, "HangulHanjaEditDictDialog", "cui/ui/hangulhanjaeditdictdialog.ui" )
        ,m_aEditHintText        ( CUI_RESSTR(RID_SVXSTR_EDITHINT) )
        ,m_rDictList            ( _rDictList )
        ,m_nCurrentDict         ( 0xFFFFFFFF )
        ,m_pSuggestions         ( nullptr )
        ,m_nTopPos              ( 0 )
        ,m_bModifiedSuggestions ( false )
        ,m_bModifiedOriginal    ( false )
    {
        get( m_aBookLB, "book" );
        get( m_aOriginalLB, "original" );
        get( m_aNewPB, "new" );
        get( m_aDeletePB, "delete" );
        get( m_aScrollSB, "scrollbar" );
        get( m_aEdit1, "edit1" );
        get( m_aEdit2, "edit2" );
        get( m_aEdit3, "edit3" );
        get( m_aEdit4, "edit4" );

        m_aEdit1->init( m_aScrollSB, nullptr, m_aEdit2 );
        m_aEdit2->init( m_aScrollSB, m_aEdit1, m_aEdit3 );
        m_aEdit3->init( m_aScrollSB, m_aEdit2, m_aEdit4 );
        m_aEdit4->init( m_aScrollSB, m_aEdit3, nullptr );

        m_aOriginalLB->SetModifyHdl( LINK( this, HangulHanjaEditDictDialog, OriginalModifyHdl ) );

        m_aNewPB->SetClickHdl( LINK( this, HangulHanjaEditDictDialog, NewPBPushHdl ) );
        m_aNewPB->Enable( false );

        m_aDeletePB->SetClickHdl( LINK( this, HangulHanjaEditDictDialog, DeletePBPushHdl ) );
        m_aDeletePB->Enable( false );

    #if( MAXNUM_SUGGESTIONS <= 4 )
        #error number of suggestions should not under-run the value of 5
    #endif

        Link<ScrollBar*,void>  aScrLk( LINK( this, HangulHanjaEditDictDialog, ScrollHdl ) );
        m_aScrollSB->SetScrollHdl( aScrLk );
        m_aScrollSB->SetEndScrollHdl( aScrLk );
        m_aScrollSB->SetRangeMin( 0 );
        m_aScrollSB->SetRangeMax( MAXNUM_SUGGESTIONS );
        m_aScrollSB->SetPageSize( 4 );       // because we have 4 edits / page
        m_aScrollSB->SetVisibleSize( 4 );

        m_aEdit1->SetModifyHdl( LINK( this, HangulHanjaEditDictDialog, EditModifyHdl1 ) );
        m_aEdit2->SetModifyHdl( LINK( this, HangulHanjaEditDictDialog, EditModifyHdl2 ) );
        m_aEdit3->SetModifyHdl( LINK( this, HangulHanjaEditDictDialog, EditModifyHdl3 ) );
        m_aEdit4->SetModifyHdl( LINK( this, HangulHanjaEditDictDialog, EditModifyHdl4 ) );

        m_aBookLB->SetSelectHdl( LINK( this, HangulHanjaEditDictDialog, BookLBSelectHdl ) );
        sal_uInt32  nDictCnt = m_rDictList.size();
        for( sal_uInt32 n = 0 ; n < nDictCnt ; ++n )
        {
            Reference< XConversionDictionary >  xDic( m_rDictList[n] );
            OUString aName;
            if( xDic.is() )
                aName = xDic->getName();
            m_aBookLB->InsertEntry( aName );
        }
        m_aBookLB->SelectEntryPos( sal_uInt16( _nSelDict ) );

        InitEditDictDialog( _nSelDict );
    }

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

    void HangulHanjaEditDictDialog::dispose()
    {
        delete m_pSuggestions;
        m_pSuggestions = nullptr;
        m_aBookLB.clear();
        m_aOriginalLB.clear();
        m_aEdit1.clear();
        m_aEdit2.clear();
        m_aEdit3.clear();
        m_aEdit4.clear();
        m_aScrollSB.clear();
        m_aNewPB.clear();
        m_aDeletePB.clear();
        ModalDialog::dispose();
    }

    void HangulHanjaEditDictDialog::UpdateScrollbar()
    {
        sal_uInt16  nPos = sal_uInt16( m_aScrollSB->GetThumbPos() );
        m_nTopPos = nPos;

        SetEditText( *m_aEdit1, nPos++ );
        SetEditText( *m_aEdit2, nPos++ );
        SetEditText( *m_aEdit3, nPos++ );
        SetEditText( *m_aEdit4, nPos );
    }


}


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