//
// Copyright (c) Microsoft Corporation 1993-1995
//
// common.c
//
// This files contains common utility and helper functions.
//
// History:
//  08-06-93 ScottH     Transferred from twin code
//  04-26-95 ScottH     Transferred and expanded from Briefcase code
//


#include "proj.h"
#include "common.h"


#ifdef NORTL

// Some of these are replacements for the C runtime routines.
//  This is so we don't have to link to the CRT libs.
//

/*----------------------------------------------------------
Purpose: memset

         Swiped from the C 7.0 runtime sources.

Returns: 
Cond:    
*/
LPSTR PUBLIC lmemset(
    LPSTR dst,
    char val,
    UINT count)
    {
    LPSTR start = dst;
    
    while (count--)
        *dst++ = val;
    return(start);
    }


/*----------------------------------------------------------
Purpose: memmove

         Swiped from the C 7.0 runtime sources.

Returns: 
Cond:    
*/
LPSTR PUBLIC lmemmove(
    LPSTR dst, 
    LPCSTR src, 
    int count)
    {
    LPSTR ret = dst;
    
    if (dst <= src || dst >= (src + count)) {
        /*
         * Non-Overlapping Buffers
         * copy from lower addresses to higher addresses
         */
        while (count--)
            *dst++ = *src++;
        }
    else {
        /*
         * Overlapping Buffers
         * copy from higher addresses to lower addresses
         */
        dst += count - 1;
        src += count - 1;
        
        while (count--)
            *dst-- = *src--;
        }
    
    return(ret);
    }


#endif // NORTL


#ifndef NOSTRING
// WARNING: all of these APIs do not setup DS, so you can not access
// any data in the default data seg of this DLL.
//
// do not create any global variables... talk to chrisg if you don't
// understand this

#ifdef DBCS
#define FASTCALL PASCAL
#else
#define FASTCALL _fastcall
#endif


/*----------------------------------------------------------
Purpose: Case sensitive character comparison for DBCS

Returns: FALSE if they match, TRUE if no match
Cond:    --
*/
BOOL PRIVATE ChrCmp(
    WORD w1, 
    WORD wMatch)
    {
    /* Most of the time this won't match, so test it first for speed.
    */
    if (LOBYTE(w1) == LOBYTE(wMatch))
        {
        if (IsDBCSLeadByte(LOBYTE(w1)))
            {
            return(w1 != wMatch);
            }
        return FALSE;
        }
    return TRUE;
    }


/*----------------------------------------------------------
Purpose: Case insensitive character comparison for DBCS

Returns: FALSE if match, TRUE if not
Cond:    --
*/
BOOL PRIVATE ChrCmpI(
    WORD w1, 
    WORD wMatch)
    {
    char sz1[3], sz2[3];

    if (IsDBCSLeadByte(sz1[0] = LOBYTE(w1)))
        {
        sz1[1] = HIBYTE(w1);
        sz1[2] = '\0';
        }
    else
        sz1[1] = '\0';

    *(WORD FAR *)sz2 = wMatch;
    sz2[2] = '\0';
    return lstrcmpi(sz1, sz2);
    }


/*----------------------------------------------------------
Purpose: strnicmp

         Swiped from the C 7.0 runtime sources.

Returns: 
Cond:    
*/
int PUBLIC lstrnicmp(
    LPCSTR psz1,
    LPCSTR psz2,
    UINT count)
    {
    int ch1;
    int ch2;
    int result = 0;
    
    if (count) 
        {
        do      
            {
            ch1 = (int)AnsiLower((LPSTR)MAKELONG(*psz1, 0));
            ch2 = (int)AnsiLower((LPSTR)MAKELONG(*psz2, 0));
            psz1 = AnsiNext(psz1);
            psz2 = AnsiNext(psz2);
            } while (--count && ch1 && ch2 && !ChrCmp((WORD)ch1, (WORD)ch2));
        result = ch1 - ch2;
        }
    return(result);
    }


/*----------------------------------------------------------
Purpose: My verion of atoi.  Supports hexadecimal too.
Returns: integer
Cond:    --
*/
int PUBLIC AnsiToInt(
    LPCSTR pszString)
    {
    #define IS_DIGIT(ch)    InRange(ch, '0', '9')

    int n;
    BOOL bNeg = FALSE;
    LPCSTR psz;
    LPCSTR pszAdj;

    // Skip leading whitespace
    //
    for (psz = pszString; *psz == ' ' || *psz == '\n' || *psz == '\t'; psz = AnsiNext(psz))
        ;
      
    // Determine possible explicit signage
    //  
    if (*psz == '+' || *psz == '-')
        {
        bNeg = (*psz == '+') ? FALSE : TRUE;
        psz = AnsiNext(psz);
        }

    // Or is this hexadecimal?
    //
    pszAdj = AnsiNext(psz);
    if (*psz == '0' && (*pszAdj == 'x' || *pszAdj == 'X'))
        {
        bNeg = FALSE;   // Never allow negative sign with hexadecimal numbers
        psz = AnsiNext(pszAdj);

        // Do the conversion
        //
        for (n = 0; ; psz = AnsiNext(psz))
            {
            if (IS_DIGIT(*psz))
                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;
                }
            }
        }
    else
        {
        for (n = 0; IS_DIGIT(*psz); psz = AnsiNext(psz))
            n = 10 * n + *psz - '0';
        }

    return bNeg ? -n : n;
    }    


