//---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation 1991-1992
//
// File: fileicon.c
//
//---------------------------------------------------------------------------
#include "shellprv.h"
#pragma  hdrstop

// REVIEW: More clean up should be done.

BOOL _ShellImageListInit(int cxIcon, int cyIcon, int cxSmIcon, int cySmIcon, UINT flags, BOOL fRestore);
BOOL _ShellImageListTerm(void);

// global shell image lists owned by shelldll

HIMAGELIST himlIcons = NULL;            // ImageList of large icons
HIMAGELIST himlIconsSmall = NULL;       // ImageList of small icons
int        g_ccIcon = 0;                // color depth of ImageLists
int        g_MaxIcons = DEF_MAX_ICONS;  // panic limit for icons in cache
int        g_lrFlags = 0;

TCHAR const g_szMaxCachedIcons[]      = TEXT("Max Cached Icons");
TCHAR const g_szShellIconSize[]       = TEXT("Shell Icon Size");
TCHAR const g_szShellSmallIconSize[]  = TEXT("Shell Small Icon Size");
TCHAR const g_szShellIconDepth[]      = TEXT("Shell Icon Bpp");
TCHAR const g_szShellIcons[]          = TEXT("Shell Icons");
TCHAR const g_szD[]                   = TEXT("%d");

//
// System imagelist - Don't change the order of this list.
// If you need to add a new icon, add it to the end of the
// array, and update shellp.h.
//
#pragma data_seg(DATASEG_READONLY)
UINT const c_SystemImageListIndexes[] = { IDI_DOCUMENT,
                                          IDI_DOCASSOC,
                                          IDI_APP,
                                          IDI_FOLDER,
                                          IDI_FOLDEROPEN,
                                          IDI_DRIVE525,
                                          IDI_DRIVE35,
                                          IDI_DRIVEREMOVE,
                                          IDI_DRIVEFIXED,
                                          IDI_DRIVENET,
                                          IDI_DRIVENETDISABLED,
                                          IDI_DRIVECD,
                                          IDI_DRIVERAM,
                                          IDI_WORLD,
                                          IDI_NETWORK,
                                          IDI_SERVER,
                                          IDI_PRINTER,
                                          IDI_MYNETWORK,
                                          IDI_GROUP,

                                          IDI_STPROGS,
                                          IDI_STDOCS,
                                          IDI_STSETNGS,
                                          IDI_STFIND,
                                          IDI_STHELP,
                                          IDI_STRUN,
                                          IDI_STSUSPD,
                                          IDI_STEJECT,
                                          IDI_STSHUTD,

                                          IDI_SHARE,
                                          IDI_LINK,
                                          IDI_READONLY,
                                          IDI_RECYCLER,
                                          IDI_RECYCLERFULL,
                                          IDI_RNA,
                                          IDI_DESKTOP,

                                          IDI_STCPANEL,
                                          IDI_STSPROGS,
                                          IDI_STPRNTRS,
                                          IDI_STFONTS,
                                          IDI_STTASKBR,

                                          IDI_CDAUDIO,
                                          IDI_TREE,
                                          IDI_STCPROGS};

#pragma data_seg()


int GetRegInt(HKEY hk, LPCTSTR szKey, int def)
{
    DWORD cb;
    TCHAR ach[20];

    if (hk == NULL)
        return def;

    ach[0] = 0;
    cb = SIZEOF(ach);
    RegQueryValueEx(hk, szKey, NULL, NULL, (LPBYTE)ach, &cb);

    if (ach[0] >= TEXT('0') && ach[0] <= TEXT('9'))
        return (int)StrToLong(ach);
    else
        return def;
}

//
// get g_MaxIcons from the registry, returning TRUE if it has changed
//
BOOL QueryNewMaxIcons(void)
{
    HKEY hkey;
    int MaxIcons = DEF_MAX_ICONS;

    if (RegOpenKey(HKEY_LOCAL_MACHINE, REGSTR_PATH_EXPLORER, &hkey) == 0)
    {
        MaxIcons = GetRegInt(hkey, g_szMaxCachedIcons, MaxIcons);
        RegCloseKey(hkey);

        if (MaxIcons < 0)
            MaxIcons = DEF_MAX_ICONS;
    }

    if (g_MaxIcons != MaxIcons)
    {
        g_MaxIcons = MaxIcons;
        return TRUE;
    }

    return FALSE;
}

