//
// candui.cpp
//

#include "private.h"
#include "globals.h"
#include "candui.h"
#include "wcand.h"
#include "immxutil.h"
#include "computil.h"
#include "candutil.h"
#include "candobj.h"
#include "msctfp.h"

//
// default key command definition
//

// key command definition in list style

const CANDUIKEYDATA rgKeyDefList[] =
{
/*
{ flag,								keydata,		command,					paramater }
    */
    { CANDUIKEY_VKEY,					VK_ESCAPE,		CANDUICMD_CANCEL,			0 },
    { CANDUIKEY_VKEY,					VK_RETURN,		CANDUICMD_COMPLETE,			0 },
    { CANDUIKEY_VKEY|CANDUIKEY_SHIFT,	VK_CONVERT,		CANDUICMD_MOVESELPREV,		0 },
    { CANDUIKEY_VKEY,					VK_CONVERT,		CANDUICMD_MOVESELNEXT,		0 },
    { CANDUIKEY_VKEY|CANDUIKEY_SHIFT,	VK_SPACE,		CANDUICMD_MOVESELPREV,		0 },
    { CANDUIKEY_VKEY,					VK_SPACE,		CANDUICMD_MOVESELNEXT,		0 },
    { CANDUIKEY_VKEY,					VK_UP,			CANDUICMD_MOVESELUP,		0 },	// horz only
    { CANDUIKEY_VKEY,					VK_DOWN,		CANDUICMD_MOVESELDOWN,		0 },	// horz only
    { CANDUIKEY_VKEY,					VK_LEFT,		CANDUICMD_MOVESELLEFT,		0 },	// vert only
    { CANDUIKEY_VKEY,					VK_RIGHT,		CANDUICMD_MOVESELRIGHT,		0 },	// vert only
    { CANDUIKEY_VKEY,					VK_PRIOR,		CANDUICMD_MOVESELPREVPG,	0 },
    { CANDUIKEY_VKEY,					VK_NEXT,		CANDUICMD_MOVESELNEXTPG,	0 },
    { CANDUIKEY_VKEY,					VK_HOME,		CANDUICMD_MOVESELFIRST,		0 },
    { CANDUIKEY_VKEY,					VK_END,			CANDUICMD_MOVESELLAST,		0 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'1',			CANDUICMD_SELECTLINE,		1 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'2',			CANDUICMD_SELECTLINE,		2 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'3',			CANDUICMD_SELECTLINE,		3 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'4',			CANDUICMD_SELECTLINE,		4 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'5',			CANDUICMD_SELECTLINE,		5 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'6',			CANDUICMD_SELECTLINE,		6 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'7',			CANDUICMD_SELECTLINE,		7 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'8',			CANDUICMD_SELECTLINE,		8 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'9',			CANDUICMD_SELECTLINE,		9 },
    { CANDUIKEY_VKEY|CANDUIKEY_NOSHIFT|CANDUIKEY_NOCTRL,	L'0',			CANDUICMD_SELECTEXTRACAND,	0 },
    { CANDUIKEY_VKEY,					VK_OEM_MINUS,	CANDUICMD_SELECTRAWDATA,	0 },
    { CANDUIKEY_VKEY,					VK_NUMPAD1,		CANDUICMD_SELECTLINE,		1 },
    { CANDUIKEY_VKEY,					VK_NUMPAD2,		CANDUICMD_SELECTLINE,		2 },
    { CANDUIKEY_VKEY,					VK_NUMPAD3,		CANDUICMD_SELECTLINE,		3 },
    { CANDUIKEY_VKEY,					VK_NUMPAD4,		CANDUICMD_SELECTLINE,		4 },
    { CANDUIKEY_VKEY,					VK_NUMPAD5,		CANDUICMD_SELECTLINE,		5 },
    { CANDUIKEY_VKEY,					VK_NUMPAD6,		CANDUICMD_SELECTLINE,		6 },
    { CANDUIKEY_VKEY,					VK_NUMPAD7,		CANDUICMD_SELECTLINE,		7 },
    { CANDUIKEY_VKEY,					VK_NUMPAD8,		CANDUICMD_SELECTLINE,		8 },
    { CANDUIKEY_VKEY,					VK_NUMPAD9,		CANDUICMD_SELECTLINE,		9 },
    { CANDUIKEY_VKEY,					VK_NUMPAD0,		CANDUICMD_SELECTEXTRACAND,	0 },
    { CANDUIKEY_VKEY,					VK_SUBTRACT,	CANDUICMD_SELECTRAWDATA,	0 },
    { CANDUIKEY_VKEY,					VK_APPS,		CANDUICMD_OPENCANDMENU,		0 },
};


// key command definition in row style

const CANDUIKEYDATA rgKeyDefRow[] =
{
/*
{ flag,								keydata,		command,					paramater }
    */
    { CANDUIKEY_VKEY,					VK_ESCAPE,		CANDUICMD_CANCEL,			0 },
    { CANDUIKEY_VKEY,					VK_RETURN,		CANDUICMD_CANCEL,			0 },
    { CANDUIKEY_VKEY,					VK_SPACE,		CANDUICMD_COMPLETE,			0 },	
    { CANDUIKEY_VKEY,					VK_UP,			CANDUICMD_MOVESELLEFT,		0 },	// horz only
    { CANDUIKEY_VKEY,					VK_DOWN,		CANDUICMD_MOVESELRIGHT,		0 },	// horz only
    { CANDUIKEY_VKEY,					VK_LEFT,		CANDUICMD_MOVESELUP,		0 },	// vert only
    { CANDUIKEY_VKEY,					VK_RIGHT,		CANDUICMD_MOVESELDOWN,		0 },	// vert only
    { CANDUIKEY_VKEY,					VK_PRIOR,		CANDUICMD_MOVESELPREVPG,	0 },
    { CANDUIKEY_VKEY,					VK_NEXT,		CANDUICMD_MOVESELNEXTPG,	0 },
    //	{ CANDUIKEY_CHAR,					L'1',			CANDUICMD_SELECTLINE,		1 },
    //	{ CANDUIKEY_CHAR,					L'2',			CANDUICMD_SELECTLINE,		2 },
    //	{ CANDUIKEY_CHAR,					L'3',			CANDUICMD_SELECTLINE,		3 },
    //	{ CANDUIKEY_CHAR,					L'4',			CANDUICMD_SELECTLINE,		4 },
    //	{ CANDUIKEY_CHAR,					L'5',			CANDUICMD_SELECTLINE,		5 },
    //	{ CANDUIKEY_CHAR,					L'6',			CANDUICMD_SELECTLINE,		6 },
    //	{ CANDUIKEY_CHAR,					L'7',			CANDUICMD_SELECTLINE,		7 },
    //	{ CANDUIKEY_CHAR,					L'8',			CANDUICMD_SELECTLINE,		8 },
    //	{ CANDUIKEY_CHAR,					L'9',			CANDUICMD_SELECTLINE,		9 },
    { CANDUIKEY_VKEY,					L'1',			CANDUICMD_SELECTLINE,		1 },
    { CANDUIKEY_VKEY,					L'2',			CANDUICMD_SELECTLINE,		2 },
    { CANDUIKEY_VKEY,					L'3',			CANDUICMD_SELECTLINE,		3 },
    { CANDUIKEY_VKEY,					L'4',			CANDUICMD_SELECTLINE,		4 },
    { CANDUIKEY_VKEY,					L'5',			CANDUICMD_SELECTLINE,		5 },
    { CANDUIKEY_VKEY,					L'6',			CANDUICMD_SELECTLINE,		6 },
    { CANDUIKEY_VKEY,					L'7',			CANDUICMD_SELECTLINE,		7 },
    { CANDUIKEY_VKEY,					L'8',			CANDUICMD_SELECTLINE,		8 },
    { CANDUIKEY_VKEY,					L'9',			CANDUICMD_SELECTLINE,		9 },
    { CANDUIKEY_VKEY,					VK_NUMPAD1,		CANDUICMD_SELECTLINE,		1 },
    { CANDUIKEY_VKEY,					VK_NUMPAD2,		CANDUICMD_SELECTLINE,		2 },
    { CANDUIKEY_VKEY,					VK_NUMPAD3,		CANDUICMD_SELECTLINE,		3 },
    { CANDUIKEY_VKEY,					VK_NUMPAD4,		CANDUICMD_SELECTLINE,		4 },
    { CANDUIKEY_VKEY,					VK_NUMPAD5,		CANDUICMD_SELECTLINE,		5 },
    { CANDUIKEY_VKEY,					VK_NUMPAD6,		CANDUICMD_SELECTLINE,		6 },
    { CANDUIKEY_VKEY,					VK_NUMPAD7,		CANDUICMD_SELECTLINE,		7 },
    { CANDUIKEY_VKEY,					VK_NUMPAD8,		CANDUICMD_SELECTLINE,		8 },
    { CANDUIKEY_VKEY,					VK_NUMPAD9,		CANDUICMD_SELECTLINE,		9 },
    { CANDUIKEY_CHAR,					L'-',			CANDUICMD_MOVESELPREVPG,	0 },
    { CANDUIKEY_CHAR,					L'_',			CANDUICMD_MOVESELPREVPG,	0 },
    { CANDUIKEY_CHAR,					L'[',			CANDUICMD_MOVESELPREVPG,	0 },
    { CANDUIKEY_CHAR,					L'+',			CANDUICMD_MOVESELNEXTPG,	0 },
    { CANDUIKEY_CHAR,					L'=',			CANDUICMD_MOVESELNEXTPG,	0 },
    { CANDUIKEY_CHAR,					L']',			CANDUICMD_MOVESELNEXTPG,	0 },
    { CANDUIKEY_VKEY,					VK_APPS,		CANDUICMD_OPENCANDMENU,		0 },
};


//
// rule definitions
//

typedef struct _RULEDEF
{
    LPCWSTR			szRuleName;
    CANDUICOMMAND	cmd;
    UINT			uiParam;
} RULEDEF;


// rule definition in normal state

