
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993-1996 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: util.c
*
* Contains miscellaneous utility functions for dlgedit.
*
* Functions:
*   MyAlloc()
*   MyRealloc()
*   MyFree()
*   IsValue()
*   HasBlanks()
*   valtoi()
*   axtoi()
*   Myitoa()
*   itoax()
*   IsUniqueID()
*   NextID()
*   Message()
*   ClientToScreenRect()
*   ScreenToClientRect()
*   DUToWinPoint()
*   WinToDUPoint()
*   DUToWinRect()
*   WinToDURect()
*   MapDlgClientPoint()
*   MapWindowPoint()
*   MyMapWindowRect()
*   GetChildRect()
*   CenterWindow()
*   FitRectToScreen()
*   ids()
*   PixelsToPointSize()
*   PointSizeToPixels()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

#include <stdarg.h>
#include <ctype.h>


#define CBOVERHEAD      (sizeof(INT)+sizeof(INT)+sizeof(INT))
#define MEMSIGHEAD      0x1234
#define MEMSIGTAIL      0x5678


STATICFN BOOL IDUsedByCtrl(INT id);



/****************************************************************************
* MyAlloc
*
* Does a local alloc.
* 
* Arguments:
*   INT cbAlloc - number of bytes to allocate.
*
* Returns:
*
* A pointer to the memory if succesful; otherwise, NULL.
*
****************************************************************************/

VOID *MyAlloc(
    INT cbAlloc)
{
    register HANDLE hMem;

    if (hMem = LocalAlloc(LMEM_FIXED, cbAlloc)) {
        return (VOID *)hMem;
    }
    else {
        MessageBeep(0);
        Message(MSG_OUTOFMEMORY);

        return NULL;
    }
}


/****************************************************************************
* MyRealloc
*
* reallocates memory and returns a pointer to the memory block.
*
****************************************************************************/

VOID *MyRealloc(
    VOID *npMem,
    INT cbNewAlloc)
{
    npMem = (VOID *)LocalReAlloc((HANDLE)npMem, cbNewAlloc, LMEM_MOVEABLE);

    if (!npMem) {
        MessageBeep(0);
        Message(MSG_OUTOFMEMORY);

        return NULL;
    }

    return npMem;
}

/****************************************************************************
* MyFree
*
* Frees the specified memory.
*
****************************************************************************/

VOID *MyFree(
    VOID *npMem)
{
    if (LocalFree((HANDLE)npMem)) {
        MessageBeep(0);
        Message(MSG_MEMERROR);

        return npMem;
    }

    return NULL;
}



/************************************************************************
* IsValue
*
* This function tells you if the string you give it represents a
* valid value or not.  For this purpose, a valid value can only
* have the ascii characters from '0' to '9' with possibly the
* first character being '-'.  Or be a Hex Number, with 0x preceeding
* it.
*
* Arguments:
*     LPTSTR pszValue  = The string to test.
*
* Returns:
*     (0 == 0) if szValue represents a value.
*     (c == 0) if szValue does not represent a value where c is
*               non-zero.
*
*
************************************************************************/

BOOL IsValue(
    LPTSTR pszValue)
{
    INT i;

    if (pszValue[0] == CHAR_0 &&
            (pszValue[1] == CHAR_X || pszValue[1] == CHAR_CAP_X)) {
        for (i = 2; iswxdigit(pszValue[i]); i++)
            ;
    }
    else {
        for (i = 0; iswdigit(pszValue[i]) ||
                (i == 0 && pszValue[i] == CHAR_MINUS); i++)
            ;
    }

    return (pszValue[i] == 0);
}



/************************************************************************
* HasBlanks
*
* This function returns TRUE if the given string has imbedded
* blanks in it.
*
* Arguments:
*   LPTSTR psz - String to check.
*
*
************************************************************************/

BOOL HasBlanks(
    LPTSTR psz)
{
    while (*psz)
        if (*psz == CHAR_SPACE)
            return TRUE;
        else
            psz = CharNext(psz);

    return FALSE;
}