/*----------------------------------------------------------
Purpose: Find first occurrence of character in string

Returns: Pointer to the first occurrence of ch in 
Cond:    --
*/
LPSTR PUBLIC AnsiChr(
    LPCSTR psz, 
    WORD wMatch)
    {
    for ( ; *psz; psz = AnsiNext(psz))
        {
        if (!ChrCmp(*(WORD FAR *)psz, wMatch))
            return (LPSTR)psz;
        }
    return NULL;
    }

#endif // NOSTRING


#ifndef NODIALOGHELPER

/*----------------------------------------------------------
Purpose: General front end to invoke dialog boxes
Returns: result from EndDialog
Cond:    --
*/
int PUBLIC DoModal(
    HWND hwndParent,            // owner of dialog
    DLGPROC lpfnDlgProc,        // dialog proc
    UINT uID,                   // dialog template ID
    LPARAM lParam)              // extra parm to pass to dialog (may be NULL)
    {
    int nResult = -1;

    nResult = DialogBoxParam(g_hinst, MAKEINTRESOURCE(uID), hwndParent,
        lpfnDlgProc, lParam);

    return nResult;
    }


/*----------------------------------------------------------
Purpose: Sets the rectangle with the bounding extent of the given string.
Returns: Rectangle
Cond:    --
*/
void PUBLIC SetRectFromExtent(
    HDC hdc,
    LPRECT lprect,
    LPCSTR lpcsz)
    {
    SIZE size;

    GetTextExtentPoint(hdc, lpcsz, lstrlen(lpcsz), &size);
    SetRect(lprect, 0, 0, size.cx, size.cy);
    }

#endif // NODIALOGHELPER


#ifndef NODRAWTEXT

#pragma data_seg(DATASEG_READONLY)

char const FAR c_szEllipses[] = "...";

#pragma data_seg()

// Global variables
int g_cxLabelMargin = 0;
int g_cxBorder = 0;
int g_cyBorder = 0;

COLORREF g_clrHighlightText = 0;
COLORREF g_clrHighlight = 0;
COLORREF g_clrWindowText = 0;
COLORREF g_clrWindow = 0;

HBRUSH g_hbrHighlight = 0;
HBRUSH g_hbrWindow = 0;


/*----------------------------------------------------------
Purpose: Get the system metrics we need
Returns: --
Cond:    --
*/
void PUBLIC GetCommonMetrics(
    WPARAM wParam)      // wParam from WM_WININICHANGE
    {
    if ((wParam == 0) || (wParam == SPI_SETNONCLIENTMETRICS))
        {
        g_cxBorder = GetSystemMetrics(SM_CXBORDER);
        g_cyBorder = GetSystemMetrics(SM_CYBORDER);

        g_cxLabelMargin = (g_cxBorder * 2);
        }
    }


/*----------------------------------------------------------
Purpose: Sees whether the entire string will fit in *prc.
         If not, compute the numbder of chars that will fit
         (including ellipses).  Returns length of string in
         *pcchDraw.

         Taken from COMMCTRL.

Returns: TRUE if the string needed ellipses
Cond:    --
*/
BOOL PRIVATE NeedsEllipses(
    HDC hdc, 
    LPCSTR pszText, 
    RECT * prc, 
    int * pcchDraw, 
    int cxEllipses)
    {
    int cchText;
    int cxRect;
    int ichMin, ichMax, ichMid;
    SIZE siz;

    cxRect = prc->right - prc->left;

    cchText = lstrlen(pszText);

    if (cchText == 0)
        {
        *pcchDraw = cchText;
        return FALSE;
        }

    GetTextExtentPoint(hdc, pszText, cchText, &siz);

    if (siz.cx <= cxRect)
        {
        *pcchDraw = cchText;
        return FALSE;
        }

    cxRect -= cxEllipses;

    // If no room for ellipses, always show first character.
    //
    ichMax = 1;
    if (cxRect > 0)
        {
        // Binary search to find character that will fit
        ichMin = 0;
        ichMax = cchText;
        while (ichMin < ichMax)
            {
            // Be sure to round up, to make sure we make progress in
            // the loop if ichMax == ichMin + 1.
            //
            ichMid = (ichMin + ichMax + 1) / 2;

            GetTextExtentPoint(hdc, &pszText[ichMin], ichMid - ichMin, &siz);

            if (siz.cx < cxRect)
                {
                ichMin = ichMid;
                cxRect -= siz.cx;
                }
            else if (siz.cx > cxRect)
                {
                ichMax = ichMid - 1;
                }
            else
                {
                // Exact match up up to ichMid: just exit.
                //
                ichMax = ichMid;
                break;
                }
            }

        // Make sure we always show at least the first character...
        //
        if (ichMax < 1)
            ichMax = 1;
        }

    *pcchDraw = ichMax;
    return TRUE;
    }