const RULEDEF rgRuleNorm[] =
{
/*
{ "rule name",		command,					paramater }
    */
    { L"Finalize",		CANDUICMD_COMPLETE,			0 },
    { L"Cancel",		CANDUICMD_CANCEL,			0 },
    { L"Next",			CANDUICMD_MOVESELNEXT,		0 },
    { L"Prev",			CANDUICMD_MOVESELPREV,		0 },
    { L"First",			CANDUICMD_MOVESELFIRST,		0 },
    { L"Last",			CANDUICMD_MOVESELLAST,		0 },
    { L"Menu",			CANDUICMD_OPENCANDMENU,		0 },
    { L"Select1",		CANDUICMD_SELECTLINE,		1 },
    { L"Select2",		CANDUICMD_SELECTLINE,		2 },
    { L"Select3",		CANDUICMD_SELECTLINE,		3 },
    { L"Select4",		CANDUICMD_SELECTLINE,		4 },
    { L"Select5",		CANDUICMD_SELECTLINE,		5 },
    { L"Select6",		CANDUICMD_SELECTLINE,		6 },
    { L"Select7",		CANDUICMD_SELECTLINE,		7 },
    { L"Select8",		CANDUICMD_SELECTLINE,		8 },
    { L"Select9",		CANDUICMD_SELECTLINE,		9 },
    { L"PageDown",      CANDUICMD_MOVESELNEXTPG,    0 },
    { L"PageUp",        CANDUICMD_MOVESELPREVPG,    0 },
};


//
//
//

class CTfCandidateUIContextOwner : public ITfCandidateUIContextOwner
{
public:
    CTfCandidateUIContextOwner( CCandidateUI *pCandUI );
    virtual ~CTfCandidateUIContextOwner( void );

    //
    // IUnknown methods
    //
    STDMETHODIMP QueryInterface( REFIID riid, void **ppvObj );
    STDMETHODIMP_(ULONG) AddRef( void );
    STDMETHODIMP_(ULONG) Release( void );

    //
    // ITfCandidateUIContextOwner methods
    //
    STDMETHODIMP ProcessCommand(CANDUICOMMAND cmd, INT iParam);
    STDMETHODIMP TestText(BSTR bstr, BOOL *pfHandles);

protected:
    ULONG        m_cRef;
    CCandidateUI *m_pCandUI;
};


/*============================================================================*/
/*                                                                            */
/*   C  C A N D I D A T E  U I                                                */
/*                                                                            */
/*============================================================================*/

/*   C  C A N D I D A T E  U I   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
CCandidateUI::CCandidateUI()
{
    Dbg_MemSetThisName(TEXT("CCandidateUI"));
    
    TF_CreateThreadMgr(&_ptim);
    m_hWndParent                = NULL;
    m_pCandWnd                  = NULL;
    
    m_pic                       = NULL;
    m_pdim                      = NULL;
    m_picParent                 = NULL;
    m_pTargetRange              = NULL;
    m_codepage                  = GetACP();
    
    m_fContextEventSinkAdvised  = FALSE;
    m_dwCookieContextOwnerSink  = 0;
    m_dwCookieContextKeySink    = 0;
    
    m_fTextEventSinkAdvised     = FALSE;
    m_dwCookieTextEditSink      = 0;
    m_dwCookieTextLayoutSink    = 0;
    m_dwCookieTransactionSink   = 0;
    
    m_pTextEventSink            = NULL;
    
    m_pCandUIKeyTable           = NULL;
    
    m_fInTransaction            = FALSE;
    m_pSelectionStart           = NULL;
    m_pSelectionCur             = NULL;
    
    m_fInCallback               = FALSE;
    m_pSpTask                   = NULL;
    
    // create candidate list manager, function manager, functions
    
    CCandListMgr::Initialize( this );
    CCandUIObjectMgr::Initialize( this );
    CCandUIPropertyMgr::Initialize( this );
    CCandUICompartmentMgr::Initialize( this );
    CCandUIFunctionMgr::Initialize( this );
    CCandUIExtensionMgr::Initialize( this );
}


/*   ~ C  C A N D I D A T E  U I   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
CCandidateUI::~CCandidateUI()
{
//  this call was for the case that CandidateUI was released wihtout 
//  CloseCandidateUI() call, but it should not happn (because it will be 
//  referenced by event sink or interfnal objects by OpenCandidateUI(), so 
//  ref count nevet be zero unless CloseCandidateUI() call...

    // CloseCandidateUIProc();
    
    //
    
    SafeReleaseClear( m_pCandUIKeyTable );
    
    //
    
    CCandUIExtensionMgr::Uninitialize();
    CCandUIFunctionMgr::Uninitialize();
    CCandUICompartmentMgr::Uninitialize();
    CCandUIPropertyMgr::Uninitialize();
    CCandUIObjectMgr::Uninitialize();
    CCandListMgr::Uninitialize();
    
    // remove ref to this in TLS
    
    SafeRelease( _ptim );
    SafeRelease( m_pTargetRange );
    DoneTextEventSinks( m_picParent );
    ClearSelectionCur();
    ClearWndCand();
    
    // release speech
    
    if(m_pSpTask) {
        delete m_pSpTask;
    }
}


/*   E N D  C A N D I D A T E  L I S T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
void CCandidateUI::EndCandidateList()
{
    DoneContextEventSinks( m_pic );
    ClearCompartment( m_tidClient, m_pic, GUID_COMPARTMENT_MSCANDIDATEUI_CONTEXTOWNER, FALSE );
    
    DoneTextEventSinks( m_picParent );
    ClearSelectionCur();
    
    SafeReleaseClear( m_pic );
    SafeReleaseClear( m_pTargetRange );
    SafeReleaseClear( m_pCandUIKeyTable );
    
    if (m_pdim) {
        m_pdim->Pop(0);
        m_pdim->Release();
        m_pdim = NULL;
        
        SafeRelease( m_picParent );
    }
}


/*   S E T  C L I E N T  I D   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::SetClientId( TfClientId tid )
{
    m_tidClient = tid;
    return S_OK;
}


/*   O P E N  C A N D I D A T E  U I   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::OpenCandidateUI( HWND hWndParent, ITfDocumentMgr *pdim, TfEditCookie ec, ITfRange *pRange )
{
    ITfInputProcessorProfiles *pProfile;
    ITfRange *pSelection = NULL;
    HRESULT hr = E_FAIL;
    
    Assert(!m_pic);
    Assert(!m_pdim);
    
    // sanity check
    
    if ((pdim == NULL) || (pRange == NULL)) {
        return E_INVALIDARG;
    }
    
    // check if candidate list has been set
    
    if (GetCandListMgr()->GetCandList() == NULL) {
        return E_FAIL;
    }
    
    // fail when it's opening candidate window already
    
    if (m_pCandWnd != NULL) {
        return E_FAIL;
    }
    
    // ensure that speech is enabled
    
    // BUGBUG - THIS IS TOO LATE TO ENSURE ENGINE IS FORCED TO SYNC VIA A RECOSTATE(INACTIVE) CALL.
    EnsureSpeech();
    
    //
    //
    //
    
    m_pdim = pdim;
    m_pdim->AddRef();
    
    // store current top IC
    
    GetTopIC( pdim, &m_picParent );
    
    //
    // create candidate window
    //
    
    ClearWndCand();
    if (CreateCandWindowObject( m_picParent, &m_pCandWnd ) == S_OK) {
        BOOL  fClipped;
        RECT  rc;
        
        SetCompartmentDWORD( m_tidClient, m_picParent, GUID_COMPARTMENT_MSCANDIDATEUI_WINDOW, 0x00000001, FALSE );

        m_pCandWnd->Initialize();
        
        // set target clause poisition
        
        GetTextExtInActiveView( ec, pRange, &rc, &fClipped );
        m_pCandWnd->SetTargetRect( &rc, fClipped );
        
        // initialize candidate list
        
        m_pCandWnd->InitCandidateList();
        
        // create window
        
        m_pCandWnd->CreateWnd( m_hWndParent );
        
        hr = S_OK;
    }
    
    //
    // create context for CandidteUI
    //
    
    SafeReleaseClear( m_pic );
    if (SUCCEEDED(hr)) {
        TfEditCookie ecTmp;
        
        // create context
        
        hr = pdim->CreateContext( m_tidClient, 0, NULL, &m_pic, &ecTmp );
        
        // disable keyboard while candidate UI open
        
        if (SUCCEEDED(hr)) {
            SetCompartmentDWORD( m_tidClient, m_pic, GUID_COMPARTMENT_KEYBOARD_DISABLED,     0x00000001, FALSE );
            SetCompartmentDWORD( m_tidClient, m_pic, GUID_COMPARTMENT_MSCANDIDATEUI_CONTEXT, 0x00000001, FALSE );
        }
        
        // create context owner instance

        if (SUCCEEDED(hr)) {
            CTfCandidateUIContextOwner *pCxtOwner;

            pCxtOwner = new CTfCandidateUIContextOwner( this );
            if (pCxtOwner == NULL) {
                hr = E_OUTOFMEMORY;
            }
            else {
                SetCompartmentUnknown( m_tidClient, m_pic, GUID_COMPARTMENT_MSCANDIDATEUI_CONTEXTOWNER, (IUnknown*)pCxtOwner );
            }
        }

        // init context event sinks
        
        if (SUCCEEDED(hr)) {
            hr = InitContextEventSinks( m_pic );
        }
        
        // push context
        
        if (SUCCEEDED(hr)) {
            hr = pdim->Push( m_pic );
        }
    }
    
    //
    // cleanup all when failed
    //
    
    if (FAILED(hr)) {
        // cleanup context
        
        if (m_pic != NULL) {
            DoneContextEventSinks( m_pic );
            ClearCompartment( m_tidClient, m_pic, GUID_COMPARTMENT_MSCANDIDATEUI_CONTEXTOWNER, FALSE );
            SafeReleaseClear( m_pic );
        }
        
        // cleanup candidate window
        
        if (m_pCandWnd != NULL) {
            m_pCandWnd->DestroyWnd();
            ClearWndCand();
        }

        // release objects
        
        SafeReleaseClear( m_picParent );
        SafeReleaseClear( m_pdim );
        
        return hr;
    }
    
    // 
    // initialize miscs
    //
    
    // get current codepage from current assembly
    
    m_codepage = GetACP();
    if (SUCCEEDED(CoCreateInstance( CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, IID_ITfInputProcessorProfiles, (void **)&pProfile ))) {
        LANGID langid;
        char   szCpg[ 16 ];
        
        if (pProfile->GetCurrentLanguage( &langid ) == S_OK) {
            if (GetLocaleInfo( MAKELCID(langid, SORT_DEFAULT), LOCALE_IDEFAULTANSICODEPAGE, szCpg, ARRAYSIZE(szCpg) ) != 0) {
                m_codepage = atoi( szCpg );
            }
        }
        pProfile->Release();
    }

    // get key table
    
    SafeRelease( m_pCandUIKeyTable );
    m_pCandUIKeyTable = GetKeyTableProc( m_picParent );
    
    // make copy target range
    
    pRange->Clone( &m_pTargetRange );
    
    // store selection first
    
    ClearSelectionCur();
    if (GetSelectionSimple( ec, m_picParent, &pSelection ) == S_OK) {
        SetSelectionCur( pSelection );
        SafeRelease( pSelection );
    }
    
    // init text event sinks
    
    DoneTextEventSinks( m_picParent );
    InitTextEventSinks( m_picParent );
    
    //
    // show candidate window at last
    //
    
    m_pCandWnd->Show( GetPropertyMgr()->GetCandWindowProp()->IsVisible() );
    m_pCandWnd->UpdateAllWindow();
    
    // notify initial seletion
    
    NotifySelectCand( GetCandListMgr()->GetCandList()->GetSelection() );
    
    return hr;
}


/*   C L O S E  C A N D I D A T E  U I   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::CloseCandidateUI( void )
{
    HRESULT hr;

    // Windows#502340
    // CandidateUI will be released during DestroyWindow().  As result, 
    // ref count will be zero and instance will be disposed during closeing UI.
    // prevent from it, keep one refcount until closing process will finished.

    AddRef();
    hr = CloseCandidateUIProc();
    Release();

    return hr;
}


/*   S E T  C A N D I D A T E  L I S T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::SetCandidateList( ITfCandidateList *pCandList )
{
    // release before change candidate list
    
    GetCandListMgr()->ClearCandiateList();
    
    // set new candidate list
    
    return GetCandListMgr()->SetCandidateList( pCandList );
}


/*   S E T  S E L E C T I O N   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::SetSelection( ULONG nIndex )
{
    HRESULT hr;
    int iCandItem;
    
    // check if candidate list has been set
    
    if (GetCandListMgr()->GetCandList() == NULL) {
        return E_FAIL;
    }
    
    // map index to icanditem
    
    hr = GetCandListMgr()->GetCandList()->MapIndexToIItem( nIndex, &iCandItem );
    if (FAILED(hr)) {
        Assert( FALSE );
        return hr;
    }
    
    return GetCandListMgr()->SetSelection( iCandItem, NULL /* no cand function */ );
}


