/****************************** Module*Header *****************************\
* Module Name: rtlmir.c                                                    *
*                                                                          *
* This module contains all the Right-To-Left (RTL) Mirroring support       *
* routines which are used across the whole IShell project. It abstracts    *
* platform-support routines of RTL mirroring (NT5 and Memphis) and removes *
* linkage depedenency with the Mirroring APIs.                             *
*                                                                          *
* Functions prefixed with Mirror, deal with the new Mirroring APIs         *
*                                                                          *
*                                                                          *
* Created: 01-Feb-1998 8:41:18 pm                                          *
* Author: Samer Arafeh [samera]                                            *
*                                                                          *
* Copyright (c) 1998 Microsoft Corporation                                 *
\**************************************************************************/

#include "..\stock.h"

#if (WINVER < 0x0500)
#error WINVER setting must be >= 0x0500
#endif

#ifndef DS_BIDI_RTL
#define DS_BIDI_RTL  0x8000
#endif

const DWORD dwNoMirrorBitmap = NOMIRRORBITMAP;
const DWORD dwExStyleRTLMirrorWnd = WS_EX_LAYOUTRTL;
const DWORD dwExStyleNoInheritLayout = WS_EX_NOINHERITLAYOUT; 
const DWORD dwPreserveBitmap = LAYOUT_BITMAPORIENTATIONPRESERVED;

/*
 * Remove linkage dependecy for the RTL mirroring APIs, by retreiving
 * their addresses at runtime.
 */
typedef DWORD (WINAPI *PFNGETLAYOUT)(HDC);                   // gdi32!GetLayout
typedef DWORD (WINAPI *PFNSETLAYOUT)(HDC, DWORD);            // gdi32!SetLayout
typedef BOOL  (WINAPI *PFNSETPROCESSDEFLAYOUT)(DWORD);       // user32!SetProcessDefaultLayout
typedef BOOL  (WINAPI *PFNGETPROCESSDEFLAYOUT)(DWORD*);      // user32!GetProcessDefaultLayout
typedef LANGID (WINAPI *PFNGETUSERDEFAULTUILANGUAGE)(void);  // kernel32!GetUserDefaultUILanguage
typedef BOOL (WINAPI *PFNENUMUILANGUAGES)(UILANGUAGE_ENUMPROC, DWORD, LONG_PTR); // kernel32!EnumUILanguages

typedef struct {
    LANGID LangID;
    BOOL   bInstalled;
    } MUIINSTALLLANG, *LPMUIINSTALLLANG;

#ifdef UNICODE
#define ConvertHexStringToInt ConvertHexStringToIntW
#else
#define ConvertHexStringToInt ConvertHexStringToIntA
#endif


/***************************************************************************\
* ConvertHexStringToIntA
*
* Converts a hex numeric string into an integer.
*
* History:
* 04-Feb-1998 samera    Created
\***************************************************************************/
BOOL ConvertHexStringToIntA( CHAR *pszHexNum , int *piNum )
{
    int   n=0L;
    CHAR  *psz=pszHexNum;

  
    for(n=0 ; ; psz=CharNextA(psz))
    {
        if( (*psz>='0') && (*psz<='9') )
            n = 0x10 * n + *psz - '0';
        else
        {
            CHAR ch = *psz;
            int n2;

            if(ch >= 'a')
                ch -= 'a' - 'A';

            n2 = ch - 'A' + 0xA;
            if (n2 >= 0xA && n2 <= 0xF)
                n = 0x10 * n + n2;
            else
                break;
        }
    }

    /*
     * Update results
     */
    *piNum = n;

    return (psz != pszHexNum);
}

/***************************************************************************\
* ConvertHexStringToIntW
*
* Converts a hex numeric string into an integer.
*
* History:
* 14-June-1998 msadek    Created
\***************************************************************************/
BOOL ConvertHexStringToIntW( WCHAR *pszHexNum , int *piNum )
{
    int   n=0L;
    WCHAR  *psz=pszHexNum;

  
    for(n=0 ; ; psz=CharNextW(psz))
    {
        if( (*psz>='0') && (*psz<='9') )
            n = 0x10 * n + *psz - '0';
        else
        {
            WCHAR ch = *psz;
            int n2;

            if(ch >= 'a')
                ch -= 'a' - 'A';

            n2 = ch - 'A' + 0xA;
            if (n2 >= 0xA && n2 <= 0xF)
                n = 0x10 * n + n2;
            else
                break;
        }
    }

    /*
     * Update results
     */
    *piNum = n;

    return (psz != pszHexNum);
}