#define CCHELLIPSES     3
#define DT_LVWRAP       (DT_CENTER | DT_WORDBREAK | DT_NOPREFIX | DT_EDITCONTROL)

/*----------------------------------------------------------
Purpose: Draws text the shell's way.  

         Taken from COMMCTRL.

Returns: --

Cond:    This function requires TRANSPARENT background mode
         and a properly selected font.
*/
void PUBLIC MyDrawText(
    HDC hdc, 
    LPCSTR pszText, 
    RECT FAR* prc, 
    UINT flags, 
    int cyChar, 
    int cxEllipses, 
    COLORREF clrText, 
    COLORREF clrTextBk)
    {
    int cchText;
    COLORREF clrSave;
    COLORREF clrSaveBk;
    UINT uETOFlags = 0;
    RECT rc;
    char ach[MAX_PATH + CCHELLIPSES];

    // REVIEW: Performance idea:
    // We could cache the currently selected text color
    // so we don't have to set and restore it each time
    // when the color is the same.
    //
    if (!pszText)
        return;

    rc = *prc;

    // If needed, add in a little extra margin...
    //
    if (IsFlagSet(flags, MDT_EXTRAMARGIN))
        {
        rc.left  += g_cxLabelMargin * 3;
        rc.right -= g_cxLabelMargin * 3;
        }
    else
        {
        rc.left  += g_cxLabelMargin;
        rc.right -= g_cxLabelMargin;
        }

    if (IsFlagSet(flags, MDT_ELLIPSES) &&
        NeedsEllipses(hdc, pszText, &rc, &cchText, cxEllipses))
        {
        hmemcpy(ach, pszText, cchText);
        lstrcpy(ach + cchText, c_szEllipses);

        pszText = ach;

        // Left-justify, in case there's no room for all of ellipses
        //
        ClearFlag(flags, (MDT_RIGHT | MDT_CENTER));
        SetFlag(flags, MDT_LEFT);

        cchText += CCHELLIPSES;
        }
    else
        {
        cchText = lstrlen(pszText);
        }

    if (IsFlagSet(flags, MDT_TRANSPARENT))
        {
        clrSave = SetTextColor(hdc, 0x000000);
        }
    else
        {
        uETOFlags |= ETO_OPAQUE;

        if (IsFlagSet(flags, MDT_SELECTED))
            {
            clrSave = SetTextColor(hdc, g_clrHighlightText);
            clrSaveBk = SetBkColor(hdc, g_clrHighlight);

            if (IsFlagSet(flags, MDT_DRAWTEXT))
                {
                FillRect(hdc, prc, g_hbrHighlight);
                }
            }
        else 
            {
            if (clrText == CLR_DEFAULT && clrTextBk == CLR_DEFAULT)
                {
                clrSave = SetTextColor(hdc, g_clrWindowText);
                clrSaveBk = SetBkColor(hdc, g_clrWindow);

                if (IsFlagSet(flags, MDT_DRAWTEXT | MDT_DESELECTED))
                    {
                    FillRect(hdc, prc, g_hbrWindow);
                    }
                }
            else
                {
                HBRUSH hbr;

                if (clrText == CLR_DEFAULT)
                    clrText = g_clrWindowText;

                if (clrTextBk == CLR_DEFAULT)
                    clrTextBk = g_clrWindow;

                clrSave = SetTextColor(hdc, clrText);
                clrSaveBk = SetBkColor(hdc, clrTextBk);

                if (IsFlagSet(flags, MDT_DRAWTEXT | MDT_DESELECTED))
                    {
                    hbr = CreateSolidBrush(GetNearestColor(hdc, clrTextBk));
                    if (hbr)
                        {
                        FillRect(hdc, prc, hbr);
                        DeleteObject(hbr);
                        }
                    else
                        FillRect(hdc, prc, GetStockObject(WHITE_BRUSH));
                    }
                }
            }
        }

    // If we want the item to display as if it was depressed, we will
    // offset the text rectangle down and to the left
    if (IsFlagSet(flags, MDT_DEPRESSED))
        OffsetRect(&rc, g_cxBorder, g_cyBorder);

    if (IsFlagSet(flags, MDT_DRAWTEXT))
        {
        UINT uDTFlags = DT_LVWRAP;

        if (IsFlagClear(flags, MDT_CLIPPED))
            uDTFlags |= DT_NOCLIP;

        DrawText(hdc, pszText, cchText, &rc, uDTFlags);
        }
    else
        {
        if (IsFlagClear(flags, MDT_LEFT))
            {
            SIZE siz;

            GetTextExtentPoint(hdc, pszText, cchText, &siz);

            if (IsFlagSet(flags, MDT_CENTER))
                rc.left = (rc.left + rc.right - siz.cx) / 2;
            else    
                {
                ASSERT(IsFlagSet(flags, MDT_RIGHT));
                rc.left = rc.right - siz.cx;
                }
            }

        if (IsFlagSet(flags, MDT_VCENTER))
            {
            // Center vertically
            rc.top += (rc.bottom - rc.top - cyChar) / 2;
            }

        if (IsFlagSet(flags, MDT_CLIPPED))
            uETOFlags |= ETO_CLIPPED;

        ExtTextOut(hdc, rc.left, rc.top, uETOFlags, prc, pszText, cchText, NULL);
        }

    if (flags & (MDT_SELECTED | MDT_DESELECTED | MDT_TRANSPARENT))
        {
        SetTextColor(hdc, clrSave);
        if (IsFlagClear(flags, MDT_TRANSPARENT))
            SetBkColor(hdc, clrSaveBk);
        }
    }