/*   G E T  S E L E C T I O N   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::GetSelection( ULONG *pnIndex )
{
    HRESULT hr;
    int iCandItem;
    ULONG nIndex;
    
    if (pnIndex == NULL) {
        return E_INVALIDARG;
    }
    
    // check if candidate list has been set
    
    if (GetCandListMgr()->GetCandList() == NULL) {
        return E_FAIL;
    }
    
    iCandItem = GetCandListMgr()->GetCandList()->GetSelection();
    
    hr = GetCandListMgr()->GetCandList()->MapIItemToIndex( iCandItem, &nIndex );
    if (FAILED(hr)) {
        Assert( FALSE );
        return hr;
    }
    
    *pnIndex = nIndex;
    return S_OK;
}


/*   S E T  T A R G E T  R A N G E   */
/*------------------------------------------------------------------------------

  Set target range
  Memo: This method works while candidate UI is opened.
  
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::SetTargetRange( ITfRange *pRange )
{
    CEditSession *pes;
    
    if (pRange == NULL) {
        return E_FAIL;
    }
    
    if (m_pCandWnd == NULL) {
        return E_FAIL;
    }
    
    SafeReleaseClear( m_pTargetRange );
    pRange->Clone( &m_pTargetRange );
    
    // move candidate window
    
    if (pes = new CEditSession( EditSessionCallback )) {
        HRESULT hr;
        
        pes->_state.u      = ESCB_RESETTARGETPOS;
        pes->_state.pv     = this;
        pes->_state.wParam = 0;
        pes->_state.pRange = m_pTargetRange;
        pes->_state.pic    = m_picParent;
        
        m_picParent->RequestEditSession( m_tidClient, pes, TF_ES_READ | TF_ES_SYNC, &hr );
        
        pes->Release();
    }
    
    return S_OK;
}


/*   G E T  T A R G E T  R A N G E   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::GetTargetRange( ITfRange **ppRange )
{
    if (m_pTargetRange == NULL) {
        return E_FAIL;
    }
    
    Assert( ppRange != NULL );
    return m_pTargetRange->Clone( ppRange );
}


/*   G E T  U I  O B J E C T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::GetUIObject( REFIID riid, IUnknown **ppunk )
{
    return GetPropertyMgr()->GetObject( riid, (void **)ppunk );
}


/*   G E T  F U N C T I O N   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::GetFunction( REFIID riid, IUnknown **ppunk )
{
    if (ppunk == NULL) {
        return E_INVALIDARG;
    }
    
    // extension manager
    
    if (IsEqualGUID( riid, IID_ITfCandUIFnExtension )) {
        CCandUIFnExtension *pObject;
        HRESULT            hr;
        
        pObject = new CCandUIFnExtension( this, GetExtensionMgr() );
        if (pObject == NULL) {
            return E_OUTOFMEMORY;
        }
        
        hr = pObject->QueryInterface( riid, (void **)ppunk );
        pObject->Release();
        
        return hr;
    }
    
    // key config
    
    if (IsEqualGUID( riid, IID_ITfCandUIFnKeyConfig )) {
        CCandUIFnKeyConfig *pObject;
        HRESULT            hr;
        
        pObject = new CCandUIFnKeyConfig( this );
        if (pObject == NULL) {
            return E_OUTOFMEMORY;
        }
        
        hr = pObject->QueryInterface( riid, (void **)ppunk );
        pObject->Release();
        
        return hr;
    }
    
    // UI config
    
    if (IsEqualGUID( riid, IID_ITfCandUIFnUIConfig )) {
        CCandUIFnUIConfig *pObject;
        HRESULT           hr;
        
        pObject = new CCandUIFnUIConfig( this );
        if (pObject == NULL) {
            return E_OUTOFMEMORY;
        }
        
        hr = pObject->QueryInterface( riid, (void **)ppunk );
        pObject->Release();
        
        return hr;
    }
    
    // regular functions
    
    return GetFunctionMgr()->GetObject( riid, (void **)ppunk );
}


/*   P R O C E S S  C O M M A N D   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
STDAPI CCandidateUI::ProcessCommand( CANDUICOMMAND cmd, INT iParam )
{
    //
    
    if (cmd == CANDUICMD_NONE) {
        return E_INVALIDARG;
    }
    
    if (m_pCandWnd == NULL) {
        return E_FAIL;
    }
    
    return m_pCandWnd->ProcessCommand( cmd, iParam );
}


/*   C L O S E  C A N D I D A T E  U  I  P R O C   */
/*------------------------------------------------------------------------------

	close candidate u i proc

------------------------------------------------------------------------------*/
HRESULT CCandidateUI::CloseCandidateUIProc( void )
{
    if (m_picParent && m_pCandWnd) {
        SetCompartmentDWORD( m_tidClient, m_picParent, GUID_COMPARTMENT_MSCANDIDATEUI_WINDOW, 0x00000000, FALSE );
    }

    if (m_pCandWnd) {
        m_pCandWnd->DestroyWnd();
        ClearWndCand();
        EndCandidateList();
    }
    
    GetCandListMgr()->ClearCandiateList();
    
    if (m_pSpTask)
        m_pSpTask->_Activate(FALSE);

    return S_OK;
}

//
// key config function methods
//