/************************************************************************
* valtoi
*
* Takes a string and returns its integer representation.
* This function handles both hex ("0x1234") and decimal ("1234")
* strings transparently.
*
* Arguments:
*   LPTSTR pszValue = The string to convert.
*
*
************************************************************************/

INT valtoi(
    LPTSTR pszValue)
{
    return (pszValue[0] == CHAR_0 &&
            (pszValue[1] == CHAR_CAP_X || pszValue[1] == CHAR_X)) ?
            axtoi(&pszValue[2]) : awtoi(pszValue);
}



/************************************************************************
* axtoi
*
* This function converts a null terminated ascii string for a
* hex number to its integer value.  Should just be the number
* with no preceeding "0x" or trailing "H".  Garbage will result
* if there are non-hex digits in the string.  Hex digits are:
* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, a, b, c, d, e, f.
* Non-hex digits will be treated like '0'.
*
* Arguments:
*     LPTSTR pch  = The null terminated hex string.
*
* Returns:
*     The integer value represented by the given ascii string.
*
************************************************************************/

INT axtoi(
    LPTSTR pch)
{
    register TCHAR ch;
    register INT n = 0;

    while((ch = *pch++) != 0) {
        if (iswdigit(ch))
            ch -= CHAR_0;
        else if (ch >= CHAR_CAP_A && ch <= CHAR_CAP_F)
            ch += (TCHAR)(10 - CHAR_CAP_A);
        else if (ch >= CHAR_A && ch <= CHAR_F)
            ch += (TCHAR)(10 - CHAR_A);
        else
            ch = (TCHAR)0;

        n = 16 * n + ch;
    }

    return n;
}



/************************************************************************
* Myitoa
*
* This function converts a word to an ascii string.  It builds either
* a decimal string or a hex string, based on whether Hex Mode is on
* when it is called.
*
* Arguments:
*   INT n      - The number to convert.
*   LPTSTR psz - The buffer to put the string in, should have at least
*                17 bytes to take the string.
*
************************************************************************/

VOID Myitoa(
    INT n,
    LPTSTR psz)
{
    if (gfHexMode)
        itoax(n, psz);
    else
        itoaw(n, psz, 10);
}



/************************************************************************
* itoax
*
* This function converts an int, 'n', to an ascii string
* representing it as a 4 digit hex number with "0x" preceeding it.
*
* Arguments:
*   INT n          = The number to convert.
*   LPTSTR pszBuff = The buffer to put the string in, must
*                    be at least 7 characters long to take the string.
*
*
************************************************************************/

VOID itoax(
    INT n,
    LPTSTR pszBuff)
{
    INT i;
    INT j;

    pszBuff[0] = CHAR_0;
    pszBuff[1] = CHAR_X;

    for (i = 5; i > 1; i--) {
        j = n & 15;

        if (j > 9)
            pszBuff[i] = (TCHAR)(j + (CHAR_A - 10));
        else
            pszBuff[i] = (TCHAR)(j + CHAR_0);

        n = n >> 4;
    }

    pszBuff[6] = CHAR_NULL;
}



/************************************************************************
* NextID
*
* This function returns the next available id.
*
* For dialogs, it starts at 100 and increments by 100.  It will not
* return a value until it finds one that begins a range that is
* not used by any control in any of the dialogs in the res list.
* In other words, it is guaranteed that the number returned and
* the range of the next 99 numbers are not used by any control in
* any dialog in the current res file.
*
* When returning a new id for a control, it usually starts at the
* dialog base, but if there are any controls, it starts at one higher
* than the id of the last control in the dialog that does not have
* one of the special-cased ids (the unused id, IDOK or IDCANCEL).
* It will find the first available id above this.
*
* When returning a default id for a new label, it starts at the dialog
* base (or 100 if there is not a dialog being edited) and starts
* searching for the first available one.  It guarantees that the
* id returned is not used by any control in the current dialog, or
* any other label, or any control in the entire resource list.
*
* Arguments:
*   INT idType      - The type of id desired:
*                     NEXTID_DIALOG  = ID for a new dialog.
*                     NEXTID_CONTROL = ID for a new control.
*                     NEXTID_LABEL   = ID for a new label.
*   NPLABEL plHead  - The current label list to check for conflicts with.
*   INT idExclude   - An id that you specifically want to skip over.
*                     Set to zero if you don't care.
*
* Returns:
*     The "next" unused id value.
*
*
************************************************************************/

