/**************************************************************************\
* Module Name: hotkey.c (corresponds to Win95 hotkey.c)
*
* Copyright (c) 1985 - 1999, Microsoft Corporation
*
* IME hot key management routines for imm32 dll
*
* History:
* 03-Jan-1996 wkwok       Created
\**************************************************************************/

#include "precomp.h"
#pragma hdrstop



//
// internal functions
//
BOOL CIMENonIMEToggle(HIMC hIMC, HKL hKL, HWND hWnd, LANGID langTarget);
BOOL IMENonIMEToggle( HIMC hIMC, HKL hKL, HWND hWnd, BOOL fIME, LANGID langTarget);
BOOL JCloseOpen( HIMC hIMC, HKL hKL, HWND hWnd);
BOOL CSymbolToggle(HIMC hIMC, HKL hKL, HWND hWnd);
BOOL TShapeToggle(HIMC hIMC, HKL hKL, HWND hWnd);
BOOL KEnglishHangul( HIMC hIMC);
BOOL KShapeToggle( HIMC hIMC);
BOOL KHanjaConvert( HIMC hIMC);


/***************************************************************************\
* ImmGetHotKey()
*
* Private API for IMEs and the control panel. The caller specifies
* the IME hotkey ID:dwID. If a hotkey is registered with the specified
* ID, this function returns the modifiers, vkey and hkl of the hotkey.
*
* History:
* 25-Mar-1996 TakaoK       Created
\***************************************************************************/
BOOL WINAPI ImmGetHotKey(
    DWORD dwID,
    PUINT puModifiers,
    PUINT puVKey,
    HKL   *phkl)
{
    if (puModifiers == NULL || puVKey == NULL) {
        return FALSE;
    }
    return NtUserGetImeHotKey( dwID, puModifiers, puVKey, phkl );
}

/**********************************************************************/
/* ImmSimulateHotKey()                                                */
/* Return Value:                                                      */
/*      TRUE - successful, FALSE - failure                            */
/**********************************************************************/
BOOL WINAPI ImmSimulateHotKey(  // simulate the functionality of that hot key
    HWND  hAppWnd,              // application window handle
    DWORD dwHotKeyID)
{
    HIMC hImc;
    HKL  hKL;
    BOOL fReturn;

    hImc = ImmGetContext( hAppWnd );
    hKL = GetKeyboardLayout( GetWindowThreadProcessId(hAppWnd, NULL) );
    fReturn = HotKeyIDDispatcher( hAppWnd, hImc, hKL, dwHotKeyID);
    ImmReleaseContext( hAppWnd, hImc );
    return fReturn;
}


/***************************************************************************\
* SaveImeHotKey()
*
*  Put/Remove the specified IME hotkey entry from the registry
*
* History:
* 25-Mar-1996 TakaoK       Created
\***************************************************************************/

/**********************************************************************/
/* HotKeyIDDispatcher                                                 */
/* Return Value:                                                      */
/*      TRUE - a hot key processed, FALSE - not processed             */
/**********************************************************************/
BOOL HotKeyIDDispatcher( HWND hWnd, HIMC hImc, HKL hKlCurrent, DWORD dwHotKeyID )
{
    /*
     * Dispatch the IME hotkey event for the specified hImc
     * only if the calling thread owns the hImc.
     */
    if (hImc != NULL_HIMC &&
            GetInputContextThread(hImc) != GetCurrentThreadId()) {
        return FALSE;
    }

    switch ( dwHotKeyID ) {
    case IME_CHOTKEY_IME_NONIME_TOGGLE:
        return CIMENonIMEToggle(hImc, hKlCurrent, hWnd, MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED));

    case IME_THOTKEY_IME_NONIME_TOGGLE:
        return CIMENonIMEToggle(hImc, hKlCurrent, hWnd, MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL));

    case IME_CHOTKEY_SYMBOL_TOGGLE:
    case IME_THOTKEY_SYMBOL_TOGGLE:
        return CSymbolToggle( hImc, hKlCurrent, hWnd);

    case IME_JHOTKEY_CLOSE_OPEN:
        return JCloseOpen( hImc, hKlCurrent, hWnd);

    case IME_KHOTKEY_ENGLISH:           // VK_HANGUL : English/Hangul mode
        return KEnglishHangul( hImc );

    case IME_KHOTKEY_SHAPE_TOGGLE:      // VK_JUNJA : full/half width
        return KShapeToggle( hImc );

    case IME_KHOTKEY_HANJACONVERT:      // VK_HANJA : convert hangul to hanja
        return KHanjaConvert( hImc );

    case IME_CHOTKEY_SHAPE_TOGGLE:
    case IME_THOTKEY_SHAPE_TOGGLE:
        return TShapeToggle( hImc, hKlCurrent, hWnd);

    default:
        /*
         * Direct swithing hotkey should have been handled in the kernel side.
         */
        ImmAssert(dwHotKeyID < IME_HOTKEY_DSWITCH_FIRST || dwHotKeyID > IME_HOTKEY_DSWITCH_LAST);

        if ( dwHotKeyID >= IME_HOTKEY_PRIVATE_FIRST &&
                    dwHotKeyID <= IME_HOTKEY_PRIVATE_LAST ) {

            PIMEDPI pImeDpi;
            BOOL    bRet = FALSE;

            if ( (pImeDpi = ImmLockImeDpi(hKlCurrent)) != NULL ) {

                bRet = (BOOL)(*pImeDpi->pfn.ImeEscape)( hImc,
                                                  IME_ESC_PRIVATE_HOTKEY,
                                                  (PVOID)&dwHotKeyID );
                ImmUnlockImeDpi(pImeDpi);
                return bRet;
            }
        }
    }
    return (FALSE);
}