/*   S E T  K E Y  T A B L E   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::SetKeyTable( ITfContext *pic, ITfCandUIKeyTable *pCandUIKeyTable )
{
    HRESULT hr;
    CCandUIKeyTable *pCandUIKeyTableCopy;
    
    if ((pic == NULL) || (pCandUIKeyTable == NULL)) {
        return E_INVALIDARG;
    }
    
    if (GetCompartmentMgr() == NULL) {
        return E_FAIL;
    }
    
    // store copy of key table to input context
    
    pCandUIKeyTableCopy = new CCandUIKeyTable();
    if (pCandUIKeyTableCopy == NULL) {
        return E_OUTOFMEMORY;
    }
    
    hr = pCandUIKeyTableCopy->SetKeyTable( pCandUIKeyTable );
    if (FAILED( hr )) {
        pCandUIKeyTableCopy->Release();
        return hr;
    }
    
    hr = GetCompartmentMgr()->SetKeyTable( pic, pCandUIKeyTableCopy );
    if (FAILED( hr )) {
        pCandUIKeyTableCopy->Release();
        return hr;
    }
    pCandUIKeyTableCopy->Release();
    
    // reload key table if exist
    
    // REVIEW: KOJIW: If we support changing keytable of candidate UI in other IC,
    // need to do this in compartment event sink.
    
    if (m_pCandUIKeyTable != NULL) {
        m_pCandUIKeyTable->Release();
        
        Assert( m_picParent != NULL );
        m_pCandUIKeyTable = GetKeyTableProc( m_picParent );
    }
    
    return S_OK;
}


/*   G E T  K E Y  T A B L E   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT  CCandidateUI::GetKeyTable( ITfContext *pic, ITfCandUIKeyTable **ppCandUIKeyTable)
{
    if ((pic == NULL) || (ppCandUIKeyTable == NULL)) {
        return E_INVALIDARG;
    }
    
    // load key table from input context
    
    *ppCandUIKeyTable = GetKeyTableProc( pic );
    return (*ppCandUIKeyTable != NULL) ? S_OK : E_FAIL;
}


/*   R E S E T  K E Y  T A B L E   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::ResetKeyTable( ITfContext *pic )
{
    HRESULT hr;
    
    if (pic == NULL) {
        return E_INVALIDARG;
    }
    
    if (GetCompartmentMgr() == NULL) {
        return E_FAIL;
    }
    
    hr = GetCompartmentMgr()->ClearKeyTable( pic );
    if (FAILED( hr )) {
        return hr;
    }
    
    // reload key table if exist
    
    if (m_pCandUIKeyTable != NULL) {
        m_pCandUIKeyTable->Release();
        
        Assert( m_picParent != NULL );
        m_pCandUIKeyTable = GetKeyTableProc( m_picParent );
    }
    
    return S_OK;
}


//
// UI config function methods
//

/*   S E T  U I  S T Y L E   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::SetUIStyle( ITfContext *pic, CANDUISTYLE style )
{
    HRESULT hr = S_OK;
    
    if (pic == NULL) {
        return E_INVALIDARG;
    }
    
    if (GetCompartmentMgr() == NULL) {
        return E_FAIL;
    }
    
    // store ui style to input context
    
    GetCompartmentMgr()->SetUIStyle( pic, style );
    
    // rebuild candidate window
    
    // REVIEW: KOJIW: If we support changing ui style of candidate UI in other IC,
    // need to do this in compartment event sink.
    
    if ((m_picParent == pic) && (m_pCandWnd != NULL)) {
        // destory candidate window object
        
        m_pCandWnd->DestroyWnd();
        ClearWndCand();
        
        // create and initialize window object
        
        hr = CreateCandWindowObject( m_picParent, &m_pCandWnd );
        if (SUCCEEDED(hr)) {
            hr = InitCandWindow();
        }
    }
    
    return hr;
}


/*   G E T  U I  S T Y L E   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::GetUIStyle( ITfContext *pic, CANDUISTYLE *pstyle )
{
    if ((pic == NULL) || (pstyle == NULL)) {
        return E_INVALIDARG;
    }
    
    if (GetCompartmentMgr() == NULL) {
        return E_FAIL;
    }
    
    if (FAILED( GetCompartmentMgr()->GetUIStyle( pic, pstyle ))) {
        *pstyle = CANDUISTY_LIST;
    }
    
    return S_OK;
}


/*   S E T  U I  O P T I O N   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::SetUIOption( ITfContext *pic, DWORD dwOption )
{
    HRESULT hr = S_OK;
    
    if (pic == NULL) {
        return E_INVALIDARG;
    }
    
    if (GetCompartmentMgr() == NULL) {
        return E_FAIL;
    }
    
    // store ui style to input context
    
    GetCompartmentMgr()->SetUIOption( pic, dwOption );
    
    // rebuild candidate window
    
    // REVIEW: KOJIW: If we support changing ui style of candidate UI in other IC,
    // need to do this in compartment event sink.
    
    if ((m_picParent == pic) && (m_pCandWnd != NULL)) {
        // destory candidate window object
        
        m_pCandWnd->DestroyWnd();
        ClearWndCand();
        
        // create and initialize window object
        
        hr = CreateCandWindowObject( m_picParent, &m_pCandWnd );
        if (SUCCEEDED(hr)) {
            hr = InitCandWindow();
        }
    }
    
    return hr;
}


/*   G E T  U I  O P T I O N   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::GetUIOption( ITfContext *pic, DWORD *pdwOption )
{
    if ((pic == NULL) || (pdwOption == NULL)) {
        return E_INVALIDARG;
    }
    
    if (GetCompartmentMgr() == NULL) {
        return E_FAIL;
    }
    
    if (FAILED( GetCompartmentMgr()->GetUIOption( pic, pdwOption ))) {
        *pdwOption = 0;
    }
    
    return S_OK;
}


//
// callback functions
//

/*   I N I T  C O N T E X T  E V E N T  S I N K S   */
/*------------------------------------------------------------------------------

  initialize sinks for input context events
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::InitContextEventSinks( ITfContext *pic )
{
    HRESULT hr = E_FAIL;
    ITfSource *pSource = NULL;
    
    Assert( pic == m_picParent );
    Assert( !m_fContextEventSinkAdvised );
    
    m_fContextEventSinkAdvised = FALSE;
    if (pic->QueryInterface( IID_ITfSource, (void **)&pSource) == S_OK) {
        if (FAILED(pSource->AdviseSink( IID_ITfContextOwner, (ITfContextOwner *)this, &m_dwCookieContextOwnerSink ))) {
            pSource->Release();
            return hr;
        }
        
        if (FAILED(pSource->AdviseSink( IID_ITfContextKeyEventSink, (ITfContextKeyEventSink *)this, &m_dwCookieContextKeySink ))) {
            pSource->UnadviseSink( m_dwCookieContextOwnerSink );
            pSource->Release();
            return hr;
        }
        pSource->Release();
        
        m_fContextEventSinkAdvised = TRUE;
        hr = S_OK;
    }
    
    // advise text event sink for own IC
    
    // NOTE: This is a temporary fix for Satori#3644 (Cicero#3407) to handle 
    // a text event from HW Tip.  So the detect logic is very tiny (it just 
    // handles half-width alphanumeric numbers).  In the next version of Cicero, 
    // we will use commanding feature to do same thing...
    // (related functions: TextEventCallback, HandleTextDeltas)
    
    m_pTextEventSink = new CTextEventSink( TextEventCallback, this );
    if (m_pTextEventSink != NULL) {
        m_pTextEventSink->_Advise( pic, ICF_TEXTDELTA );
    }
    
    return hr;
}


/*   D O N E  C O N T E X T  E V E N T  S I N K S   */
/*------------------------------------------------------------------------------

  uninitialize sinks for input context events
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::DoneContextEventSinks( ITfContext *pic )
{
    HRESULT hr = E_FAIL;
    ITfSource *pSource;
    
    Assert( pic == m_picParent );
    
    // unadvise text event sink for own IC
    
    if (m_pTextEventSink != NULL) {
        m_pTextEventSink->_Unadvise();
        SafeReleaseClear( m_pTextEventSink );
    }
    
    if (!m_fContextEventSinkAdvised) {
        return S_OK;
    }
    
    if (pic->QueryInterface( IID_ITfSource, (void **)&pSource) == S_OK) {
        pSource->UnadviseSink( m_dwCookieContextOwnerSink );
        pSource->UnadviseSink( m_dwCookieContextKeySink );
        pSource->Release();
        
        m_fContextEventSinkAdvised = FALSE;
        hr = S_OK;
    }
    
    return hr;
}


/*   G E T  A  C  P  F R O M  P O I N T   */
/*------------------------------------------------------------------------------

  Get acp from point 
  (ITfContextOwner method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::GetACPFromPoint( const POINT *pt, DWORD dwFlags, LONG *pacp )
{
    return E_FAIL;
}


/*   G E T  S C R E E N  E X T   */
/*------------------------------------------------------------------------------

  Get screen extent of context
  (ITfContextOwner method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::GetScreenExt( RECT *prc )
{
    return E_FAIL;
}


/*   G E T  T E X T  E X T   */
/*------------------------------------------------------------------------------

  Get text externt of context
  (ITfContextOwner method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::GetTextExt( LONG acpStart, LONG acpEnd, RECT *prc, BOOL *pfClipped )
{
    return E_FAIL;
}


/*   G E T  S T A T U S   */
/*------------------------------------------------------------------------------

  Get status of context
  (ITfContextOwner method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::GetStatus( TF_STATUS *pdcs )
{
    if (pdcs == NULL) {
        return E_POINTER;
    }
    
    memset(pdcs, 0, sizeof(*pdcs));
    pdcs->dwDynamicFlags = 0;
    pdcs->dwStaticFlags  = 0;
    
    return S_OK;
}


/*   G E T  W N D   */
/*------------------------------------------------------------------------------

  Get window of context
  (ITfContextOwner method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::GetWnd( HWND *phwnd )
{
    if (phwnd == NULL) {
        return E_POINTER;
    }
    
    *phwnd = NULL;
    if (m_pCandWnd != NULL) {
        *phwnd = m_pCandWnd->GetWnd();
    }
    
    return S_OK;
}


/*   G E T  A T T R I B U T E   */
/*------------------------------------------------------------------------------

  Get attribute of context
  (ITfContextOwner method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::GetAttribute( REFGUID rguidAttribute, VARIANT *pvarValue )
{
    return E_NOTIMPL;
}


/*   O N  K E Y  D O W N   */
/*------------------------------------------------------------------------------

  event sink for key down event
  (ITfContextKeyEventSink method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::OnKeyDown( WPARAM wParam, LPARAM lParam, BOOL *pfEaten )
{
    return OnKeyEvent( ICO_KEYDOWN, wParam, lParam, pfEaten );
}


/*   O N  K E Y  U P   */
/*------------------------------------------------------------------------------

  event sink for key up event
  (ITfContextKeyEventSink method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::OnKeyUp( WPARAM wParam, LPARAM lParam, BOOL *pfEaten )
{
    return OnKeyEvent( ICO_KEYUP, wParam, lParam, pfEaten );
}


/*   O N  T E S T  K E Y  D O W N   */
/*------------------------------------------------------------------------------

  event sink for key down testing event
  (ITfContextKeyEventSink method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::OnTestKeyDown( WPARAM wParam, LPARAM lParam, BOOL *pfEaten )
{
    return OnKeyEvent( ICO_TESTKEYDOWN, wParam, lParam, pfEaten );
}


/*   O N  T E S T  K E Y  U P   */
/*------------------------------------------------------------------------------

  event sink for key up testing event
  (ITfContextKeyEventSink method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::OnTestKeyUp( WPARAM wParam, LPARAM lParam, BOOL *pfEaten )
{
    return OnKeyEvent( ICO_TESTKEYUP, wParam, lParam, pfEaten );
}


/*   T E X T  E V E N T  C A L L B A C K   */
/*------------------------------------------------------------------------------

  text event (for own IC) callback function
  (static function)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::TextEventCallback( UINT uCode, VOID *pv, VOID *pvData )
{
    HRESULT      hr;
    CCandidateUI *pCandUI;
    
    // 
    
    pCandUI = (CCandidateUI *)pv;
    Assert( pCandUI != NULL );
    
    // ignore event of myself
    
    if (uCode == ICF_TEXTDELTA) {
        TESENDEDIT    *pTESEndEdit = (TESENDEDIT*)pvData;
        IEnumTfRanges *pEnumRanges;
        
        if (SUCCEEDED(pTESEndEdit->pEditRecord->GetTextAndPropertyUpdates( TF_GTP_INCL_TEXT, NULL, 0, &pEnumRanges ))) {
            CEditSession *pes;
            
            if (pes = new CEditSession(EditSessionCallback)) {
                pes->_state.u   = ESCB_TEXTEVENT;
                pes->_state.pv  = (void *)pCandUI;
                pes->_state.pic = pCandUI->m_pic;
                pes->_state.pv1 = pEnumRanges;     // DONT FORGET TO RELESE IT IN EDIT SESSION!!!
                
                pCandUI->m_pic->RequestEditSession( 0, pes, TF_ES_READ | TF_ES_SYNC, &hr );
                
                pes->Release();
            }
            else {
                pEnumRanges->Release();
            }
        }
    }
    
    return S_OK;
}


//
// text event sink functions
//

/*   I N I T  T E X T  E V E N T  S I N K S   */
/*------------------------------------------------------------------------------

  initialize sinks for text events
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::InitTextEventSinks( ITfContext *pic )
{
    HRESULT hr = E_FAIL;
    ITfSource *pSource = NULL;
    
    Assert( pic == m_picParent );
    Assert( !m_fTextEventSinkAdvised );
    Assert( !IsInEditTransaction() );
    
    m_fTextEventSinkAdvised = FALSE;
    LeaveEditTransaction();
    
    if (pic->QueryInterface( IID_ITfSource, (void **)&pSource) == S_OK) {
        if (FAILED(pSource->AdviseSink( IID_ITfTextEditSink, (ITfTextEditSink *)this, &m_dwCookieTextEditSink ))) {
            pSource->Release();
            return hr;
        }
        
        if (FAILED(pSource->AdviseSink( IID_ITfTextLayoutSink, (ITfTextLayoutSink *)this, &m_dwCookieTextLayoutSink ))) {
            pSource->UnadviseSink( m_dwCookieTextEditSink );
            pSource->Release();
            return hr;
        }
        
        if (FAILED(pSource->AdviseSink( IID_ITfEditTransactionSink, (ITfEditTransactionSink *)this, &m_dwCookieTransactionSink ))) {
            pSource->UnadviseSink( m_dwCookieTextEditSink );
            pSource->UnadviseSink( m_dwCookieTextLayoutSink );
            pSource->Release();
            return hr;
        }
        pSource->Release();
        
        m_fTextEventSinkAdvised = TRUE;
        hr = S_OK;
    }
    
    return hr;
}


/*   D O N E  T E X T  E V E N T  S I N K S   */
/*------------------------------------------------------------------------------

  uninitialize sinks for text events
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::DoneTextEventSinks( ITfContext *pic )
{
    HRESULT hr = E_FAIL;
    ITfSource *pSource;
    
    Assert( pic == m_picParent );
    
    LeaveEditTransaction();
    
    if (!m_fTextEventSinkAdvised) {
        return S_OK;
    }
    
    if (pic->QueryInterface( IID_ITfSource, (void **)&pSource) == S_OK) {
        pSource->UnadviseSink( m_dwCookieTextEditSink );
        pSource->UnadviseSink( m_dwCookieTextLayoutSink );
        pSource->UnadviseSink( m_dwCookieTransactionSink );
        pSource->Release();
        
        m_fTextEventSinkAdvised = FALSE;
        hr = S_OK;
    }
    
    return hr;
}


/*   O N  E N D  E D I T   */
/*------------------------------------------------------------------------------

  event sink for text edit event
  (ITfTextEditSink method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::OnEndEdit( ITfContext *pic, TfEditCookie ecReadOnly, ITfEditRecord *pEditRecord )
{
    BOOL fInWriteSession = FALSE;
    BOOL fSelChanged = FALSE;
    
    // get selection status
    
    if (FAILED(pEditRecord->GetSelectionStatus(&fSelChanged))) {
        return S_OK;
    }
    
    // keep current selection always
    
    if (fSelChanged) {
        ITfRange *pSelection = NULL;
        
        if (GetSelectionSimple( ecReadOnly, pic, &pSelection ) == S_OK) {
            SetSelectionCur( pSelection );
        }
        
        SafeRelease( pSelection );
    }
    
    // ignore events during edit transaction
    
    if (IsInEditTransaction()) {
        return S_OK;
    }
    
    // ignore events made by client tip
    
    pic->InWriteSession( m_tidClient, &fInWriteSession );
    if (fInWriteSession) {
        return S_OK;
    }
    
    // cancel candidate session when selection has been moved
    
    if (fSelChanged) {
        NotifyCancelCand();
    }
    
    return S_OK;
}


/*   O N  L A Y O U T  C H A N G E   */
/*------------------------------------------------------------------------------

  event sink for text layout event
  (ITfTextLayoutSink method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::OnLayoutChange( ITfContext *pic, TfLayoutCode lcode, ITfContextView *pView )
{
    BOOL fInWriteSession = FALSE;
    CEditSession *pes;
    
    // ignore events made by client tip
    
    pic->InWriteSession( m_tidClient, &fInWriteSession );
    if (fInWriteSession) {
        return S_OK;
    }
    
    // we only care about the active view
    
    if (!IsActiveView( m_picParent, (ITfContextView *)pView )) {
        return S_OK;
    }
    
    // move candidate window
    
    Assert( m_pCandWnd != NULL );
    if (pes = new CEditSession( EditSessionCallback )) {
        HRESULT hr;
        
        pes->_state.u      = ESCB_RESETTARGETPOS;
        pes->_state.pv     = this;
        pes->_state.wParam = 0;
        pes->_state.pRange = m_pTargetRange;
        pes->_state.pic    = m_picParent;
        
        m_picParent->RequestEditSession( m_tidClient, pes, TF_ES_READ | TF_ES_SYNC, &hr );
        
        pes->Release();
    }
    
    return S_OK;
}


/*   O N  S T A R T  E D I T  T R A N S A C T I O N   */
/*------------------------------------------------------------------------------

  event sink for start of application transaction
  (ITfEditTransactionSink method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::OnStartEditTransaction( ITfContext *pic )
{
    // enter transaction session
    
    Assert( !IsInEditTransaction() );
    EnterEditTransaction( GetSelectionCur() );
    
    return S_OK;
}


/*   O N  E N D  E D I T  T R A N S A C T I O N   */
/*------------------------------------------------------------------------------

  event sink for end of application transaction
  (ITfEditTransactionSink method)
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::OnEndEditTransaction( ITfContext *pic )
{
    CEditSession *pes;
    // sanity check
    
    if (!IsInEditTransaction()) {
        return S_OK;
    }
    
    // check selection movement
    
    if (pes = new CEditSession( EditSessionCallback )) {
        HRESULT hr;
        
        pes->_state.u   = ESCB_COMPARERANGEANDCLOSECANDIDATE;
        pes->_state.pv  = this;
        pes->_state.pv1 = GetSelectionStart();
        pes->_state.pv2 = GetSelectionCur();
        pes->_state.pic = m_picParent;
        
        m_picParent->RequestEditSession( m_tidClient, pes, TF_ES_READ | TF_ES_ASYNC, &hr );
        
        pes->Release();
    }
    
    // leave transaction session
    
    LeaveEditTransaction();
    
    return S_OK;
}


//
// edit session
//

/*   E D I T  S E S S I O N  C A L L B A C K   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::EditSessionCallback( TfEditCookie ec, CEditSession *pes )
{
    CCandidateUI *pCandUI;
    
    switch (pes->_state.u)
    {
        case ESCB_RESETTARGETPOS:
        {
            pCandUI = (CCandidateUI *)pes->_state.pv;
            RECT rc;
            BOOL fClipped;
        
            if (pes->_state.pRange == NULL || pCandUI->m_pCandWnd == NULL) {
                break;
            }
        
            if (!pCandUI->GetPropertyMgr()->GetCandWindowProp()->IsAutoMoveEnabled()) {
                break;
            }
        
            // reset target clause poisition
        
            GetTextExtInActiveView( ec, pes->_state.pRange, &rc, &fClipped );
            pCandUI->m_pCandWnd->SetTargetRect( &rc, fClipped );
            break;
        }
        
        case ESCB_COMPARERANGEANDCLOSECANDIDATE:
        {
            ITfContext *pic = pes->_state.pic;
            BOOL       fRangeIdentical = FALSE;
            ITfRange   *pRange1 = (ITfRange*)pes->_state.pv1;
            ITfRange   *pRange2 = (ITfRange*)pes->_state.pv2;
            LONG       lStart;
            LONG       lEnd;
            CCandidateUI *_this = (CCandidateUI *)pes->_state.pv;
        
            pRange1->CompareStart( ec, pRange2, TF_ANCHOR_START, &lStart );
            pRange1->CompareEnd( ec, pRange2, TF_ANCHOR_END, &lEnd );
        
            fRangeIdentical = (lStart == 0) && (lEnd == 0);
        
            // Since we made this call asynchronous, we need 
            if (!fRangeIdentical) 
            {
                _this->NotifyCancelCand();
            }
        
            break;
        }
        
        case ESCB_TEXTEVENT: 
        {
            pCandUI = (CCandidateUI *)pes->_state.pv;
            ITfContext *pic = pes->_state.pic;
            IEnumTfRanges *pEnumRanges = (IEnumTfRanges *)pes->_state.pv1;
        
            // handle textevent
            pCandUI->HandleTextDeltas( ec, pic, pEnumRanges );
        
            pEnumRanges->Release();
            break;
        }
    }
    
    return S_OK;
}


//
// selection
//

/*   S E T  S E L E C T I O N  C U R   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
void CCandidateUI::SetSelectionCur( ITfRange *pSelection )
{
    SafeReleaseClear( m_pSelectionCur );
    m_pSelectionCur = pSelection;
    if (m_pSelectionCur != NULL) {
        m_pSelectionCur->AddRef();
    }
}


/*   C L E A R  S E L E C T I O N  C U R   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
void CCandidateUI::ClearSelectionCur( void )
{
    SafeReleaseClear( m_pSelectionCur );
}


/*   G E T  S E L E C T I O N  C U R   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
ITfRange *CCandidateUI::GetSelectionCur( void )
{
    return m_pSelectionCur;
}


//
// transaction session functions
//

/*   S E T  S E L E C T I O N  S T A R T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
void CCandidateUI::SetSelectionStart( ITfRange *pSelection )
{
    SafeReleaseClear( m_pSelectionStart );
    m_pSelectionStart = pSelection;
    if (m_pSelectionStart != NULL) {
        m_pSelectionStart->AddRef();
    }
}


/*   C L E A R  S E L E C T I O N  S T A R T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
void CCandidateUI::ClearSelectionStart( void )
{
    SafeReleaseClear( m_pSelectionStart );
}


/*   G E T  S E L E C T I O N  S T A R T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
ITfRange *CCandidateUI::GetSelectionStart( void )
{
    return m_pSelectionStart;
}


/*   E N T E R  E D I T  T R A N S A C T I O N   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
void CCandidateUI::EnterEditTransaction( ITfRange *pSelection )
{
    Assert( !m_fInTransaction );
    
    if (pSelection == NULL) {
        return;
    }
    
    m_fInTransaction = TRUE;
    SetSelectionStart( pSelection );
}


/*   L E A V E  E D I T  T R A N S A C T I O N   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
void CCandidateUI::LeaveEditTransaction( void )
{
    m_fInTransaction = FALSE;
    ClearSelectionStart();
}


//
// notification function (notification to client)
//

/*   N O T I F Y  C A N C E L  C A N D   */
/*------------------------------------------------------------------------------

  Send notification (callback) to TIP that to cancel candidate
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifyCancelCand( void )
{
    if (m_pCandWnd) {
        m_pCandWnd->UpdateAllWindow();
    }
    
    return CallSetResult( 0, CAND_CANCELED );
}


/*   N O T I F Y  S E L E C T  C A N D   */
/*------------------------------------------------------------------------------

  Send notification (callback) to TIP that selection has been changed
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifySelectCand( int iCandItem )
{
    HRESULT hr;
    ULONG nIndex;
    
    // NOTE: Do not send a notification to TIP to prevent from updating inline 
    // text during filtering.
    // This will be called by filtering candidates, and aslo sorting candidates 
    // because selection in UI will be changed by sorting.  But the actual selected 
    // item never changed by sorting.  When the selection has been changed by 
    // user action such as hitting arrow key, the filtering string has already 
    // been reset.  So, we can send notify correctly in that case.
    
    if (GetFunctionMgr()->GetCandFnAutoFilter()->IsEnabled()) {
        if (GetFunctionMgr()->GetCandFnAutoFilter()->GetFilterString() != NULL) {
            return S_OK;
        }
    }
    
    Assert( GetCandListMgr()->GetCandList() != NULL );
    
    hr = GetCandListMgr()->GetCandList()->MapIItemToIndex( iCandItem, &nIndex );
    if (FAILED(hr)) {
        Assert( FALSE );
        return hr;
    }
    
    if (m_pCandWnd) {
        m_pCandWnd->UpdateAllWindow();
    }
    
    return CallSetResult( nIndex, CAND_SELECTED );
}


/*   N O T I F Y  C O M P L E T E  O P T I O N   */
/*------------------------------------------------------------------------------

  Send notification (callback) to TIP that to complete option
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifyCompleteOption( int iCandItem )
{
    HRESULT hr;
    ULONG nIndex;
    
    Assert( GetCandListMgr()->GetOptionsList() != NULL );
    
    hr = GetCandListMgr()->GetOptionsList()->MapIItemToIndex( iCandItem, &nIndex );
    if (FAILED(hr)) {
        Assert( FALSE );
        return hr;
    }
    
    if (m_pCandWnd) {
        m_pCandWnd->UpdateAllWindow();
    }
    
    return CallSetOptionResult( nIndex, CAND_FINALIZED );
}


/*   N O T I F Y  C O M P L E T E  C A N D   */
/*------------------------------------------------------------------------------

  Send notification (callback) to TIP that to complete candidate
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifyCompleteCand( int iCandItem )
{
    HRESULT hr;
    ULONG nIndex;
    
    Assert( GetCandListMgr()->GetCandList() != NULL );
    
    hr = GetCandListMgr()->GetCandList()->MapIItemToIndex( iCandItem, &nIndex );
    if (FAILED(hr)) {
        Assert( FALSE );
        return hr;
    }
    
    if (m_pCandWnd) {
        m_pCandWnd->UpdateAllWindow();
    }
    
    return CallSetResult( nIndex, CAND_FINALIZED );
}


/*   N O T I F Y  E X T E N S I O N  E V E N T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifyExtensionEvent( int iExtension, DWORD dwCommand, LPARAM lParam )
{
    CCandUIExtension *pExtension;
    HRESULT          hr = E_FAIL;
    
    pExtension = GetExtensionMgr()->GetExtension( iExtension );
    if (pExtension != NULL) {
        hr = pExtension->NotifyExtensionEvent( dwCommand, lParam );
    }
    
    if (m_pCandWnd) {
        m_pCandWnd->UpdateAllWindow();
    }
    
    return hr;
}


/*   N O T I F Y  F I L T E R I N G  E V E N T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifyFilteringEvent( CANDUIFILTEREVENT ev )
{
    Assert( GetFunctionMgr()->GetCandFnAutoFilter()->IsEnabled() );
    
    if (m_pCandWnd) {
        m_pCandWnd->UpdateAllWindow();
    }
    
    if (GetFunctionMgr()->GetCandFnAutoFilter()->GetEventSink() != NULL) {
        return GetFunctionMgr()->GetCandFnAutoFilter()->GetEventSink()->OnFilterEvent( ev );
    }
    else {
        return S_OK;
    }
}


/*   N O T I F Y  S O R T  E V E N T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifySortEvent( CANDUISORTEVENT ev )
{
    if (m_pCandWnd) {
        m_pCandWnd->UpdateAllWindow();
    }
    
    if (GetFunctionMgr()->GetCandFnSort()->GetEventSink() != NULL) {
        return GetFunctionMgr()->GetCandFnSort()->GetEventSink()->OnSortEvent( ev );
    }
    else {
        return S_OK;
    }
}


/*   N O T I F Y  C O M P L E T E  R A W  D A T A   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifyCompleteRawData( void )
{
    HRESULT hr;
    
    Assert( GetCandListMgr()->GetCandList() != NULL );
    
    if (m_pCandWnd) {
        m_pCandWnd->UpdateAllWindow();
    }
    
    hr = CallSetResult( GetCandListMgr()->GetCandList()->GetRawDataIndex(), CAND_FINALIZED );
    
    return hr;
}


/*   N O T I F Y  C O M P L E T E  E X T R A  C A N D   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifyCompleteExtraCand( void )
{
    HRESULT hr;
    
    Assert( GetCandListMgr()->GetCandList() != NULL );
    
    if (m_pCandWnd) {
        m_pCandWnd->UpdateAllWindow();
    }
    
    hr = CallSetResult( GetCandListMgr()->GetCandList()->GetExtraCandIndex(), CAND_FINALIZED );
    
    return hr;
}


/*   C A L L  S E T  R E S U L T   */
/*------------------------------------------------------------------------------

  Send notification to TIP
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::CallSetOptionResult( int nIndex, TfCandidateResult imcr )
{
    HRESULT hr = E_FAIL;
    
    AddRef();
    
    if (!m_fInCallback) {
        ITfOptionsCandidateList *pCandList;
        
        m_fInCallback = TRUE;
        if (SUCCEEDED(GetCandListMgr()->GetOptionsCandidateList( &pCandList ))) {
            hr = pCandList->SetOptionsResult( nIndex, imcr );
            pCandList->Release();
        }
        m_fInCallback = FALSE;
    }
    
    Release();
    return hr;
}


/*   C A L L  S E T  R E S U L T   */
/*------------------------------------------------------------------------------

  Send notification to TIP
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::CallSetResult( int nIndex, TfCandidateResult imcr )
{
    HRESULT hr = E_FAIL;
    
    AddRef();
    
    if (!m_fInCallback) {
        ITfCandidateList *pCandList;
        
        m_fInCallback = TRUE;
        if (SUCCEEDED(GetCandListMgr()->GetCandidateList( &pCandList ))) {
            hr = pCandList->SetResult( nIndex, imcr );
            pCandList->Release();
        }
        m_fInCallback = FALSE;
    }
    
    Release();
    return hr;
}


//
// internal functions
//

/*   C R E A T E  C A N D  W I N D O W  O B J E C T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::CreateCandWindowObject( ITfContext *pic, CCandWindowBase** ppCandWnd )
{
    CANDUISTYLE style;
    DWORD       dwOption;
    DWORD       dwStyle;
    
    Assert( ppCandWnd );
    *ppCandWnd = NULL;
    
    if (FAILED( GetCompartmentMgr()->GetUIStyle( pic, &style ))) {
        style = CANDUISTY_LIST;
    }
    
    if (FAILED( GetCompartmentMgr()->GetUIOption( pic, &dwOption ))) {
        dwOption = 0;
    }
    
    dwStyle = 0;
    if ((dwOption & CANDUIOPT_ENABLETHEME) != 0) {
        dwStyle |= UIWINDOW_WHISTLERLOOK;
    }
    
    switch (style) {
    default:
    case CANDUISTY_LIST: {
        *ppCandWnd = new CCandWindow( this, dwStyle );
        break;
                         }
        
    case CANDUISTY_ROW: {
        *ppCandWnd = new CChsCandWindow( this, dwStyle );
        break;
                        }
    }
    
    return (*ppCandWnd != NULL) ? S_OK : E_OUTOFMEMORY;
}


/*   I N I T  C A N D  W I N D O W   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::InitCandWindow( void )
{
    CEditSession *pes;
    
    if (m_pCandWnd == NULL) {
        return E_FAIL;
    }
    
    m_pCandWnd->Initialize();
    
    // move candidate window
    
    Assert( m_pCandWnd != NULL );
    if (pes = new CEditSession( EditSessionCallback )) {
        HRESULT hr;
        
        pes->_state.u      = ESCB_RESETTARGETPOS;
        pes->_state.pv     = this;
        pes->_state.wParam = 0;
        pes->_state.pRange = m_pTargetRange;
        pes->_state.pic    = m_picParent;
        
        m_picParent->RequestEditSession( m_tidClient, pes, TF_ES_READ | TF_ES_SYNC, &hr );
        
        pes->Release();
    }
    
    // initialize candidate list
    
    m_pCandWnd->InitCandidateList();
    
    // create window
    
    m_pCandWnd->CreateWnd( m_hWndParent );
    m_pCandWnd->Show( GetPropertyMgr()->GetCandWindowProp()->IsVisible() );
    m_pCandWnd->UpdateAllWindow();
    
    return S_OK;
}


//
//
//

/*   O N  K E Y  E V E N T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::OnKeyEvent( UINT uCode, WPARAM wParam, LPARAM lParam, BOOL *pfEaten )
{
    HRESULT hr = E_FAIL;
    BOOL fHandled = FALSE;
    BYTE rgbKeyState[ 256 ];
    
    Assert( pfEaten != NULL );
    Assert( uCode == ICO_KEYDOWN || uCode == ICO_KEYUP || uCode == ICO_TESTKEYDOWN || uCode == ICO_TESTKEYUP );
    
    if (pfEaten == NULL) {
        return E_POINTER;
    }
    
    *pfEaten = FALSE;
    
    if (m_pCandWnd == NULL) {
        return hr;
    }
    
    if (GetKeyboardState( rgbKeyState )) {
        if (GetFunctionMgr()->GetCandFnAutoFilter()->IsEnabled()) {
            fHandled = FHandleFilteringKey( uCode, (int)wParam, rgbKeyState, pfEaten );
        }
        
        if (!fHandled) {
            fHandled = FHandleKeyEvent( uCode, (int)wParam, rgbKeyState, pfEaten );
        }
        
        // cancel candidate when unknown key has come
        
        if (!fHandled) {
            NotifyCancelCand();
        }
        
        hr = S_OK;
    }
    
    return hr;
}


/*   H A N D L E  K E Y  E V E N T   */
/*------------------------------------------------------------------------------

  Handling key event
  return S_OK when processed the key event.
  
------------------------------------------------------------------------------*/
BOOL CCandidateUI::FHandleKeyEvent( UINT uCode, UINT uVKey, BYTE *pbKeyState, BOOL *pfEatKey )
{
    CANDUICOMMAND cmd;
    UINT uiParam;
    
    // NOTE: KOJIW: We need to ignore keyup events to not close candidate UI
    // immediately after TIP opens CandidateUI with KEYDOWN of unknown key.
    
    if (uCode == ICO_KEYUP || uCode == ICO_TESTKEYUP) {
        return TRUE;
    }
    
    // process command on keydown
    
    CommandFromKey( uVKey, pbKeyState, &cmd, &uiParam );
    if (cmd == CANDUICMD_NONE) {
        switch (uVKey) {
        case VK_SHIFT:
        case VK_CONTROL: {
            return TRUE;
                         }
            
        default: {
            return FALSE;
                 }
        }
    }
    
    if (uCode == ICO_KEYDOWN) {
        *pfEatKey = SUCCEEDED(m_pCandWnd->ProcessCommand( cmd, uiParam ));
    }
    else {
        *pfEatKey = TRUE;
    }
    
    return *pfEatKey;
}