/***************************************************************************\
* IsBiDiLocalizedSystemEx
*
* returns TRUE if running on a lozalized BiDi (Arabic/Hebrew) NT5 or Memphis.
* Should be called whenever SetProcessDefaultLayout is to be called.
*
* History:
* 02-Feb-1998 samera    Created
\***************************************************************************/

BOOL IsBiDiLocalizedSystemEx( LANGID *pLangID )
{
    int           iLCID=0L;
    static TRIBIT s_tbBiDi = TRIBIT_UNDEFINED;
    static LANGID s_langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);

    if (s_tbBiDi == TRIBIT_UNDEFINED)
    {
        BOOL bRet = FALSE;
        if(staticIsOS(OS_WIN2000ORGREATER))
        {
            /*
             * Need to use NT5 detection method (Multiligual UI ID)
             */
            s_langID = Mirror_GetUserDefaultUILanguage();

            if(s_langID)
            {
                WCHAR wchLCIDFontSignature[16];
                iLCID = MAKELCID(s_langID, SORT_DEFAULT );

                /*
                 * Let's verify this is a RTL (BiDi) locale. Since reg value is a hex string, let's
                 * convert to decimal value and call GetLocaleInfo afterwards.
                 * LOCALE_FONTSIGNATURE always gives back 16 WCHARs.
                 */

                if( GetLocaleInfoW( iLCID , 
                                    LOCALE_FONTSIGNATURE , 
                                    (WCHAR *) &wchLCIDFontSignature[0] ,
                                    (sizeof(wchLCIDFontSignature)/sizeof(WCHAR))) )
                {
          
                    /* Let's verify the bits we have a BiDi UI locale */
                    if(( wchLCIDFontSignature[7] & (WCHAR)0x0800) && Mirror_IsUILanguageInstalled(s_langID) )
                    {
                        bRet = TRUE;
                    }
                }
            }
        } else {

            /*
             * Check if BiDi-Memphis is running with Lozalized Resources (
             * i.e. Arabic/Hebrew systems) -It should be enabled ofcourse-.
             */
            if( (staticIsOS(OS_WIN98ORGREATER)) && (GetSystemMetrics(SM_MIDEASTENABLED)) )
            {
                HKEY          hKey;

                if( RegOpenKeyExA( HKEY_CURRENT_USER , 
                                   "Control Panel\\Desktop\\ResourceLocale" , 
                                   0, 
                                   KEY_READ, &hKey) == ERROR_SUCCESS) 
                {
                    CHAR szResourceLocale[12];
                    DWORD dwSize = sizeof(szResourceLocale);
                    RegQueryValueExA( hKey , "" , 0 , NULL, (LPBYTE)szResourceLocale , &dwSize );
                    szResourceLocale[(sizeof(szResourceLocale)/sizeof(CHAR))-1] = 0;

                    RegCloseKey(hKey);

                    if( ConvertHexStringToIntA( szResourceLocale , &iLCID ) )
                    {
                        iLCID = PRIMARYLANGID(LANGIDFROMLCID(iLCID));
                        if( (LANG_ARABIC == iLCID) || (LANG_HEBREW == iLCID) )
                        {
                            bRet = TRUE;
                            s_langID = LANGIDFROMLCID(iLCID);
                        }
                    }
                }
            }
        }

        COMPILETIME_ASSERT(sizeof(s_tbBiDi) == sizeof(long));
        //  close multiproc race on startup
        InterlockedExchange((long*)&s_tbBiDi, bRet ? TRIBIT_TRUE : TRIBIT_FALSE);
    }

    if (s_tbBiDi == TRIBIT_TRUE && pLangID)
    {
        *pLangID = s_langID;
    }
    
    return (s_tbBiDi == TRIBIT_TRUE);
}

BOOL IsBiDiLocalizedSystem( void )
{
    return IsBiDiLocalizedSystemEx(NULL);
}