/**********************************************************************/
/* JCloseOpen()                                                       */
/* Return Value:                                                      */
/*      TRUE - a hot key processed, FALSE - not processed             */
/**********************************************************************/
BOOL JCloseOpen(         // open/close toggle
    HIMC        hIMC,
    HKL         hCurrentKL,
    HWND        hWnd)
{

    if (ImmIsIME(hCurrentKL) &&
            LOWORD(HandleToUlong(hCurrentKL)) == MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT)) {
        //
        // If current KL is IME and its language is Japanese,
        // we only have to switch the open/close status.
        //
        ImmSetOpenStatus( hIMC, !ImmGetOpenStatus(hIMC) );
    } else {
        //
        // If current KL is not IME or its language is not Japanese,
        // we should find the Japanese IME and set it open.
        //
        if (IMENonIMEToggle(hIMC, hCurrentKL, hWnd, FALSE, MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT))) {
            //
            // Mark it so that later we can initialize the fOpen
            // as expected.
            //
            PINPUTCONTEXT pInputContext = ImmLockIMC(hIMC);

            if (pInputContext) {
                pInputContext->fdwDirty |= IMSS_INIT_OPEN;
                ImmUnlockIMC(hIMC);
            }
        }
    }
    return TRUE;

#if 0   // for your reference : old code ported from Win95
    LPINPUTCONTEXT pInputContext;
    PIMEDPI            pImeDpi;


    if ( (pInputContext = ImmLockIMC( hIMC )) == NULL ) {
    //
    // The return value is same as Win95.
    // Not happens so often any way.
    //
        return TRUE;
    }

    pImeDpi = ImmLockImeDpi( hCurrentKL );
    if ( pImeDpi != NULL ) {
    //
    // update Input Context
    //
        pInputContext->fOpen = !pInputContext->fOpen;

    //
    // notify IME
    //
        (*pImeDpi->pfn.NotifyIME)( hIMC,
                                   NI_CONTEXTUPDATED,
                                   0L,
                                   IMC_SETOPENSTATUS );
    //
    // inform UI
    //
        SendMessage(hWnd, WM_IME_NOTIFY, IMN_SETOPENSTATUS, 0L);
        SendMessage(hWnd, WM_IME_SYSTEM, IMS_SETOPENSTATUS, 0L);

        ImmUnlockIMC( hIMC );
        ImmUnlockImeDpi(pImeDpi);
        return TRUE;

    } else {

        if ( !pInputContext->fOpen ) {
            pInputContext->fOpen = TRUE;
            SendMessage(hWnd, WM_IME_NOTIFY, IMN_SETOPENSTATUS, 0L);
            SendMessage(hWnd, WM_IME_SYSTEM, IMS_SETOPENSTATUS, 0L);
        }
        ImmUnlockIMC( hIMC );

        return IMENonIMEToggle(hIMC, hCurrentKL, hWnd, FALSE);
    }
#endif
}

/***************************************************************************\
* HotkeyImmIsIME
*
* Checks whether the specified hKL is a HKL of an IME or not.
*
\***************************************************************************/

BOOL HotkeyImmIsIME(
    HKL hKL)
{
#if defined(CUAS_ENABLE)
    if (!IS_IME_KBDLAYOUT(hKL))
        return FALSE;
#else
    if (!ImmIsIME(hKL))
        return FALSE;
#endif

    return TRUE;
}