/*   H A N D L E  T E X T  D E L T A S   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
BOOL CCandidateUI::HandleTextDeltas( TfEditCookie ec, ITfContext *pic, IEnumTfRanges *pEnumRanges )
{
    ULONG     ulFetched;
    ITfRange *pRange;
    
    pEnumRanges->Reset();
    while (pEnumRanges->Next( 1, &pRange, &ulFetched ) == S_OK) {
        WCHAR szText[ 256 ];
        ULONG cch;
        
        // check text in the range
        
        szText[0] = L'\0';
        cch = 0;
        if (pRange != NULL) {
            pRange->GetText( ec, 0, szText, ARRAYSIZE(szText), &cch );
            pRange->Release();
        }
        
        // 
        
        if (0 < cch) {
            int i = 0;
            ULONG ich;
            
            for (ich = 0; ich < cch; ich++) {
                if ((L'0' <= szText[ich]) && (szText[ich] <= L'9')) {
                    i = i * 10 + (szText[ich] - L'0');
                }
                else if (szText[ich] == L' ') {
                    break;
                }
                else {
                    i = -1;
                    break;
                }
            }
            
            if (0 <= i) {
                if (i == 0) {
                    PostCommand( CANDUICMD_SELECTEXTRACAND, 0 );
                } 
                else {
                    PostCommand( CANDUICMD_SELECTLINE, i );
                }
            }
        }
    }
    
    return TRUE;
}


/*   P O S T  C O M M A N D   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
void CCandidateUI::PostCommand( CANDUICOMMAND cmd, INT iParam )
{
    if ((cmd != CANDUICMD_NONE) && (m_pCandWnd != NULL)) {
        PostMessage( m_pCandWnd->GetWnd(), WM_USER, (WPARAM)cmd, (LPARAM)iParam );
    }
}


//
// Auto filtering functions
//

/*   H A N D L E  F I L T E R I N G  K E Y   */
/*------------------------------------------------------------------------------

  Handle key event for filtering
  Returns TRUE to eat the event (when key has been handled)
  
------------------------------------------------------------------------------*/
BOOL CCandidateUI::FHandleFilteringKey( UINT uCode, UINT uVKey, BYTE *pbKeyState, BOOL *pfEatKey )
{
    BOOL fHandled = FALSE;
    BOOL fUpdateList = FALSE;
    
    switch (uVKey) {
    case VK_RETURN: {
        break;
                    }
        
    case VK_TAB: {
        if (GetCandListMgr()->GetCandList() != NULL) {
            if (uCode == ICO_KEYDOWN) {
                int iCandItem;
                
                iCandItem = GetCandListMgr()->GetCandList()->GetSelection();
                NotifyCompleteCand( iCandItem );
                
                *pfEatKey = TRUE;
            }
            else {
                *pfEatKey = TRUE;
            }
            
            fHandled = TRUE;
        }
        break;
                 }
        
    case VK_BACK: {
        if (uCode == ICO_KEYDOWN) {
            *pfEatKey = (DelFilteringChar( &fUpdateList ) == S_OK);
        }
        else {
            *pfEatKey = TRUE;
        }
        
        fHandled = TRUE;
        break;
                  }
        
    default: {
        WCHAR wch;
        
        // Check this is not a control + key combination as we do not want to pass this on to the filtering system.

        if (pbKeyState[VK_CONTROL] & 0x80) {
            break;
        }
        
        // convert key to char

        wch = CharFromKey( uVKey, pbKeyState );
        if (wch == L'\0') {
            break;
        }
        
        // add filtering character
        
        if (uCode == ICO_KEYDOWN) {
            *pfEatKey = (AddFilteringChar( wch, &fUpdateList ) == S_OK);
        }
        else {
            *pfEatKey = TRUE;   
        }
        
        fHandled = *pfEatKey;
        break;
             }
    }
    
    // update candidate list 
    
    if (fUpdateList) {
        *pfEatKey &= (FilterCandidateList() == S_OK);
    }
    
    return fHandled;
}