INT NextID(
    INT idType,
    NPLABEL plHead,
    INT idExclude)
{
    INT id;

    if (idType == NEXTID_CONTROL) {
        /*
         * Start at the base from the dialog plus one.  It is
         * assumed that this routine will not be called for an
         * id for a control if there is not a dialog being
         * edited first.
         */
        id = gcd.npc->id + 1;

        /*
         * Keep looping until an unused id is found.
         */
        while (!IsUniqueID(id) || FindID(id, plHead) || id == idExclude)
            id++;
    }
    else if (idType == NEXTID_DIALOG) {
        /*
         * Start at 100.
         */
        id = 100;

        /*
         * Keep looping by hundreds until an unused id is found.
         */
        while (!IsUniqueID(id) || FindID(id, plHead) || id == idExclude)
            id += 100;
    }
    else {
        /*
         * We are looking for a default id for a new label.  Start
         * at the dialog base, if there is a dialog being edited.
         */
        if (gfEditingDlg)
            id = gcd.npc->id + 1;
        else
            id = 100;

        /*
         * Keep looping until an unused id is found.  The id should
         * not be used by any control in the current dialog, any
         * other label already, or any control in the res file.
         */
        while (FindID(id, plHead) || FindIDInRes(id) || id == idExclude)
            id++;
    }

    /*
     * We found an unused one.  Return it.
     */
    return id;
}



/************************************************************************
* IDUsedByCtrl
*
* This function returns TRUE if the given ID is used by any control
* in the current dialog.  This also counts the text field of W_ICON
* controls, if they are ordinals.
*
* Arguments:
*   INT id = The ID to look for.
*
* Returns:
*   TRUE if the id is used, FALSE if not.
*
*
************************************************************************/

STATICFN BOOL IDUsedByCtrl(
    INT id)
{
    register NPCTYPE npc;

    for (npc = npcHead; npc; npc = npc->npcNext) {
        if (npc->id == id ||
                (npc->pwcd->iType == W_ICON &&
                npc->text &&
                IsOrd(npc->text) &&
                id == (INT)OrdID(npc->text)))
            return TRUE;
    }

    return FALSE;
}



/************************************************************************
* IsUniqueID
*
* This function returns TRUE if the given id is unique.  A unique
* id is either the special "unused" id value, or it is an id that
* is not already assigned to any other control in the current dialog
* and it is not assigned to any other dialog in the current res list.
*
* Note that this routine does NOT look for duplicates in the include
* file of this id, only for ids that have been used by other controls
* or dialogs already.
*
* Arguments:
*   INT id = The id to verify is unique.
*
* Returns:
*   TRUE if the id is "unique", FALSE if it is not.
*
*
************************************************************************/

BOOL IsUniqueID(
    INT id)
{
    ORDINAL ord;

    /*
     * If the id is the special unused id, it is considered unique.
     */
    if (id == IDUNUSED)
        return TRUE;

    /*
     * Not unique if another control in the dialog has the same id.
     */
    if (IDUsedByCtrl(id))
        return FALSE;

    /*
     * Not unique if another dialog has the same id.
     */
    WriteOrd(&ord, id);
    if (FindDialog((LPTSTR)&ord))
        return FALSE;

    return TRUE;
}



/************************************************************************
* Message
*
* This function puts up a message box with a string indexed by idMsg.
*
* Returns:
*     What MessageBox returns.
*
************************************************************************/