/**********************************************************************/
/* CIMENonIMEToggle()                                                 */
/* Return Value:                                                      */
/*      TRUE - a hot key processed, FALSE - not processed             */
/**********************************************************************/
BOOL CIMENonIMEToggle(   // non-IME and IME toggle
    HIMC        hIMC,
    HKL         hKlCurrent,
    HWND        hWnd,
    LANGID      langId)
{
    if (hWnd == NULL)
        return(FALSE);

    if (!HotkeyImmIsIME(hKlCurrent) || LOWORD(HandleToUlong(hKlCurrent)) != langId) 
    {
        //
        // Current keyboard layout is not IME or its language does not match.
        // Let's try to switch to our IME.
        //
        IMENonIMEToggle(hIMC, hKlCurrent, hWnd, FALSE, langId);
        return TRUE;

    } else {

        LPINPUTCONTEXT pInputContext = ImmLockIMC( hIMC );

        if ( pInputContext == NULL ) {
            //
            // returning TRUE even if we didn't change
            //
            return TRUE;
        }
        if (!pInputContext->fOpen) {
            //
            // toggle close to open
            //
            ImmSetOpenStatus(hIMC, TRUE);
            ImmUnlockIMC(hIMC);
            return TRUE;
        } else {
            ImmUnlockIMC(hIMC);
            IMENonIMEToggle(hIMC, hKlCurrent, hWnd, TRUE, 0);
            return TRUE;
        }
    }
}

/**********************************************************************/
/* IMENonIMEToggle()                                                  */
/* Return Value:                                                      */
/*      TRUE - a hot key processed, FALSE - not processed             */
/**********************************************************************/
BOOL IMENonIMEToggle(
    HIMC        hIMC,
    HKL         hCurrentKL,
    HWND        hWnd,
    BOOL        fCurrentIsIME,
    LANGID      langTarget)
{
    HKL  hEnumKL[32], hTargetKL;
    UINT nLayouts, i;
    HKL hPrevKL;

    UNREFERENCED_PARAMETER(hIMC);

    hPrevKL = (HKL)NtUserGetThreadState( UserThreadStatePreviousKeyboardLayout );

    //
    // If we find the same layout in the layout list, let's switch to
    // the layout. If we fail, let's switch to a first-found good
    // layout.
    //

    hTargetKL = NULL;
    nLayouts = GetKeyboardLayoutList(sizeof(hEnumKL)/sizeof(HKL), hEnumKL);

    // LATER:
    // Hmm, looks like we can't simply rely on hPrevKL on multiple lanugage
    // environment..
    //
    if (hPrevKL != NULL) {
        if (langTarget == 0 || LOWORD(HandleToUlong(hPrevKL)) == langTarget) {
            //
            // If langtarget is not specified, or
            // if it matches the previous langauge.
            //
            for (i = 0; i < nLayouts; i++) {
                // valid target HKL
                if (hEnumKL[i] == hPrevKL) {
                    hTargetKL = hPrevKL;
                    break;
                }
            }
        }
    }
    if (hTargetKL == NULL) {
        for (i = 0; i < nLayouts; i++) {
            // find a valid target HKL
            if (fCurrentIsIME ^ HotkeyImmIsIME(hEnumKL[i])) {
                if (langTarget != 0 && LOWORD(HandleToUlong(hEnumKL[i])) != langTarget) {
                    // If the target language is specified, check it
                    continue;
                }
                hTargetKL = hEnumKL[i];
                break;
            }
        }
    }
    if (hTargetKL != NULL && hCurrentKL != hTargetKL) {

        // depends on multilingual message and how to get the base charset
        // wait for confirmation of multiingual spec - tmp solution
        PostMessage(hWnd, WM_INPUTLANGCHANGEREQUEST, DEFAULT_CHARSET, (LPARAM)hTargetKL);
    }
    //
    // returning TRUE, even if we failed to switch
    //
    return HotkeyImmIsIME(hTargetKL);
}