#endif // NODRAWTEXT


#ifndef NOFILEINFO

/*----------------------------------------------------------
Purpose: Takes a DWORD value and converts it to a string, adding
         commas on the way.

         This was taken from the shell.

Returns: Pointer to buffer

Cond:    --
*/
LPSTR PRIVATE AddCommas(
    DWORD dw, 
    LPSTR pszBuffer,
    UINT cbBuffer)
    {
    char  szTemp[30];
    char  szSep[5];
    NUMBERFMT nfmt;

    nfmt.NumDigits=0;
    nfmt.LeadingZero=0;
    GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, szSep, sizeof(szSep));
    nfmt.Grouping = AnsiToInt(szSep);
    GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, szSep, sizeof(szSep));
    nfmt.lpDecimalSep = nfmt.lpThousandSep = szSep;
    nfmt.NegativeOrder= 0;

    wsprintf(szTemp, "%lu", dw);

    GetNumberFormatA(LOCALE_USER_DEFAULT, 0, szTemp, &nfmt, pszBuffer, cbBuffer);
    return pszBuffer;
    }


const short s_rgidsOrders[] = {IDS_BYTES, IDS_ORDERKB, IDS_ORDERMB, IDS_ORDERGB, IDS_ORDERTB};

/*----------------------------------------------------------
Purpose: Converts a number into a short, string format.

         This code was taken from the shell.

            532     -> 523 bytes
            1340    -> 1.3KB
            23506   -> 23.5KB
                    -> 2.4MB
                    -> 5.2GB

Returns: pointer to buffer
Cond:    --
*/
LPSTR PRIVATE ShortSizeFormat64(
    __int64 dw64, 
    LPSTR szBuf)
    {
    int i;
    UINT wInt, wLen, wDec;
    char szTemp[10], szOrder[20], szFormat[5];

    if (dw64 < 1000) 
        {
        wsprintf(szTemp, "%d", LODWORD(dw64));
        i = 0;
        goto AddOrder;
        }

    for (i = 1; i < ARRAY_ELEMENTS(s_rgidsOrders)-1 && dw64 >= 1000L * 1024L; dw64 >>= 10, i++);
        /* do nothing */

    wInt = LODWORD(dw64 >> 10);
    AddCommas(wInt, szTemp, sizeof(szTemp));
    wLen = lstrlen(szTemp);
    if (wLen < 3)
        {
        wDec = LODWORD(dw64 - (__int64)wInt * 1024L) * 1000 / 1024;
        // At this point, wDec should be between 0 and 1000
        // we want get the top one (or two) digits.
        wDec /= 10;
        if (wLen == 2)
            wDec /= 10;

        // Note that we need to set the format before getting the
        // intl char.
        lstrcpy(szFormat, "%02d");

        szFormat[2] = '0' + 3 - wLen;
        GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL,
                szTemp+wLen, sizeof(szTemp)-wLen);
        wLen = lstrlen(szTemp);
        wLen += wsprintf(szTemp+wLen, szFormat, wDec);
        }

AddOrder:
    LoadString(g_hinst, s_rgidsOrders[i], szOrder, sizeof(szOrder));
    wsprintf(szBuf, szOrder, (LPSTR)szTemp);

    return szBuf;
    }



/*----------------------------------------------------------
Purpose: Converts a number into a short, string format.

         This code was taken from the shell.

            532     -> 523 bytes
            1340    -> 1.3KB
            23506   -> 23.5KB
                    -> 2.4MB
                    -> 5.2GB

Returns: pointer to buffer
Cond:    --
*/
LPSTR PRIVATE ShortSizeFormat(DWORD dw, LPSTR szBuf)
    {
    return(ShortSizeFormat64((__int64)dw, szBuf));
    }