INT Message(
    INT idMsg,
    ...)
{
    va_list marker;
    INT RetCode;
    TCHAR szT[CCHTEXTMAX];
    BOOL fDisabledSave;

    va_start(marker, idMsg);
    wvsprintf(szT, ids(gamdMessages[idMsg].ids), marker);

    fDisabledSave = gfDisabled;
    gfDisabled = TRUE;
    RetCode = MessageBox(NULL, szT, ids(IDS_DLGEDIT),
            (WORD)(gamdMessages[idMsg].fMessageBox | MB_TASKMODAL));
    gfDisabled = fDisabledSave;

    va_end(marker);

    return RetCode;
}



/************************************************************************
* ClientToScreenRect
*
* This function converts the coordinates in a rectangle from points
* relative to the client area into points that are relative to the
* screen.
*
* Arguments:
*   HWND hwnd - Window handle for the conversion.
*   PRECT prc - Pointer to the rectangle to convert.
*
*
************************************************************************/

VOID ClientToScreenRect(
    HWND hwnd,
    PRECT prc)
{
    ClientToScreen(hwnd, (PPOINT)prc);
    ClientToScreen(hwnd, ((PPOINT)prc) + 1);
}



/************************************************************************
* ScreenToClientRect
*
* This function converts the coordinates in a rectangle from points
* relative to the screen into points that are relative to the given
* window's client area.
*
* Arguments:
*   HWND hwnd - Window handle for the conversion.
*   PRECT prc - Pointer to the rectangle to convert.
*
*
************************************************************************/

VOID ScreenToClientRect(
    HWND hwnd,
    PRECT prc)
{
    ScreenToClient(hwnd, (PPOINT)prc);
    ScreenToClient(hwnd, ((PPOINT)prc) + 1);
}



/************************************************************************
* DUToWinPoint
*
* This function converts the coordinates in the given point from
* dialog units (DU's) to window units for the current dialog.
*
* Arguments:
*   PPOINT ppt - Pointer to the point to convert.
*
*
************************************************************************/

VOID DUToWinPoint(
    PPOINT ppt)
{
    ppt->x = MulDiv(ppt->x, gcd.cxChar, 4);
    ppt->y = MulDiv(ppt->y, gcd.cyChar, 8);
}



/************************************************************************
* WinToDUPoint
*
* This function converts the coordinates in the given point from
* window points to dialog units (DU's) for the current dialog.
*
* Arguments:
*   PPOINT ppt - Pointer to the point to convert.
*
*
************************************************************************/

VOID WinToDUPoint(
    PPOINT ppt)
{
    ppt->x = MulDiv(ppt->x, 4, gcd.cxChar);
    ppt->y = MulDiv(ppt->y, 8, gcd.cyChar);
}



/************************************************************************
* DUToWinRect
*
* This function converts the coordinates in a rectangle from
* dialog units for the current dialog to window units.
*
* Arguments:
*   PRECT prc - Pointer to the rectangle to convert.
*
*
************************************************************************/

VOID DUToWinRect(
    PRECT prc)
{
    DUToWinPoint((PPOINT)prc);
    DUToWinPoint(((PPOINT)prc) + 1);
}



/************************************************************************
* WinToDURect
*
* This function converts the coordinates in a rectangle from
* window units to dialog units for the current dialog.
*
* Arguments:
*   PRECT prc - Pointer to the rectangle to convert.
*
*
************************************************************************/

VOID WinToDURect(
    PRECT prc)
{
    WinToDUPoint((PPOINT)prc);
    WinToDUPoint(((PPOINT)prc) + 1);
}



/************************************************************************
* MapDlgClientPoint
*
* This function converts client points to be relative to the window
* origin instead, or the other way around.  If fFromClient is TRUE,
* the point is considered to be relative to the client origin in
* the dialog, and will be converted to a point relative to the
* window origin instead.
*
* If fFromClient is FALSE, the point is considered to be relative
* to the window origin, and will be mapped to a point that is
* relative to the client origin.
*
* This function assumes that the global grcDlgClient has been
* previously calculated.  It should only be called to map points
* for the current dialog being edited (for which grcDlgClient has
* been calculated).
*
* Arguments:
*   PPOINT ppt       - Pointer to the point to convert.
*   BOOL fFromClient - TRUE if the point is relative to the client origin.
*
*
************************************************************************/