//
// Initializes shared resources for Shell_GetIconIndex and others
//
BOOL WINAPI FileIconInit( BOOL fRestoreCache )
{
    BOOL fNotify = FALSE;
    BOOL fInit = FALSE;
    HKEY hkey;
    int cxIcon, cyIcon, ccIcon, cxSmIcon, cySmIcon, res;

    QueryNewMaxIcons(); // in case the size of the icon cache has changed

    ccIcon   = 0;
    cxIcon   = GetSystemMetrics(SM_CXICON);
    cyIcon   = GetSystemMetrics(SM_CYICON);
////cxSmIcon = GetSystemMetrics(SM_CXSMICON);
////cySmIcon = GetSystemMetrics(SM_CYSMICON);
    cxSmIcon = GetSystemMetrics(SM_CXICON) / 2;
    cySmIcon = GetSystemMetrics(SM_CYICON) / 2;

    //
    //  get the user prefered icon size (and color depth) from the
    //  registry.
    //
    if (RegOpenKey(HKEY_CURRENT_USER, REGSTR_PATH_METRICS, &hkey) == 0)
    {
        cxIcon   = GetRegInt(hkey, g_szShellIconSize, cxIcon);
        cxSmIcon = GetRegInt(hkey, g_szShellSmallIconSize, cxSmIcon);
        ccIcon   = GetRegInt(hkey, g_szShellIconDepth, ccIcon);

        cyIcon   = cxIcon;      // icons are always square
        cySmIcon = cxSmIcon;

        RegCloseKey(hkey);
    }

    res = (int)GetCurColorRes();

    if (ccIcon > res)
        ccIcon = 0;

    if (res <= 8)
        ccIcon = 0; // wouldn't have worked anyway

#if 0
    //
    // use a 8bpp imagelist on a HiColor device iff we will
    // be stretching icons.
    //
    if (res > 8 && ccIcon == 0 && (cxIcon != GetSystemMetrics(SM_CXICON) ||
         cxSmIcon != GetSystemMetrics(SM_CXICON)/2))
    {
        ccIcon = 8;
    }
#endif

    ENTERCRITICAL

    //
    // if we already have a icon cache make sure it is the right size etc.
    //
    if (himlIcons)
    {
        // cant change this while running sorry.
        ccIcon = g_ccIcon;

        if (g_cxIcon   == cxIcon &&
            g_cyIcon   == cyIcon &&
            g_cxSmIcon == cxSmIcon &&
            g_cySmIcon == cySmIcon &&
            g_ccIcon   == ccIcon)
        {
            fInit = TRUE;
            goto Exit;
        }

        FlushIconCache();
        FlushFileClass();

        // make sure every one updates.
        fNotify = TRUE;
    }

    g_cxIcon   = cxIcon;
    g_cyIcon   = cyIcon;
    g_ccIcon   = ccIcon;
    g_cxSmIcon = cxSmIcon;
    g_cySmIcon = cySmIcon;

    if (res > 4 && g_ccIcon <= 4)
        g_lrFlags = LR_VGACOLOR;
    else
        g_lrFlags = 0;

    DebugMsg(DM_TRACE, TEXT("IconCache: Size=%dx%d SmSize=%dx%d Bpp=%d"), cxIcon, cyIcon, cxSmIcon, cySmIcon, (ccIcon&ILC_COLOR));

    if (g_iLastSysIcon == 0)        // Keep track of which icons are perm.
    {
        if (fRestoreCache)
            g_iLastSysIcon = II_LASTSYSICON;
        else
            g_iLastSysIcon = (II_OVERLAYLAST - II_OVERLAYFIRST) + 1;
    }

    // try to restore the icon cache (if we have not loaded it yet)
    if (himlIcons != NULL || !fRestoreCache || !_IconCacheRestore(g_cxIcon, g_cyIcon, g_cxSmIcon, g_cySmIcon, g_ccIcon))
    {
        if (!_ShellImageListInit(g_cxIcon, g_cyIcon, g_cxSmIcon, g_cySmIcon, g_ccIcon, fRestoreCache))
            goto Exit;
    }

    fInit = TRUE;

Exit:
    LEAVECRITICAL

    if (fInit && fNotify)
    {
        DebugMsg(DM_TRACE, TEXT("IconCache: icon size has changed sending SHCNE_UPDATEIMAGE(-1)..."));
        SHChangeNotify(SHCNE_UPDATEIMAGE, SHCNF_DWORD, (LPCVOID)-1, NULL);
    }

    return fInit;
}

void FileIconTerm(void)
{
    ENTERCRITICAL

    _ShellImageListTerm();

    LEAVECRITICAL
}

/*************************************************************************
 *************************************************************************/