/***************************************************************************\
* IsBiDiLocalizedWin95
*
* returns TRUE if running on a lozalized BiDi (Arabic/Hebrew) Win95.
* Needed for legacy operating system check for needed RTL UI elements
* For example, DefView ListView, TreeView,...etc 
* History:
* 12-June-1998 a-msadek    Created
\***************************************************************************/
BOOL IsBiDiLocalizedWin95(BOOL bArabicOnly)
{
    HKEY  hKey;
    DWORD dwType;
    BOOL  bRet = FALSE;
    CHAR  szResourceLocale[12];
    DWORD dwSize = sizeof(szResourceLocale)/sizeof(CHAR);
    int   iLCID=0L;

         /*
         * Check if BiDi-Win95 is running with Lozalized Resources (
         * i.e. Arabic/Hebrew systems) -It should be enabled ofcourse-.
         */
        if( (staticIsOS(OS_WIN95ORGREATER)) && (!staticIsOS(OS_WIN98ORGREATER)) && (GetSystemMetrics(SM_MIDEASTENABLED)) )
        {

            if( RegOpenKeyExA( HKEY_CURRENT_USER , 
                               "Control Panel\\Desktop\\ResourceLocale" , 
                               0, 
                               KEY_READ, &hKey) == ERROR_SUCCESS) 
            {
                RegQueryValueExA( hKey , "" , 0 , &dwType , (LPBYTE)szResourceLocale , &dwSize );
                szResourceLocale[(sizeof(szResourceLocale)/sizeof(CHAR))-1] = 0;

                RegCloseKey(hKey);

                if( ConvertHexStringToIntA( szResourceLocale , &iLCID ) )
                {
                    iLCID = PRIMARYLANGID(LANGIDFROMLCID(iLCID));
                    //
                    //If bArabicOnly we will return true if it a Arabic Win95 localized. 
                    //
                    if( (LANG_ARABIC == iLCID) || ((LANG_HEBREW == iLCID) && !bArabicOnly ))
                    {
                        bRet = TRUE;
                    }
                }
            }
        }
    
    return bRet;
}

/***************************************************************************\
* Mirror_IsEnabledOS
*
* returns TRUE if the mirroring APIs are enabled on the current OS.
*
* History:
* 02-Feb-1998 samera    Created
\***************************************************************************/
BOOL Mirror_IsEnabledOS( void )
{
    BOOL bRet = FALSE;

    if(staticIsOS(OS_WIN2000ORGREATER))
    {
        bRet = TRUE;
    } else if( staticIsOS(OS_WIN98ORGREATER) && GetSystemMetrics(SM_MIDEASTENABLED)) {
        bRet=TRUE;
    }

    return bRet;
}


/***************************************************************************\
* Mirror_GetUserDefaultUILanguage
*
* Reads the User UI language on NT5
*
* History:
* 22-June-1998 samera    Created
\***************************************************************************/
LANGID Mirror_GetUserDefaultUILanguage( void )
{
    LANGID langId=0;
    static PFNGETUSERDEFAULTUILANGUAGE pfnGetUserDefaultUILanguage=NULL;

    if( NULL == pfnGetUserDefaultUILanguage )
    {
        HMODULE hmod = GetModuleHandleA("KERNEL32");

        if( hmod )
            pfnGetUserDefaultUILanguage = (PFNGETUSERDEFAULTUILANGUAGE)
                                          GetProcAddress(hmod, "GetUserDefaultUILanguage");
    }

    if( pfnGetUserDefaultUILanguage )
        langId = pfnGetUserDefaultUILanguage();

    return langId;
}

/***************************************************************************\
* Mirror_IsUILanguageInstalled
*
* Verifies that the User UI language is installed on W2k
*
* History:
* 14-June-1999 msadek    Created
\***************************************************************************/
BOOL Mirror_IsUILanguageInstalled( LANGID langId )
{

    MUIINSTALLLANG MUILangInstalled = {0};
    MUILangInstalled.LangID = langId;
    
    static PFNENUMUILANGUAGES pfnEnumUILanguages=NULL;

    if( NULL == pfnEnumUILanguages )
    {
        HMODULE hmod = GetModuleHandleA("KERNEL32");

        if( hmod )
            pfnEnumUILanguages = (PFNENUMUILANGUAGES)
                                          GetProcAddress(hmod, "EnumUILanguagesW");
    }

    if( pfnEnumUILanguages )
        pfnEnumUILanguages(Mirror_EnumUILanguagesProc, 0, (LONG_PTR)&MUILangInstalled);

    return MUILangInstalled.bInstalled;
}

/***************************************************************************\
* Mirror_EnumUILanguagesProc
*
* Enumerates MUI installed languages on W2k
* History:
* 14-June-1999 msadek    Created
\***************************************************************************/