VOID MapDlgClientPoint(
    PPOINT ppt,
    BOOL fFromClient)
{
    if (fFromClient) {
        ppt->x += grcDlgClient.left;
        ppt->y += grcDlgClient.top;
    }
    else {
        ppt->x -= grcDlgClient.left;
        ppt->y -= grcDlgClient.top;
    }
}



/************************************************************************
* MapWindowPoint
*
* This function maps a point from one window to another.  The point
* given is in window coordinates (not client coordinates) and is
* mapped so that it is relative to the destination window.
*
* Arguments:
*   HWND hwndFrom   - Source window.
*   HWND hwndTo     - Destination window.
*   PPOINT ppt      - Pointer to the point to convert.
*
* Comments:
* In Win 3.1 (and NT) the MapWindowPoints call can be used and this one can be removed.
* It is only needed here for compatibility with Win 3.0.
*
************************************************************************/

VOID MapWindowPoint(
    HWND hwndFrom,
    HWND hwndTo,
    PPOINT ppt)
{
    RECT rcFrom;
    RECT rcTo;

    GetWindowRect(hwndFrom, &rcFrom);
    GetWindowRect(hwndTo, &rcTo);

    ppt->x += rcFrom.left - rcTo.left;
    ppt->y += rcFrom.top - rcTo.top;
}



/************************************************************************
* MyMapWindowRect
*
* This function maps a rectangle from one window to another.  The rectangle
* given is in window coordinates (not client coordinates) and is
* mapped so that it is relative to the destination window.
*
* Arguments:
*   HWND hwndFrom   - Source window.
*   HWND hwndTo     - Destination window.
*   PRECT prc       - Pointer to the rectangle to convert.
*
* Comments:
* In Win 3.1 (and NT) the MapWindowRect call can be used and this one can be removed.
* It is only needed here for compatibility with Win 3.0.
************************************************************************/

VOID MyMapWindowRect(
    HWND hwndFrom,
    HWND hwndTo,
    PRECT prc)
{
    RECT rcFrom;
    RECT rcTo;

    GetWindowRect(hwndFrom, &rcFrom);
    GetWindowRect(hwndTo, &rcTo);

    OffsetRect(prc, rcFrom.left - rcTo.left, rcFrom.top - rcTo.top);
}



/************************************************************************
* GetChildRect
*
* This function returns the client rectangle for a given child control,
* mapped to its parent window.
*
* Arguments:
*   HWND hwndChild - Child window.
*   PRECT prc      - Where to return the rectangle.
*
************************************************************************/

VOID GetChildRect(
    HWND hwndChild,
    PRECT prc)
{
    HWND hwndParent;

    hwndParent = GetParent(hwndChild);
    GetClientRect(hwndChild, prc);
    ClientToScreenRect(hwndChild, prc);
    ScreenToClientRect(hwndParent, prc);
}



/************************************************************************
* CenterWindow
*
* This function centers the given window over its owner.  It ensures
* that the window is entirely within the visible screen, however.
* If the window does not have an owner, it is centered over the
* desktop.
*
* Arguments:
*   HWND hwnd - The window to center.
*
************************************************************************/