BOOL _ShellImageListInit(int cxIcon, int cyIcon, int cxSmIcon, int cySmIcon, UINT flags, BOOL fRestore)
{
    int i;
    TCHAR szModule[MAX_PATH];
    HKEY hkeyIcons;

    ASSERTCRITICAL;

    if (himlIcons == NULL) {
        himlIcons = ImageList_Create(cxIcon, cyIcon, ILC_MASK|ILC_SHARED|flags, 0, 32);
    }
    else {
        ImageList_Remove(himlIcons, -1);
        ImageList_SetIconSize(himlIcons, cxIcon, cyIcon);
    }

    if (himlIcons == NULL) {
        return FALSE;
    }

    if (himlIconsSmall == NULL) {
        himlIconsSmall = ImageList_Create(cxSmIcon, cySmIcon, ILC_MASK|ILC_SHARED|flags, 0, 32);
    }
    else {
        ImageList_Remove(himlIconsSmall, -1);
        ImageList_SetIconSize(himlIconsSmall, cxSmIcon, cySmIcon);
    }

    if (himlIconsSmall == NULL) {
        ImageList_Destroy(himlIcons);
        himlIcons = NULL;
        return FALSE;
    }

    // set the bk colors to COLOR_WINDOW since this is what will
    // be used most of the time as the bk for these lists (cabinet, tray)
    // this avoids having to do ROPs when drawing, thus making it fast

    ImageList_SetBkColor(himlIcons, GetSysColor(COLOR_WINDOW));
    ImageList_SetBkColor(himlIconsSmall, GetSysColor(COLOR_WINDOW));

    GetModuleFileName(HINST_THISDLL, szModule, ARRAYSIZE(szModule));

    // WARNING: this code assumes that these icons are the first in
    // our RC file and are in this order and these indexes correspond
    // to the II_ constants in shell.h.

    hkeyIcons =
        SHGetExplorerSubHkey(HKEY_LOCAL_MACHINE, g_szShellIcons, FALSE);

    for (i = 0; i < ARRAYSIZE(c_SystemImageListIndexes); i++) {

        HICON hIcon=NULL;
        HICON hSmallIcon=NULL;
        int iIndex;

        //
        // Load all of the icons with fRestore == TRUE, or only the overlays
        // if fRestore == FALSE.
        //
        if (fRestore || (i >= II_OVERLAYFIRST && i <= II_OVERLAYLAST))
        {
            //
            // check to see if icon is overridden in the registry
            //
            if (hkeyIcons)
            {
                TCHAR val[10];
                TCHAR ach[MAX_PATH];
                DWORD cb=MAX_PATH;
                int iIcon;

                wsprintf(val, g_szD, i);

                ach[0] = 0;
                RegQueryValueEx(hkeyIcons, val, NULL, NULL, (LPBYTE)ach, &cb);

                if (ach[0])
                {
                    HICON hIcons[2] = {0, 0};

                    iIcon = PathParseIconLocation(ach);

                    ExtractIcons(ach, iIcon,
                        MAKELONG(g_cxIcon,g_cxSmIcon),
                        MAKELONG(g_cyIcon,g_cySmIcon),
                        hIcons, NULL, 2, g_lrFlags);

                    hIcon = hIcons[0];
                    hSmallIcon = hIcons[1];

                    if (hIcon)
                    {
                        DebugMsg(DM_TRACE, TEXT("ShellImageListInit: Got default icon #%d from registry: %s,%d"), i, ach, iIcon);
                    }
                }
            }


            if (hIcon == NULL)
            {
                hIcon      = LoadImage(HINST_THISDLL, MAKEINTRESOURCE(c_SystemImageListIndexes[i]), IMAGE_ICON, cxIcon, cyIcon, g_lrFlags);
                hSmallIcon = LoadImage(HINST_THISDLL, MAKEINTRESOURCE(c_SystemImageListIndexes[i]), IMAGE_ICON, cxSmIcon, cySmIcon, g_lrFlags);
            }

            if (hIcon)
            {
                iIndex = SHAddIconsToCache(hIcon, hSmallIcon, szModule, i, 0);
                Assert(iIndex == i);     // assume index

                if (i >= II_OVERLAYFIRST && i <= II_OVERLAYLAST)
                {
                    ImageList_SetOverlayImage(himlIcons,      iIndex, i - II_OVERLAYFIRST + 1);
                    ImageList_SetOverlayImage(himlIconsSmall, iIndex, i - II_OVERLAYFIRST + 1);
                }
            }
        }
    }

    if (hkeyIcons)
        RegCloseKey(hkeyIcons);

    return TRUE;
}