/*----------------------------------------------------------
Purpose: Gets the file info given a path.  If the path refers
         to a directory, then simply the path field is filled.

         If himl != NULL, then the function will add the file's
         image to the provided image list and set the image index
         field in the *ppfi.

Returns: standard hresult
Cond:    --
*/
HRESULT PUBLIC FICreate(
    LPCSTR pszPath,
    FileInfo ** ppfi,
    UINT uFlags)
    {
    HRESULT hres = ResultFromScode(E_OUTOFMEMORY);
    int cchPath;
    SHFILEINFO sfi;
    UINT uInfoFlags = SHGFI_DISPLAYNAME | SHGFI_ATTRIBUTES;
    DWORD dwAttr;

    ASSERT(pszPath);
    ASSERT(ppfi);

    // Get shell file info 
    if (IsFlagSet(uFlags, FIF_ICON))
        uInfoFlags |= SHGFI_ICON;
    if (IsFlagSet(uFlags, FIF_DONTTOUCH))
        {
        uInfoFlags |= SHGFI_USEFILEATTRIBUTES;

        // Today, FICreate is not called for folders, so this is ifdef'd out
#ifdef SUPPORT_FOLDERS
        dwAttr = IsFlagSet(uFlags, FIF_FOLDER) ? FILE_ATTRIBUTE_DIRECTORY : 0;
#else
        dwAttr = 0;
#endif
        }
    else
        dwAttr = 0;

    if (SHGetFileInfo(pszPath, dwAttr, &sfi, sizeof(sfi), uInfoFlags))
        {
        // Allocate enough for the structure, plus buffer for the fully qualified
        // path and buffer for the display name (and extra null terminator).
        cchPath = lstrlen(pszPath);

        *ppfi = GAlloc(sizeof(FileInfo)+cchPath+1-sizeof((*ppfi)->szPath)+lstrlen(sfi.szDisplayName)+1);
        if (*ppfi)
            {
            FileInfo * pfi = *ppfi;

            pfi->pszDisplayName = pfi->szPath+cchPath+1;
            lstrcpy(pfi->pszDisplayName, sfi.szDisplayName);

            if (IsFlagSet(uFlags, FIF_ICON))
                pfi->hicon = sfi.hIcon;

            pfi->dwAttributes = sfi.dwAttributes;

            // Does the path refer to a directory?
            if (FIIsFolder(pfi))
                {
                // Yes; just fill in the path field
                lstrcpy(pfi->szPath, pszPath);
                hres = NOERROR;
                }
            else
                {
                // No; assume the file exists?
                if (IsFlagClear(uFlags, FIF_DONTTOUCH))
                    {
                    // Yes; get the time, date and size of the file
                    HANDLE hfile = CreateFile(pszPath, GENERIC_READ, 
                                FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                                NULL);

                    if (hfile == INVALID_HANDLE_VALUE)
                        {
                        GFree(*ppfi);
                        *ppfi = NULL;
                        hres = ResultFromScode(E_HANDLE);
                        }
                    else
                        {
                        hres = NOERROR;

                        lstrcpy(pfi->szPath, pszPath);
                        pfi->dwSize = GetFileSize(hfile, NULL);
                        GetFileTime(hfile, NULL, NULL, &pfi->ftMod);
                        CloseHandle(hfile);
                        }
                    }
                else
                    {
                    // No; use what we have
                    hres = NOERROR;
                    lstrcpy(pfi->szPath, pszPath);
                    }
                }
            }
        }
    else if (!WPPathExists(pszPath))
        {
        // Differentiate between out of memory and file not found
        hres = E_FAIL;
        }

    return hres;
    }


/*----------------------------------------------------------
Purpose: Get some file info of the given path.
         The returned string is of the format "# bytes <date>"

         If the path is a folder, the string is empty.

Returns: FALSE if path is not found
Cond:    --
*/
BOOL PUBLIC FIGetInfoString(
    FileInfo * pfi,
    LPSTR pszBuf,
    int cchBuf)
    {
    BOOL bRet;

    ASSERT(pfi);
    ASSERT(pszBuf);

    *pszBuf = NULL_CHAR;

    if (pfi)
        {
        // Is this a file?
        if ( !FIIsFolder(pfi) )
            {
            // Yes
            char szSize[MAX_BUF_MED];
            char szDate[MAX_BUF_MED];
            char szTime[MAX_BUF_MED];
            LPSTR pszMsg;
            SYSTEMTIME st;
            FILETIME ftLocal;

            // Construct the string
            FileTimeToLocalFileTime(&pfi->ftMod, &ftLocal);
            FileTimeToSystemTime(&ftLocal, &st);
            GetDateFormatA(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szDate, sizeof(szDate));
            GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &st, NULL, szTime, sizeof(szTime));

            if (ConstructMessage(&pszMsg, g_hinst, MAKEINTRESOURCE(IDS_DATESIZELINE),
                ShortSizeFormat(FIGetSize(pfi), szSize), szDate, szTime))
                {
                lstrcpy(pszBuf, pszMsg);
                GFree(pszMsg);
                }
            else
                *pszBuf = 0;

            bRet = TRUE;
            }
        else
            bRet = FALSE;
        }
    else
        bRet = FALSE;

    return bRet;
    }


/*----------------------------------------------------------
Purpose: Set the path entry.  This can move the pfi.

Returns: FALSE on out of memory
Cond:    --
*/
BOOL PUBLIC FISetPath(
    FileInfo ** ppfi,
    LPCSTR pszPathNew,
    UINT uFlags)
    {
    ASSERT(ppfi);
    ASSERT(pszPathNew);

    FIFree(*ppfi);

    return SUCCEEDED(FICreate(pszPathNew, ppfi, uFlags));
    }