/*   A D D  F I L T E R I N G  C H A R   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::AddFilteringChar( WCHAR wch, BOOL *pfUpdateList )
{
    HRESULT hr = S_FALSE;
    LPCWSTR szFilterCur;
    WCHAR   *szFilterNew;
    int     cch;
    
    *pfUpdateList = FALSE;
    if (!GetFunctionMgr()->GetCandFnAutoFilter()->IsEnabled()) {
        return S_FALSE;
    }
    
    // append a character and set filtering string
    
    szFilterCur = GetFunctionMgr()->GetCandFnAutoFilter()->GetFilterString();
    if (szFilterCur == NULL) {
        cch = 0;
        szFilterNew = new WCHAR[ 2 ];
    }
    else {
        cch = wcslen(szFilterCur);
        szFilterNew = new WCHAR[ cch + 2 ];
    }
    
    if (szFilterNew == NULL) {
        return E_OUTOFMEMORY;
    }
    
    if (szFilterCur != NULL) {
        StringCchCopyW( szFilterNew, cch+2, szFilterCur );
    }
    *(szFilterNew + cch) = wch;
    *(szFilterNew + cch + 1) = L'\0';
    
    // Satori#3632: check if there is item matches with new filter string
    // (return S_FALSE when no item matches to pass key event to keyboard command handler)
    
    if (GetFunctionMgr()->GetCandFnAutoFilter()->FExistItemMatches( szFilterNew )) {
        GetFunctionMgr()->GetCandFnAutoFilter()->SetFilterString( szFilterNew );
        *pfUpdateList = TRUE;
        hr = S_OK;
    }
    else {

        // Only when alpha, punctation, space key is pressed,
        // and there is no alternate match because of this input, 
        // we want to notify client of NONMATCH event, so that 
        // client can inject the previous filter string to document,
        //
        // for all other key input, we don't notify that event. 

        if ( iswalpha(wch) || iswpunct(wch) )
        {
          // Notify client of non-matching.
          NotifyFilteringEvent( CANDUIFEV_NONMATCH );
          NotifyCancelCand();
        }

        hr = S_FALSE;
        
    }
    
    delete szFilterNew;
    
    //
    
    return hr;
}


/*   D E L  F I L T E R I N G  C H A R   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::DelFilteringChar( BOOL *pfUpdateList )
{
    LPCWSTR szFilterCur;
    WCHAR   *szFilterNew;
    int cch;
    
    *pfUpdateList = FALSE;
    if (!GetFunctionMgr()->GetCandFnAutoFilter()->IsEnabled()) {
        return S_OK;
    }
    
    // get current filtering string
    
    szFilterCur = GetFunctionMgr()->GetCandFnAutoFilter()->GetFilterString();
    if (szFilterCur == NULL) {
        NotifyCancelCand();
        return S_FALSE;
    }
    
    // delete last character and set filtering string
    
    cch = wcslen(szFilterCur);
    Assert( 0 < cch );
    
    szFilterNew = new WCHAR[ cch + 1 ];
    if (szFilterNew == NULL)
    {
        return E_OUTOFMEMORY;
    }
    
    StringCchCopyW( szFilterNew, cch+1, szFilterCur );
    *(szFilterNew + cch - 1) = L'\0';
    
    GetFunctionMgr()->GetCandFnAutoFilter()->SetFilterString( szFilterNew );
    
    delete szFilterNew;
    
    //
    
    *pfUpdateList = TRUE;
    return S_OK;
}


/*   F I L T E R  C A N D I D A T E  L I S T   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::FilterCandidateList( void )
{
    int nItemVisible;
    
    if (!GetFunctionMgr()->GetCandFnAutoFilter()->IsEnabled()) {
        return S_OK;
    }
    
    Assert( GetCandListMgr()->GetCandList() != NULL );
    
    // build candidate list with filtering
    
    nItemVisible = GetFunctionMgr()->GetCandFnAutoFilter()->FilterCandidateList();
    
    // close candidate when no item has been mathced
    
    if (nItemVisible == 0) {
        NotifyCancelCand();
        return E_FAIL;
    }
    
    // complete candidate when only one item matched and user typed fully
    
    if (nItemVisible == 1) {
        CCandidateItem *pCandItem;
        int iCandItem;
        BOOL fComplete = FALSE;
        
        iCandItem = GetCandListMgr()->GetCandList()->GetSelection();
        pCandItem = GetCandListMgr()->GetCandList()->GetCandidateItem( iCandItem );
        Assert( pCandItem != NULL );
        
        if ((pCandItem != NULL) && (GetFunctionMgr()->GetCandFnAutoFilter()->GetFilterString() != NULL)) {
            fComplete = (wcslen(pCandItem->GetString()) == wcslen(GetFunctionMgr()->GetCandFnAutoFilter()->GetFilterString()));
        }
        
        if (fComplete) {
            NotifyCompleteCand( iCandItem );
            return S_OK;
        }
    }
    
    // notify TIP that filtering has been updated
    
    NotifyFilteringEvent( CANDUIFEV_UPDATED );
    return S_OK;
}


/* FHandleSpellingChar */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::FHandleSpellingChar(WCHAR ch)
{
    BOOL fUpdateList = FALSE;
    
    if (S_OK == AddFilteringChar( ch, &fUpdateList ) && fUpdateList) {
        return FilterCandidateList();
    }
    return E_FAIL;
}