BOOL _ShellImageListTerm(void)
{
    ENTERCRITICAL
    if (himlIcons) {
        ImageList_Destroy(himlIcons);
        himlIcons = NULL;
    }

    if (himlIconsSmall) {
        ImageList_Destroy(himlIconsSmall);
        himlIconsSmall = NULL;
    }
    LEAVECRITICAL
    return TRUE;
}

// get a hold of the system image lists

BOOL WINAPI Shell_GetImageLists(HIMAGELIST *phiml, HIMAGELIST *phimlSmall)
{
    FileIconInit( FALSE );  // make sure they are created and the right size.

    if (phiml)
        *phiml = himlIcons;

    if (phimlSmall)
        *phimlSmall = himlIconsSmall;

    return TRUE;
}


void WINAPI Shell_SysColorChange(void)
{
    COLORREF clrWindow;

    ENTERCRITICAL
    clrWindow = GetSysColor(COLOR_WINDOW);
    ImageList_SetBkColor(himlIcons     , clrWindow);
    ImageList_SetBkColor(himlIconsSmall, clrWindow);
    LEAVECRITICAL
}

// simulate the document icon by crunching a copy of an icon and putting it in the
// middle of our default document icon, then add it to the passsed image list
//
// in:
//      hIcon   icon to use as a basis for the simulation
//
// returns:
//      hicon

HICON SimulateDocIcon(HIMAGELIST himl, HICON hIcon, BOOL fSmall)
{
    int cx = fSmall ? g_cxSmIcon : g_cxIcon;
    int cy = fSmall ? g_cxSmIcon : g_cxIcon;

    HDC hdc;
    HDC hdcMem;
    HBITMAP hbmMask;
    HBITMAP hbmColor;
    HBITMAP hbmT;
    ICONINFO ii;
    UINT iIndex;

    if (himl == NULL || hIcon == NULL)
        return NULL;

    hdc = GetDC(NULL);
    hdcMem = CreateCompatibleDC(hdc);
    hbmColor = CreateCompatibleBitmap(hdc, cx, cy);
    hbmMask = CreateBitmap(cx, cy, 1, 1, NULL);
    ReleaseDC(NULL, hdc);

    hbmT = SelectObject(hdcMem, hbmMask);
    iIndex = Shell_GetCachedImageIndex(c_szShell32Dll, II_DOCNOASSOC, 0);
    ImageList_Draw(himl, iIndex, hdcMem, 0, 0, ILD_MASK);

    SelectObject(hdcMem, hbmColor);
    ImageList_DrawEx(himl, iIndex, hdcMem, 0, 0, 0, 0, RGB(0,0,0), CLR_DEFAULT, ILD_NORMAL);

    //BUGBUG this assumes the generic icon is white
    PatBlt(hdcMem, cx/4-1, cy/4-1, cx/2+(fSmall?2:4), cy/2+2, WHITENESS);
    DrawIconEx(hdcMem, cx/4, cy/4, hIcon, cx/2, cy/2, 0, NULL, DI_NORMAL);

    SelectObject(hdcMem, hbmT);
    DeleteDC(hdcMem);

    ii.fIcon    = TRUE;
    ii.xHotspot = 0;
    ii.yHotspot = 0;
    ii.hbmColor = hbmColor;
    ii.hbmMask  = hbmMask;
    hIcon = CreateIconIndirect(&ii);

    DeleteObject(hbmColor);
    DeleteObject(hbmMask);

    return hIcon;
}