/*----------------------------------------------------------
Purpose: Free our file info struct
Returns: --
Cond:    --
*/
void PUBLIC FIFree(
    FileInfo * pfi)
    {
    if (pfi)
        {
        if (pfi->hicon)
            DestroyIcon(pfi->hicon);

        GFree(pfi);     // This macro already checks for NULL pfi condition
        }
    }


/*----------------------------------------------------------
Purpose: Convert FILETIME struct to a readable string

Returns: String 
Cond:    --
*/
void PUBLIC FileTimeToDateTimeString(
    LPFILETIME pft, 
    LPSTR pszBuf,
    int cchBuf)
    {
    SYSTEMTIME st;
    FILETIME ftLocal;

    FileTimeToLocalFileTime(pft, &ftLocal);
    FileTimeToSystemTime(&ftLocal, &st);
    GetDateFormatA(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, pszBuf, cchBuf/2);
    pszBuf += lstrlen(pszBuf);
    *pszBuf++ = ' ';
    GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, pszBuf, cchBuf/2);
    }

#endif // NOFILEINFO


#ifndef NOSYNC
CRITICAL_SECTION g_csCommon = { 0 };
DEBUG_CODE( UINT g_cRefCommonCS = 0; )

/*----------------------------------------------------------
Purpose: Waits for on object to signal.  This function "does 
         the right thing" to prevent deadlocks which can occur
         because the calculation thread calls SendMessage.

Returns: value of MsgWaitForMultipleObjects
Cond:    --
*/
DWORD PUBLIC MsgWaitObjectsSendMessage(
    DWORD cObjects,
    LPHANDLE phObjects,
    DWORD dwTimeout)
    {
    DWORD dwRet;

    while (TRUE)
        {
        dwRet = MsgWaitForMultipleObjects(cObjects, phObjects, FALSE,
                                        dwTimeout, QS_SENDMESSAGE);

        // If it is not a message, return
        if ((WAIT_OBJECT_0 + cObjects) != dwRet)
            {
            return dwRet;
            }
        else
            {
            // Process all the sent messages
            MSG msg;
            PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
            }
        }
    }


/*----------------------------------------------------------
Purpose: Initialize the critical section.

Returns: --

Cond:    Note that critical sections differ between Win95
         and NT.  On Win95, critical sections synchronize
         across processes.  On NT, they are per-process.
*/
void PUBLIC Common_InitExclusive(void)
    {
    ReinitializeCriticalSection(&g_csCommon);
    ASSERT(0 != *((LPDWORD)&g_csCommon));

#ifdef DEBUG
    g_cRefCommonCS = 0;
#endif
    }


/*----------------------------------------------------------
Purpose: Enter a critical section
Returns: --

Cond:    Note that critical sections differ between Win95
         and NT.  On Win95, critical sections synchronize
         across processes.  On NT, they are per-process.
*/
void PUBLIC Common_EnterExclusive(void)
    {
    EnterCriticalSection(&g_csCommon);
#ifdef DEBUG
    g_cRefCommonCS++;
#endif
    }


/*----------------------------------------------------------
Purpose: Leave a critical section
Returns: --

Cond:    Note that critical sections differ between Win95
         and NT.  On Win95, critical sections synchronize
         across processes.  On NT, they are per-process.
*/
void PUBLIC Common_LeaveExclusive(void)
    {
#ifdef DEBUG
    g_cRefCommonCS--;
#endif
    LeaveCriticalSection(&g_csCommon);
    }

#endif // NOSYNC


#ifndef NOMESSAGESTRING

/*----------------------------------------------------------
Purpose: Load the string (if necessary) and format the string
         properly.

Returns: A pointer to the allocated string containing the formatted
         message or
         NULL if out of memory

Cond:    free pointer with LocalFree()
*/
LPSTR PUBLIC ConstructVMessageString(
    HINSTANCE hinst, 
    LPCSTR pszMsg, 
    va_list *ArgList)
    {
    char szTemp[MAX_BUF];
    LPSTR pszRet;
    LPSTR pszRes;

    if (HIWORD(pszMsg))
        pszRes = (LPSTR)pszMsg;
    else if (LOWORD(pszMsg) && LoadString(hinst, LOWORD(pszMsg), szTemp, sizeof(szTemp)))
        pszRes = szTemp;
    else
        pszRes = NULL;

    if (pszRes)
        {
        if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
                           pszRes, 0, 0, (LPTSTR)&pszRet, 0, ArgList)) 
            {
            pszRet = NULL;
            }
        }
    else
        {
        // Bad parameter
        pszRet = NULL;
        }

    return pszRet;      // free with LocalFree()
    }