/**********************************************************************/
/* CSymbolToggle()                                                    */
/* Return Value:                                                      */
/*      TRUE - a hot key processed, FALSE - not processed             */
/**********************************************************************/
BOOL CSymbolToggle(              // symbol & non symbol toggle
    HIMC        hIMC,
    HKL         hKL,
    HWND        hWnd)
{
    LPINPUTCONTEXT pInputContext;

    //
    // Return TRUE even no layout switching - Win95 behavior
    //
    if (hWnd == NULL)
        return(FALSE);

    if ( ! HotkeyImmIsIME( hKL ) ) {
        return (FALSE);
    }

    if ( (pInputContext = ImmLockIMC( hIMC )) == NULL ) {
        //
        // The return value is same as Win95.
        // Not happens so often any way.
        //
        return TRUE;
    }

    if (pInputContext->fOpen) {
        //
        // toggle the symbol mode
        //
        ImmSetConversionStatus(hIMC,
                               pInputContext->fdwConversion ^ IME_CMODE_SYMBOL,
                               pInputContext->fdwSentence);
    }
    else {
        //
        // change close -> open
        //
        ImmSetOpenStatus(hIMC, TRUE);
    }

    ImmUnlockIMC(hIMC);
    return (TRUE);

}

/**********************************************************************/
/* TShapeToggle()                                                     */
/* Return Value:                                                      */
/*      TRUE - a hot key processed, FALSE - not processed             */
/**********************************************************************/
BOOL TShapeToggle(               // fullshape & halfshape toggle
    HIMC        hIMC,
    HKL         hKL,
    HWND        hWnd)
{
    LPINPUTCONTEXT pInputContext;

    //
    // Return TRUE even no layout switching - Win95 behavior
    //
    if (hWnd == NULL)
        return(FALSE);

    if ( ! HotkeyImmIsIME( hKL ) ) {
        return (FALSE);
    }

    if ( (pInputContext = ImmLockIMC( hIMC )) == NULL ) {
        //
        // The return value is same as Win95.
        // Not happens so often any way.
        //
        return TRUE;
    }

    if (pInputContext->fOpen) {
        //
        // toggle the symbol mode
        //
        ImmSetConversionStatus(hIMC,
                               pInputContext->fdwConversion ^ IME_CMODE_FULLSHAPE,
                               pInputContext->fdwSentence);
    }
    else {
        //
        // change close -> open
        //
        ImmSetOpenStatus(hIMC, TRUE);
    }

    ImmUnlockIMC(hIMC);
    return (TRUE);
}

/**********************************************************************/
/* KEnglishHangul() - Egnlish & Hangeul toggle                       */
/* Return Value:                                                      */
/*      TRUE - a hot key processed, FALSE - not processed             */
/**********************************************************************/
BOOL KEnglishHangul( HIMC hImc )
{
    PINPUTCONTEXT pInputContext;

    if ((pInputContext = ImmLockIMC(hImc)) != NULL) {

        ImmSetConversionStatus(hImc,
                pInputContext->fdwConversion ^ IME_CMODE_HANGEUL,
                pInputContext->fdwSentence);

        if ((pInputContext->fdwConversion & IME_CMODE_HANGEUL) ||
                (pInputContext->fdwConversion & IME_CMODE_FULLSHAPE)) {
            ImmSetOpenStatus(hImc, TRUE);
        } else {
            ImmSetOpenStatus(hImc, FALSE);
        }
        ImmUnlockIMC(hImc);
        return TRUE;
    }

    return FALSE;
}

/**********************************************************************/
/* KShapeToggle() - Fullshape & Halfshape toggle                      */
/* Return Value:                                                      */
/*      TRUE - a hot key processed, FALSE - not processed             */
/**********************************************************************/
BOOL KShapeToggle( HIMC hImc )
{
    PINPUTCONTEXT pInputContext;

    if ( (pInputContext = ImmLockIMC( hImc )) != NULL ) {

        ImmSetConversionStatus(hImc,
            pInputContext->fdwConversion ^ IME_CMODE_FULLSHAPE,
            pInputContext->fdwSentence);

        if ((pInputContext->fdwConversion & IME_CMODE_HANGEUL)
                || (pInputContext->fdwConversion & IME_CMODE_FULLSHAPE))
            ImmSetOpenStatus(hImc, TRUE);
        else
            ImmSetOpenStatus(hImc, FALSE);
        ImmUnlockIMC(hImc);
        return TRUE;
    }

    return FALSE;
}

/**********************************************************************/
/* KHanjaConvert() - Hanja conversion toggle                          */
/* Return Value:                                                      */
/*      TRUE - a hot key processed, FALSE - not processed             */
/**********************************************************************/
BOOL KHanjaConvert( HIMC hImc )
{
    PINPUTCONTEXT pInputContext;

    if ( (pInputContext = ImmLockIMC( hImc )) != NULL ) {

        ImmSetConversionStatus( hImc,
                                pInputContext->fdwConversion ^ IME_CMODE_HANJACONVERT,
                                pInputContext->fdwSentence );

        ImmUnlockIMC( hImc );
        return TRUE;
    }
    return FALSE;
}