// add icons to the system imagelist (icon cache) and put the location
// in the location cache
//
// in:
//      hIcon, hIconSmall       the icons, hIconSmall can be NULL
//      pszIconPath             locations (for location cache)
//      iIconIndex              index in pszIconPath (for location cache)
//      uIconFlags              GIL_ flags (for location cahce)
// returns:
//      location in system image list
//
int SHAddIconsToCache(HICON hIcon, HICON hIconSmall, LPCTSTR pszIconPath, int iIconIndex, UINT uIconFlags)
{
    int iImage;
    int iImageSmall;
    int iImageFree;

    Assert(himlIcons);

    if (hIcon == NULL)
    {
        SHDefExtractIcon(pszIconPath, iIconIndex, uIconFlags, &hIcon, &hIconSmall, 0);
    }

    if (hIcon == NULL)
        return -1;

    if (hIconSmall == NULL)
        hIconSmall = hIcon;  // ImageList_AddIcon will shrink for us

    ENTERCRITICAL

    iImageFree = GetFreeImageIndex();

    iImage = ImageList_ReplaceIcon(himlIcons, iImageFree, hIcon);

    if (iImage >= 0)
    {
        iImageSmall = ImageList_ReplaceIcon(himlIconsSmall, iImageFree, hIconSmall);

        if (iImageSmall < 0)
        {
            DebugMsg(DM_TRACE, TEXT("AddIconsToCache() ImageList_AddIcon failed (small)"));
            // only remove it if it was added at the end otherwise all the
            // index's above iImage will change.
            // ImageList_ReplaceIcon should only fail on the end anyway.
            if (iImageFree == -1)
                ImageList_Remove(himlIcons, iImage);   // remove big
            iImage = -1;
        }
        else
        {
            Assert(iImageSmall == iImage);
        }
    }
    else
    {
        DebugMsg(DM_TRACE, TEXT("AddIconsToCache() ImageList_AddIcon failed"));
        iImage = -1;
    }

    if (iImage >= 0)
        AddToIconTable(pszIconPath, iIconIndex, uIconFlags, iImage);

    LEAVECRITICAL

    if (hIcon)
        DestroyIcon(hIcon);

    if (hIconSmall && hIcon != hIconSmall)
        DestroyIcon(hIconSmall);

    return iImage;
}

//
//  default handler to extract a icon from a file
//
//  supports GIL_SIMULATEDOC
//
//  returns S_OK if success
//  returns S_FALSE if the file has no icons (or not the asked for icon)
//  returns E_FAIL for files on a slow link.
//  returns E_FAIL if cant access the file
//
//  BUGBUG: Returning S_FALSE for executables that have no icons causes
//  BUGBUG: us to extract the icon twice, see DefViewX.c / _ILIndexGivenIcon
//
//  LOWORD(nIconSize) = normal icon size
//  HIWORD(nIconSize) = smal icon size
//
HRESULT SHDefExtractIcon(LPCTSTR szIconFile, int iIndex, UINT uFlags,
        HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize)
{
    HICON hIcons[2] = {0, 0};
    UINT u;

#ifdef DEBUG
    TCHAR ach[128];
    GetModuleFileName(HINST_THISDLL, ach, ARRAYSIZE(ach));

    if (lstrcmpi(szIconFile, ach) == 0 && iIndex >= 0)
    {
        DebugMsg(DM_TRACE, TEXT("re-extracting %d from SHELL32.DLL"), iIndex);
        Assert(0);
    }
#endif

    //
    //  get the icon from the file
    //
    if (PathIsSlow(szIconFile))
    {
        DebugMsg(DM_TRACE, TEXT("not extracting icon from '%s' because of slow link"), szIconFile);
        return E_FAIL;
    }

    //
    // nIconSize == 0 means use the default size.
    // Backup is passing nIconSize == 1 need to support them too.
    //
    if (nIconSize <= 2)
        nIconSize = MAKELONG(g_cxIcon, g_cxSmIcon);

    if (uFlags & GIL_SIMULATEDOC)
    {
        HICON hIconSmall;

        u = ExtractIcons(szIconFile, iIndex, g_cxSmIcon, g_cySmIcon,
            &hIconSmall, NULL, 1, g_lrFlags);

        if (u == -1)
            return E_FAIL;

        hIcons[0] = SimulateDocIcon(himlIcons, hIconSmall, FALSE);
        hIcons[1] = SimulateDocIcon(himlIconsSmall, hIconSmall, TRUE);

        if (hIconSmall)
            DestroyIcon(hIconSmall);
    }
    else
    {
        u = ExtractIcons(szIconFile, iIndex, nIconSize, nIconSize,
            hIcons, NULL, 2, g_lrFlags);

        if (u == -1)
            return E_FAIL;
    }

    *phiconLarge = hIcons[0];
    *phiconSmall = hIcons[1];

    return u==0 ? S_FALSE : S_OK;
}

//
// in:
//      pszIconPath     file to get icon from (eg. cabinet.exe)
//      iIconIndex      icon index in pszIconPath to get
//      uIconFlags      GIL_ values indicating simulate doc icon, etc.

int WINAPI Shell_GetCachedImageIndex(LPCTSTR pszIconPath, int iIconIndex, UINT uIconFlags)
{
    int iImageIndex;

    if (himlIcons == NULL)
    {
        FileIconInit( FALSE );
    }

    iImageIndex = LookupIconIndex(PathFindFileName(pszIconPath), iIconIndex, uIconFlags);

    if (iImageIndex == -1)
    {
        iImageIndex = SHAddIconsToCache(NULL, NULL, pszIconPath, iIconIndex, uIconFlags);
    }

    return iImageIndex;
}