/*----------------------------------------------------------
Purpose: Constructs a formatted string.  The returned string 
         must be freed using GFree().

Returns: TRUE on success

Cond:    Free pointer with GFree()
*/
BOOL PUBLIC ConstructMessage(
    LPSTR * ppsz,
    HINSTANCE hinst, 
    LPCSTR pszMsg, ...)
    {
    BOOL bRet;
    LPSTR pszRet;
    va_list ArgList;

    va_start(ArgList, pszMsg);

    pszRet = ConstructVMessageString(hinst, pszMsg, &ArgList);

    va_end(ArgList);

    *ppsz = NULL;

    if (pszRet)
        {
        bRet = GSetString(ppsz, pszRet);
        LocalFree(pszRet);
        }
    else
        bRet = FALSE;

    return bRet;
    }


/*----------------------------------------------------------
Purpose: Invoke a message box.

Returns: ID of button that terminated the dialog
Cond:    --
*/
int PUBLIC MsgBox(
    HINSTANCE hinst,
    HWND hwndOwner,
    LPCSTR pszText,
    LPCSTR pszCaption,
    HICON hicon,            // May be NULL
    DWORD dwStyle, ...)
    {
    int iRet = -1;
    int ids;
    char szCaption[MAX_BUF];
    LPSTR pszRet;
    va_list ArgList;

    va_start(ArgList, dwStyle);
    
    pszRet = ConstructVMessageString(hinst, pszText, &ArgList);

    va_end(ArgList);

    if (pszRet)
        {
        // Is pszCaption a resource ID?
        if (0 == HIWORD(pszCaption))
            {
            // Yes; load it
            ids = LOWORD(pszCaption);
            SzFromIDS(hinst, ids, szCaption, sizeof(szCaption));
            pszCaption = szCaption;
            }

        // Invoke dialog
        if (pszCaption)
            {
            MSGBOXPARAMS mbp;

            mbp.cbSize = sizeof(mbp);
            mbp.hwndOwner = hwndOwner;
            mbp.hInstance = hinst;
            mbp.lpszText = pszRet;
            mbp.lpszCaption = pszCaption;
            mbp.dwStyle = dwStyle | MB_SETFOREGROUND;
            mbp.lpszIcon = MAKEINTRESOURCE(hicon);
            mbp.lpfnMsgBoxCallback = NULL;
            mbp.dwLanguageId = LANG_NEUTRAL;

            iRet = MessageBoxIndirect(&mbp);
            }
        LocalFree(pszRet);
        }

    return iRet;
    }

#endif // NOMESSAGESTRING


#ifndef NODEBUGHELP

#ifdef DEBUG

// Globals
DWORD g_dwBreakFlags = 0;
DWORD g_dwDumpFlags = 0;
DWORD g_dwTraceFlags = 0;


#pragma data_seg(DATASEG_READONLY)

char const FAR c_szNewline[] = "\r\n";
char const FAR c_szTrace[] = "t " SZ_MODULE "  ";
char const FAR c_szDbg[] = SZ_MODULE "  ";
char const FAR c_szAssertFailed[] = SZ_MODULE "  Assertion failed in %s on line %d\r\n";

#ifdef WANT_OLE_SUPPORT
struct _RIIDMAP
    {
    REFIID  riid;
    LPCSTR  psz;
    } const c_rgriidmap[] = {
        { &IID_IUnknown,        "IID_IUnknown" },
        { &IID_IBriefcaseStg,   "IID_IBriefcaseStg" },
        { &IID_IEnumUnknown,    "IID_IEnumUnknown" },
        { &IID_IShellBrowser,   "IID_IShellBrowser" },
        { &IID_IShellView,      "IID_IShellView" },
        { &IID_IContextMenu,    "IID_IContextMenu" },
        { &IID_IShellFolder,    "IID_IShellFolder" },
        { &IID_IShellExtInit,   "IID_IShellExtInit" },
        { &IID_IShellPropSheetExt, "IID_IShellPropSheetExt" },
        { &IID_IPersistFolder,  "IID_IPersistFolder" },
        { &IID_IExtractIcon,    "IID_IExtractIcon" },
        { &IID_IShellDetails,   "IID_IShellDetails" },
        { &IID_IDelayedRelease, "IID_IDelayedRelease" },
        { &IID_IShellLink,      "IID_IShellLink" },
        };
#endif // WANT_OLE_SUPPORT

struct _SCODEMAP
    {
    SCODE  sc;
    LPCSTR psz;
    } const c_rgscodemap[] = {
        { S_OK,             "S_OK" },
        { S_FALSE,          "S_FALSE" },
        { E_UNEXPECTED,     "E_UNEXPECTED" },
        { E_NOTIMPL,        "E_NOTIMPL" },
        { E_OUTOFMEMORY,    "E_OUTOFMEMORY" },
        { E_INVALIDARG,     "E_INVALIDARG" },
        { E_NOINTERFACE,    "E_NOINTERFACE" },
        { E_POINTER,        "E_POINTER" },
        { E_HANDLE,         "E_HANDLE" },
        { E_ABORT,          "E_ABORT" },
        { E_FAIL,           "E_FAIL" },
        { E_ACCESSDENIED,   "E_ACCESSDENIED" },
        };

#pragma data_seg()