BOOL CALLBACK Mirror_EnumUILanguagesProc(LPTSTR lpUILanguageString, LONG_PTR lParam)
{
    int langID = 0;

    ConvertHexStringToInt(lpUILanguageString, &langID);

    if((LANGID)langID == ((LPMUIINSTALLLANG)lParam)->LangID)
    {
        ((LPMUIINSTALLLANG)lParam)->bInstalled = TRUE;
        return FALSE;
    }
    return TRUE;
}


/***************************************************************************\
* Mirror_IsWindowMirroredRTL
*
* returns TRUE if the window is RTL mirrored
*
* History:
* 02-Feb-1998 samera    Created
\***************************************************************************/
BOOL Mirror_IsWindowMirroredRTL( HWND hWnd )
{
    return (GetWindowLongA( hWnd , GWL_EXSTYLE ) & WS_EX_LAYOUTRTL );
}




/***************************************************************************\
* Mirror_GetLayout
*
* returns TRUE if the hdc is RTL mirrored
*
* History:
* 02-Feb-1998 samera    Created
\***************************************************************************/
DWORD Mirror_GetLayout( HDC hdc )
{
    DWORD dwRet=0;
    static PFNGETLAYOUT pfnGetLayout=NULL;

    if( NULL == pfnGetLayout )
    {
        HMODULE hmod = GetModuleHandleA("GDI32");

        if( hmod )
            pfnGetLayout = (PFNGETLAYOUT)GetProcAddress(hmod, "GetLayout");
    }

    if( pfnGetLayout )
        dwRet = pfnGetLayout( hdc );

    return dwRet;
}

DWORD Mirror_IsDCMirroredRTL( HDC hdc )
{
    return (Mirror_GetLayout( hdc ) & LAYOUT_RTL);
}



/***************************************************************************\
* Mirror_SetLayout
*
* RTL Mirror the hdc
*
* History:
* 02-Feb-1998 samera    Created
\***************************************************************************/
DWORD Mirror_SetLayout( HDC hdc , DWORD dwLayout )
{
    DWORD dwRet=0;
    static PFNSETLAYOUT pfnSetLayout=NULL;

    if( NULL == pfnSetLayout )
    {
        HMODULE hmod = GetModuleHandleA("GDI32");

        if( hmod )
            pfnSetLayout = (PFNSETLAYOUT)GetProcAddress(hmod, "SetLayout");
    }

    if( pfnSetLayout )
        dwRet = pfnSetLayout( hdc , dwLayout );

    return dwRet;
}

DWORD Mirror_MirrorDC( HDC hdc )
{
    return Mirror_SetLayout( hdc , LAYOUT_RTL );
}


/***************************************************************************\
* Mirror_SetProcessDefaultLayout
*
* Set the process-default layout.
*
* History:
* 02-Feb-1998 samera    Created
\***************************************************************************/
BOOL Mirror_SetProcessDefaultLayout( DWORD dwDefaultLayout )
{
    BOOL bRet=0;
    static PFNSETPROCESSDEFLAYOUT pfnSetProcessDefLayout=NULL;

    if( NULL == pfnSetProcessDefLayout )
    {
        HMODULE hmod = GetModuleHandleA("USER32");

        if( hmod )
            pfnSetProcessDefLayout = (PFNSETPROCESSDEFLAYOUT)
                                     GetProcAddress(hmod, "SetProcessDefaultLayout");
    }

    if( pfnSetProcessDefLayout )
        bRet = pfnSetProcessDefLayout( dwDefaultLayout );

    return bRet;
}

BOOL Mirror_MirrorProcessRTL( void )
{
    return Mirror_SetProcessDefaultLayout( LAYOUT_RTL );
}


/***************************************************************************\
* Mirror_GetProcessDefaultLayout
*
* Get the process-default layout.
*
* History:
* 26-Feb-1998 samera    Created
\***************************************************************************/
BOOL Mirror_GetProcessDefaultLayout( DWORD *pdwDefaultLayout )
{
    BOOL bRet=0;
    static PFNGETPROCESSDEFLAYOUT pfnGetProcessDefLayout=NULL;

    if( NULL == pfnGetProcessDefLayout )
    {
        HMODULE hmod = GetModuleHandleA("USER32");

        if( hmod )
            pfnGetProcessDefLayout = (PFNGETPROCESSDEFLAYOUT)
                                     GetProcAddress(hmod, "GetProcessDefaultLayout");
    }

    if( pfnGetProcessDefLayout )
        bRet = pfnGetProcessDefLayout( pdwDefaultLayout );

    return bRet;
}