/*
**
** Speech handling functions
**
**
*/

/*   E N S U R E  S P E E C H   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
void CCandidateUI::EnsureSpeech(void)
{
    if (m_pSpTask)
    {
        // make sure grammars are up/running
        m_pSpTask->_LoadGrammars();
        m_pSpTask->_Activate(TRUE);
        return;
        
    }
    
    m_pSpTask = new CSpTask(this);
    if (m_pSpTask)
    {
        if (!m_pSpTask->IsSpeechInitialized())
        {
            m_pSpTask->InitializeSpeech();
        }
    }
}


/*   N O T I F Y  S P E E C H  C M D   */
/*------------------------------------------------------------------------------

  Speech command handler
  
------------------------------------------------------------------------------*/
HRESULT CCandidateUI::NotifySpeechCmd(SPPHRASE *pPhrase, const WCHAR *pszRuleName, ULONG ulRuleId)
{
    HRESULT hr = S_OK;
    CANDUICOMMAND cmd;
    UINT uiParam;
    
    if (m_pCandWnd == NULL) {
        return E_FAIL;
    }
    
    CommandFromRule( pszRuleName, &cmd, &uiParam );
    if (cmd != CANDUICMD_NONE) {
        m_pCandWnd->ProcessCommand( cmd, uiParam );
    }
    
    return hr;
}