/*----------------------------------------------------------
Purpose: Return English reason for the debug break
Returns: String
Cond:    --
*/
LPCSTR PRIVATE GetReasonString(
    DWORD flag)      // One of BF_ flags
    {
    LPCSTR psz;

    if (IsFlagSet(flag, BF_ONOPEN))
        psz = "BREAK ON OPEN\r\n";

    else if (IsFlagSet(flag, BF_ONCLOSE))
        psz = "BREAK ON CLOSE\r\n";

    else if (IsFlagSet(flag, BF_ONVALIDATE))
        psz = "BREAK ON VALIDATION FAILURE\r\n";

    else if (IsFlagSet(flag, BF_ONTHREADATT))
        psz = "BREAK ON THREAD ATTACH\r\n";

    else if (IsFlagSet(flag, BF_ONTHREADDET))
        psz = "BREAK ON THREAD DETACH\r\n";

    else if (IsFlagSet(flag, BF_ONPROCESSATT))
        psz = "BREAK ON PROCESS ATTACH\r\n";

    else if (IsFlagSet(flag, BF_ONPROCESSDET))
        psz = "BREAK ON PROCESS DETACH\r\n";

    else
        psz = c_szNewline;

    return psz;
    }


/*----------------------------------------------------------
Purpose: Perform a debug break based on the flag
Returns: --
Cond:    --
*/
void PUBLIC CommonDebugBreak(
    DWORD flag)      // One of BF_ flags
    {
    if (IsFlagSet(g_dwBreakFlags, flag))
        {
        TRACE_MSG(TF_ALWAYS, GetReasonString(flag));
        DebugBreak();
        }
    }


/*----------------------------------------------------------
Purpose: Assert failed
Returns: --
Cond:    --
*/
void PUBLIC CommonAssertFailed(
    LPCSTR pszFile, 
    int line)
    {
    LPCSTR psz;
    char ach[256];

    // Strip off path info from filename string, if present.
    //
    for (psz = pszFile + lstrlen(pszFile); psz != pszFile; psz=AnsiPrev(pszFile, psz))
        {
#ifdef  DBCS
        if ((AnsiPrev(pszFile, psz) != (psz-2)) && *(psz - 1) == '\\')
#else
        if (*(psz - 1) == '\\')
#endif
            break;
        }
    wsprintf(ach, c_szAssertFailed, psz, line);
    OutputDebugString(ach);
    
    if (IsFlagSet(g_dwBreakFlags, BF_ONVALIDATE))
        DebugBreak();
    }


/*----------------------------------------------------------
Purpose: Assert failed message only
Returns: --
Cond:    --
*/
void CPUBLIC CommonAssertMsg(
    BOOL f, 
    LPCSTR pszMsg, ...)
    {
    char ach[MAX_PATH+40];    // Largest path plus extra

    if (!f)
        {
        lstrcpy(ach, c_szTrace);
        wvsprintf(&ach[sizeof(c_szTrace)-1], pszMsg, (va_list)(&pszMsg + 1));
        OutputDebugString(ach);
        OutputDebugString(c_szNewline);
        }
    }


/*----------------------------------------------------------
Purpose: Debug spew
Returns: --
Cond:    --
*/
void CPUBLIC CommonDebugMsg(
    DWORD flag,
    LPCSTR pszMsg, ...)
    {
    char ach[MAX_PATH+40];    // Largest path plus extra

    if (TF_ALWAYS == flag || IsFlagSet(g_dwTraceFlags, flag))
        {
        lstrcpy(ach, c_szTrace);
        wvsprintf(&ach[sizeof(c_szTrace)-1], pszMsg, (va_list)(&pszMsg + 1));
        OutputDebugString(ach);
        OutputDebugString(c_szNewline);
        }
    }


#ifdef WANT_OLE_SUPPORT
/*----------------------------------------------------------
Purpose: Returns the string form of an known interface ID.

Returns: String ptr
Cond:    --
*/
LPCSTR PUBLIC Dbg_GetRiidName(
    REFIID riid)
    {
    int i;

    for (i = 0; i < ARRAY_ELEMENTS(c_rgriidmap); i++)
        {
        if (IsEqualIID(riid, c_rgriidmap[i].riid))
            return c_rgriidmap[i].psz;
        }
    return "Unknown riid";
    }
#endif


/*----------------------------------------------------------
Purpose: Returns the string form of an scode given an hresult.

Returns: String ptr
Cond:    --
*/
LPCSTR PUBLIC Dbg_GetScode(
    HRESULT hres)
    {
    int i;
    SCODE sc;

    sc = GetScode(hres);
    for (i = 0; i < ARRAY_ELEMENTS(c_rgscodemap); i++)
        {
        if (sc == c_rgscodemap[i].sc)
            return c_rgscodemap[i].psz;
        }
    return "Unknown scode";
    }


/*----------------------------------------------------------
Purpose: Returns a string safe enough to print...and I don't
         mean swear words.

Returns: String ptr
Cond:    --
*/
LPCSTR PUBLIC Dbg_SafeStr(
    LPCSTR psz)
    {
    if (psz)
        return psz;
    else
        return "NULL";
    }

#endif // DEBUG

#endif // NODEBUGHELP