VOID CenterWindow(
    HWND hwnd)
{
    RECT rc;
    RECT rcOwner;
    RECT rcCenter;
    HWND hwndOwner;

    GetWindowRect(hwnd, &rc);

    if (!(hwndOwner = GetWindow(hwnd, GW_OWNER)))
        hwndOwner = GetDesktopWindow();

    GetWindowRect(hwndOwner, &rcOwner);

    /*
     *  Calculate the starting x,y for the new
     *  window so that it would be centered.
     */
    rcCenter.left = rcOwner.left +
            (((rcOwner.right - rcOwner.left) -
            (rc.right - rc.left))
            / 2);

    rcCenter.top = rcOwner.top +
            (((rcOwner.bottom - rcOwner.top) -
            (rc.bottom - rc.top))
            / 2);

    rcCenter.right = rcCenter.left + (rc.right - rc.left);
    rcCenter.bottom = rcCenter.top + (rc.bottom - rc.top);

    FitRectToScreen(&rcCenter);

    SetWindowPos(hwnd, NULL, rcCenter.left, rcCenter.top, 0, 0,
            SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
}



/************************************************************************
* FitRectToScreen
*
* This function ensures that the given rectangle is entirely within
* the visible screen, adjusting it if necessary.
*
* Arguments:
*   PRECT prc - The rectangle.
*
************************************************************************/

VOID FitRectToScreen(
    PRECT prc)
{
    INT cxScreen;
    INT cyScreen;
    INT delta;

    cxScreen = GetSystemMetrics(SM_CXSCREEN);
    cyScreen = GetSystemMetrics(SM_CYSCREEN);

    if (prc->right > cxScreen) {
        delta = prc->right - prc->left;
        prc->right = cxScreen;
        prc->left = prc->right - delta;
    }

    if (prc->left < 0) {
        delta = prc->right - prc->left;
        prc->left = 0;
        prc->right = prc->left + delta;
    }

    if (prc->bottom > cyScreen) {
        delta = prc->bottom - prc->top;
        prc->bottom = cyScreen;
        prc->top = prc->bottom - delta;
    }

    if (prc->top < 0) {
        delta = prc->bottom - prc->top;
        prc->top = 0;
        prc->bottom = prc->top + delta;
    }
}



/************************************************************************
* ids
*
* This function will return a string, given the string id.  If this is
* the first time that the string has been retrieved, memory will be
* allocated for it and it will be loaded.  After it is loaded once, it
* is then cached in a LPTSTR array and is available for later without
* having to load it again.
*
* Arguments:
*   UINT idString - String ID of the string to retrieve.
*
************************************************************************/

LPTSTR ids(
    UINT idString)
{
    static LPTSTR apsz[CSTRINGS];       // String resource array cache.
    LPTSTR psz;
    INT cch;

    if (apsz[idString])
        return apsz[idString];

    if (!(psz = MyAlloc(CCHTEXTMAX * sizeof(TCHAR))))
        return szEmpty;

    if (!(cch = LoadString(ghInst, idString, psz, CCHTEXTMAX))) {
        MyFree(psz);
        return szEmpty;
    }

    apsz[idString] = psz = MyRealloc(psz, (cch + 1) * sizeof(TCHAR));

    return (psz ? psz : szEmpty);
}



/************************************************************************
* PixelsToPointSize
*
* This function takes a font height in pixels and converts it to
* the equivalent point size.  Note that the pixel height of a font
* is actually the tmHeight field of the TEXTMETRIC structure minus
* the tmInternalLeading value.
*
* This function relies on the global gcyPixelsPerInch having been
* set before it is called.
*
* Arguments:
*   INT nPixels - Pixel size to convert to point size.
*
************************************************************************/

INT PixelsToPointSize(
    INT nPixels)
{
    return MulDiv(nPixels, 72, gcyPixelsPerInch);
}



/************************************************************************
* PointSizeToPixels
*
* This function takes a given point size and converts it to the
* equivalent pixel text height.  This value can be placed in
* the TEXTMETRIC structure's tmHeight field if it is made negative
* first.  This will cause a CreateFont call to automatically
* subtract the internal leading value before creating the font.
*
* This function relies on the global gcyPixelsPerInch having been
* set before it is called.
*
* Arguments:
*   INT nPointSize - Point size to convert to pixels.
*
************************************************************************/

INT PointSizeToPixels(
    INT nPointSize)
{
    return MulDiv(nPointSize, gcyPixelsPerInch, 72);
}