/*   C H A R  F R O M  K E Y   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
WCHAR CCandidateUI::CharFromKey( UINT uVKey, BYTE *pbKeyState )
{
    WORD  wBuf;
    char  rgch[2];
    WCHAR wch;
    int   cch;
    int   cwch;
    
    cch = ToAscii( uVKey, 0, pbKeyState, &wBuf, 0 );
    
    rgch[0] = LOBYTE(wBuf);
    rgch[1] = HIBYTE(wBuf);
    cwch = MultiByteToWideChar( m_codepage, 0, rgch, cch, &wch, 1 );
    if (cwch != 1) {
        wch = L'\0';
    }
    
    return wch;
}


/*   G E T  K E Y  C O N F I G  P R O C   */
/*------------------------------------------------------------------------------

  
    
------------------------------------------------------------------------------*/
CCandUIKeyTable *CCandidateUI::GetKeyTableProc( ITfContext *pic )
{
    CCandUIKeyTable *pCandUIKeyTable;
    CANDUISTYLE style;
    
    // check key table in input context 
    
    if (GetCompartmentMgr() != NULL) {
        if (SUCCEEDED(GetCompartmentMgr()->GetKeyTable( pic, &pCandUIKeyTable ))) {
            return pCandUIKeyTable;
        }
    }
    
    // use default key table
    
    if (FAILED(GetCompartmentMgr()->GetUIStyle( pic, &style ))) {
        style = CANDUISTY_LIST;
    }
    
    pCandUIKeyTable = new CCandUIKeyTable();
    if (pCandUIKeyTable)
    {
        switch (style) {
        default:
        case CANDUISTY_LIST: {
            pCandUIKeyTable->SetKeyTable( rgKeyDefList, ARRAYSIZE(rgKeyDefList) );
            break;
                             }
            
        case CANDUISTY_ROW: {
            pCandUIKeyTable->SetKeyTable( rgKeyDefRow, ARRAYSIZE(rgKeyDefRow) );
            break;
                            }
        }
    }
    
    return pCandUIKeyTable;
}


/*   C O M M A N D  F R O M  K E Y   */
/*------------------------------------------------------------------------------

  Get command from key
  
------------------------------------------------------------------------------*/
void CCandidateUI::CommandFromKey( UINT uVKey, BYTE *pbKeyState, CANDUICOMMAND *pcmd, UINT *pParam )
{
    Assert( pcmd != NULL );
    Assert( pParam != NULL );
    Assert( pbKeyState != NULL );
    
    *pcmd = CANDUICMD_NONE;
    *pParam = 0;
    
    // check special keys
    
    switch( uVKey) {
    case VK_TAB: {
        *pcmd = CANDUICMD_NOP;
        break;
                 }
    }
    
    if (*pcmd != CANDUICMD_NONE) {
        return;
    }
    
    // find from key table 
    
    if (m_pCandUIKeyTable != NULL) {
        WCHAR wch = CharFromKey( uVKey, pbKeyState );
        
        m_pCandUIKeyTable->CommandFromKey( uVKey, wch, pbKeyState, GetPropertyMgr()->GetCandWindowProp()->GetUIDirection(), pcmd, pParam );
    }
}


/*   C O M M A N D  F R O M  R U L E   */
/*------------------------------------------------------------------------------

  Get command from speech rule
  
------------------------------------------------------------------------------*/
void CCandidateUI::CommandFromRule( LPCWSTR szRule, CANDUICOMMAND *pcmd, UINT *pParam )
{
    const RULEDEF *pRuleDef = NULL;
    int nRuleDef = 0;
    
    Assert( pcmd != NULL );
    Assert( pParam != NULL );
    
    *pcmd = CANDUICMD_NONE;
    *pParam = 0;
    
    //
    // find ruledef table from current state
    //
    
    // NOTE: Currently CandidateUI doesn't have candidate Menu... only Normal state is available
    if (!m_pCandWnd->FCandMenuOpen()) {
        pRuleDef = rgRuleNorm;
        nRuleDef = ARRAYSIZE(rgRuleNorm);
    }
    
    //
    // get command from ruledef table
    //
    
    if (pRuleDef != NULL) {
        while (0 < nRuleDef) {
            if (wcscmp( szRule, pRuleDef->szRuleName ) == 0) {
                *pcmd   = pRuleDef->cmd;
                *pParam = pRuleDef->uiParam;
                break;
            }
            nRuleDef--;
            pRuleDef++;
        }
    }
}


//
//
//

/*   C  T F  C A N D I D A T E  U  I  C O N T E X T  O W N E R   */
/*------------------------------------------------------------------------------

    constructor of CTfCandidateUIContextOwner

------------------------------------------------------------------------------*/
CTfCandidateUIContextOwner::CTfCandidateUIContextOwner( CCandidateUI *pCandUI )
{
    m_pCandUI = pCandUI;
    if (m_pCandUI != NULL) {
        m_pCandUI->AddRef();
    }
}


/*   ~  C  T F  C A N D I D A T E  U  I  C O N T E X T  O W N E R   */
/*------------------------------------------------------------------------------

    destructor of CTfCandidateUIContextOwner

------------------------------------------------------------------------------*/
CTfCandidateUIContextOwner::~CTfCandidateUIContextOwner( void )
{
    if (m_pCandUI != NULL) {
        m_pCandUI->Release();
    }
}


/*   Q U E R Y  I N T E R F A C E   */
/*------------------------------------------------------------------------------

    Query interface
    (IUnknown method)

------------------------------------------------------------------------------*/
STDAPI CTfCandidateUIContextOwner::QueryInterface( REFIID riid, void **ppvObj )
{
    if (ppvObj == NULL) {
        return E_POINTER;
    }

    *ppvObj = NULL;

    if (IsEqualIID( riid, IID_IUnknown ) || IsEqualIID( riid, IID_ITfCandidateUIContextOwner )) {
        *ppvObj = SAFECAST( this, ITfCandidateUIContextOwner* );
    }

    if (*ppvObj == NULL) {
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}


/*   A D D  R E F   */
/*------------------------------------------------------------------------------

    Increment reference count
    (IUnknown method)

------------------------------------------------------------------------------*/
STDAPI_(ULONG) CTfCandidateUIContextOwner::AddRef( void )
{
    m_cRef++;
    return m_cRef;
}


/*   R E L E A S E   */
/*------------------------------------------------------------------------------

    Decrement reference count and release object
    (IUnknown method)

------------------------------------------------------------------------------*/
STDAPI_(ULONG) CTfCandidateUIContextOwner::Release( void )
{
    m_cRef--;
    if (0 < m_cRef) {
        return m_cRef;
    }

    delete this;
    return 0;    
}



/*   P R O C E S S  C O M M A N D   */
/*------------------------------------------------------------------------------

    process command

------------------------------------------------------------------------------*/
STDAPI CTfCandidateUIContextOwner::ProcessCommand(CANDUICOMMAND cmd, INT iParam)
{
    HRESULT hr;

    if (m_pCandUI != NULL) {
        m_pCandUI->PostCommand( cmd, iParam );
        hr = S_OK;
    }
    else {
        hr = E_FAIL;
    }

    return hr;
}


/*   T E S T  T E X T   */
/*------------------------------------------------------------------------------

    test text

------------------------------------------------------------------------------*/
STDAPI CTfCandidateUIContextOwner::TestText(BSTR bstr, BOOL *pfHandles)
{
    HRESULT hr;
    int i;
    ULONG ich;
    ULONG cch;

    if (bstr == NULL || pfHandles == NULL) {
        hr = E_INVALIDARG;
        goto Exit;
    }

    if (m_pCandUI == NULL) {
        hr = E_FAIL;
        goto Exit;
    }

    *pfHandles = FALSE;

    i = 0;
    cch = SysStringLen( bstr );
    for (ich = 0; ich < cch; ich++) {
        if ((L'0' <= bstr[ich]) && (bstr[ich] <= L'9')) {
            i = i * 10 + (bstr[ich] - L'0');
        }
        else if (bstr[ich] == L' ') {
            break;
        }
        else {
            i = -1;
            break;
        }
    }

    if (0 <= i) {
        if (i == 0) {
            if (m_pCandUI->GetCandListMgr()->GetCandList() != NULL) {
                *pfHandles = (m_pCandUI->GetCandListMgr()->GetCandList()->GetExtraCandItem() != NULL);
            }
        }
        else {
            if (m_pCandUI->GetCandWindow() != NULL) {
                m_pCandUI->GetCandWindow()->IsIndexValid( i, pfHandles );
            }
        }
    }

    hr = S_OK;

Exit:
    return hr;
}