BOOL Mirror_IsProcessRTL( void )
{
    DWORD dwDefLayout=0;

    return (Mirror_GetProcessDefaultLayout(&dwDefLayout) && (dwDefLayout&LAYOUT_RTL));
}

////////////////////////////////////////////////////////////////////////////
// Skip_IDorString
//
// Skips string (or ID) and returns the next aligned WORD.
////////////////////////////////////////////////////////////////////////////
PBYTE Skip_IDorString(LPBYTE pb)
{
    LPWORD pw = (LPWORD)pb;

    if (*pw == 0xFFFF)
        return (LPBYTE)(pw + 2);

    while (*pw++ != 0)
        ;

    return (LPBYTE)pw;
}

////////////////////////////////////////////////////////////////////////////
// Skip_DialogHeader
//
// Skips the dialog header and returns the next aligned WORD. 
////////////////////////////////////////////////////////////////////////////
PBYTE Skip_DialogHeader(LPDLGTEMPLATE pdt)
{
    LPBYTE pb;

    pb = (LPBYTE)(pdt + 1);

    // If there is a menu ordinal, add 4 bytes skip it. Otherwise it is a string or just a 0.
    pb = Skip_IDorString(pb);

    // Skip window class and window text, adjust to next word boundary.
    pb = Skip_IDorString(pb);    // class
    pb = Skip_IDorString(pb);    // window text

    // Skip font type, size and name, adjust to next dword boundary.
    if (pdt->style & DS_SETFONT)
    {
        pb += sizeof(WORD);
        pb = Skip_IDorString(pb);
    }
    pb = (LPBYTE)(((ULONG_PTR)pb + 3) & ~3);    // DWORD align

    return pb;
}

////////////////////////////////////////////////////////////////////////////
// EditBiDiDLGTemplate
//
// Edits a dialog template for BiDi stuff.
// Optionally, skipping some controls.
// Works only with DLGTEMPLATE.
////////////////////////////////////////////////////////////////////////////
void EditBiDiDLGTemplate(LPDLGTEMPLATE pdt, DWORD dwFlags, PWORD pwIgnoreList, int cIgnore)
{
    LPBYTE pb;
    UINT cItems;

    if (!pdt)
        return;
    // we should never get an extended template
    ASSERT (((LPDLGTEMPLATEEX)pdt)->wSignature != 0xFFFF);
    
    if(dwFlags & EBDT_NOMIRROR)
    {
        // Turn off the mirroring styles for the dialog.
        pdt->dwExtendedStyle &= ~(WS_EX_LAYOUTRTL | WS_EX_NOINHERITLAYOUT);
    }
    cItems = pdt->cdit;

    // skip DLGTEMPLATE part
    pb = Skip_DialogHeader(pdt);

    while (cItems--)
    {
        UINT cbCreateParams;
        int i = 0;
        BOOL bIgnore = FALSE;

        if(pwIgnoreList && cIgnore)
        {
            for(i = 0;i < cIgnore; i++)
            {
                if((((LPDLGITEMTEMPLATE)pb)->id == *(pwIgnoreList +i)))
                {
                    bIgnore = TRUE;
                }
            }
        }
        
        if((dwFlags & EBDT_NOMIRROR) && !bIgnore)
        {
            // Turn off the mirroring styles for this item.
            ((LPDLGITEMTEMPLATE)pb)->dwExtendedStyle &= ~(WS_EX_LAYOUTRTL | WS_EX_NOINHERITLAYOUT); 
        }    

        if((dwFlags & EBDT_FLIP) && !bIgnore)
        {
            ((LPDLGITEMTEMPLATE)pb)->x = pdt->cx - (((LPDLGITEMTEMPLATE)pb)->x + ((LPDLGITEMTEMPLATE)pb)->cx);
            // BUGBUG: Should we force RTL reading order for title as well ?
            // The client has the option of doining this already by PSH_RTLREADING
        }
        pb += sizeof(DLGITEMTEMPLATE);

        // Skip the dialog control class name.
        pb = Skip_IDorString(pb);

        // Look at window text now.
        pb = Skip_IDorString(pb);

        cbCreateParams = *((LPWORD)pb);

        // skip any CreateParams which include the generated size WORD.
        if (cbCreateParams)
            pb += cbCreateParams;

        pb += sizeof(WORD);

        // Point at the next dialog item. (DWORD aligned)
        pb = (LPBYTE)(((ULONG_PTR)pb + 3) & ~3);

        bIgnore = FALSE;
    }
}




