
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993 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: rwdlg.c
*
* Does the writing of .DLG files.
*
* Functions:
*    WriteDlg()
*    WriteDlgInclude()
*    WriteDialogHeader()
*    WriteDialogHeaderLanguage()
*    WriteControl()
*    WriteNameOrd()
*    WriteText()
*    WriteIDDlg()
*    GetControlKeyword()
*    WriteClass()
*    WriteStyles()
*    WriteClassStyle()
*    WriteCustomStyle()
*    WriteCoords()
*    WriteValue()
*    WriteHexWord()
*    WriteHexDWord()
*    WriteString()
*    WriteQuotedString()
*    WriteEscapedString()
*    WriteDlgChar()
*    WriteDlgFlush()
*    Tab()
*    NewLine()
*    Quote()
*    Comma()
*    Space()
*    ORSymbol()
*
* Comments:
*
****************************************************************************/

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


/*
 * Wrap lines before they go over this right margin.
 */
#define CCHRIGHTMARGIN      76

/*
 * Defines for the tabs and tab indent levels.
 */
#define CCHTABWIDTH         4           // Tabs are four spaces wide.
#define TABLEVELNONE        0           // No indent (at left margin).
#define TABLEVELCONTROL     1           // Indent to start of controls.
#define TABLEVELCONTROLDESC 5           // Indent to control description.

/*
 * Macro to set the current tab level.  The level is multiplied
 * by the tab width.
 */
#define SetTab(t)           (cTabStop = ((t)*CCHTABWIDTH))

/*
 * Macro that determines if the current position is the first
 * column for the current tab setting.
 */
#define AtFirstTabColumn()  ((cColumn == cTabStop) ? TRUE : FALSE)

STATICFN VOID WriteDlgInclude(LPTSTR pszFullDlgFile);
STATICFN PCONTROLDATA WriteDialogHeader(PRES pRes, PDIALOGBOXHEADER pdbh);
STATICFN VOID WriteDialogHeaderLanguage(WORD wLanguage);
STATICFN PCONTROLDATA WriteControl(PCONTROLDATA pcd);
STATICFN VOID WriteNameOrd(LPTSTR pszNameOrd);
STATICFN VOID WriteText(LPTSTR pszText);
STATICFN VOID WriteIDDlg(INT id, BOOL fHexOK);
STATICFN LPTSTR GetControlKeyword(INT iClass, DWORD flStyle,
    DWORD *pflStylePredef, DWORD *pflStyleDefault, BOOL *pfWriteText,
    BOOL *pfNotFound);
STATICFN VOID WriteClass(LPTSTR pszClass);
STATICFN BOOL WriteStyles(INT iClass, LPTSTR pszClass, DWORD flStyle,
    DWORD flStylePredef, DWORD flStyleDefault, PDWORD pflStyleLeft,
    BOOL fNullStyles, BOOL fCommaPrefix);
STATICFN BOOL WriteClassStyle(INT iClass, DWORD flStyle,
    DWORD flStylePredef, DWORD flStyleDefault, PDWORD pflStyleLeft,
    BOOL fPrevWritten, BOOL fNullStyles, BOOL fCommaPrefix);
STATICFN BOOL WriteCustomStyle(LPTSTR pszClass, DWORD flStyle,
    PDWORD pflStyleLeft);
STATICFN VOID WriteCoords(INT x, INT y, INT cx, INT cy);
STATICFN VOID WriteValue(INT n);
STATICFN VOID WriteHexWord(WORD w);
STATICFN VOID WriteHexDWord(DWORD dw);
STATICFN VOID WriteString(LPTSTR psz);
STATICFN VOID WriteQuotedString(LPTSTR psz);
STATICFN VOID WriteEscapedString(LPTSTR psz);
STATICFN VOID WriteDlgChar(TCHAR ch);
STATICFN VOID WriteDlgFlush(VOID);
STATICFN VOID Tab(VOID);
STATICFN VOID NewLine(VOID);
STATICFN VOID Quote(VOID);
STATICFN VOID Comma(VOID);
STATICFN VOID Space(VOID);
STATICFN VOID ORSymbol(VOID);

static INT cColumn;                 /* Current column in the line.      */
static INT cTabStop;                /* Current tabstop column.          */
static HANDLE hfDlg;                /* All workers write to this file.  */
static jmp_buf jbWriteDlg;          /* Capture the state for longjmp.   */



/************************************************************************
* WriteDlg
*
* This function writes the dialog boxes in the given resource to the
* hfWrite file in the .DLG file RC format.
*
* Arguments:
*    HANDLE - handle to the file
*    LPTSTR - pointer to the resource file name
*
************************************************************************/

BOOL WriteDlg(
    HANDLE hfWrite,
    LPTSTR pszFullDlgFile)
{
    HANDLE hResLocked = NULL;
    PRES pRes = NULL;
    PRESLINK prl;
    PDIALOGBOXHEADER pdbh;
    PCONTROLDATA pcd;
    INT cItems;

    /*
     * Set our error trap up.  The api setjmp will return a zero at first,
     * then if a write error occurs later and longjmp is called, it
     * will return non-zero and we will return the failure up to the
     * caller.  After this point, there must be no calls to allocate
     * memory, open files, etc., unless this trap has a way to detect
     * what happened and clean it up.  See the c-runtime help file for more
     * information about setjump/longjmp.
     */
    if (_setjmp(jbWriteDlg)) {
        /*
         * If the resource is locked, unlock it.
         */
        if (hResLocked)
            GlobalUnlock(hResLocked);

        return FALSE;
    }

    /*
     * Initialize our globals.  The hfDlg global is used so that hfWrite
     * doesn't have to be passed on the stack over and over.
     */
    hfDlg = hfWrite;
    cColumn = 0;
    SetTab(TABLEVELNONE);

    WriteDlgInclude(pszFullDlgFile);

    /*
     * Process each resource in the list.
     */
    for (prl = gprlHead; prl; prl = prl->prlNext) {
        /*
         * Skip if it is not a dialog resource.
         */
        if (!prl->fDlgResource)
            continue;

        /*
         * Set up pointers to this dialog resource.
         */
        pRes = (PRES)GlobalLock(prl->hRes);
        hResLocked = prl->hRes;

        pdbh = (PDIALOGBOXHEADER)SkipResHeader(pRes);

        NewLine();
        pcd = WriteDialogHeader(pRes, pdbh);

        WriteString(ids(IDS_BEGIN));
        NewLine();

        /*
         * Write the controls.
         */
        cItems = (INT)pdbh->NumberOfItems;
        while (cItems--)
            pcd = WriteControl(pcd);

        /*
         * Finish up dialog template.
         */
        WriteString(ids(IDS_END));
        NewLine();

        GlobalUnlock(prl->hRes);
        hResLocked = NULL;
    }

    /*
     * Flush any remaining characters in the write buffer.
     */
    WriteDlgFlush();

    return TRUE;
}



/************************************************************************
* WriteDlgInclude
*
* This routine writes out the "DLGINCLUDE" lines to the .DLG file.
*
* Arguments:
*    LPTSTR - dialog file name
*
************************************************************************/

STATICFN VOID WriteDlgInclude(
    LPTSTR pszFullDlgFile)
{
    if (pszIncludeFile) {
        WriteValue(ORDID_DLGINCLUDE_NAME);
        Space();
        WriteString(ids(IDS_DLGINCLUDE));
        Space();
        Quote();

        /*
         * If the include file is in a different directory than the resource
         * file, write the full path to it.  Otherwise, we just write the
         * include file name.
         */
        if (DifferentDirs(pszFullDlgFile, szFullIncludeFile))
            WriteEscapedString(szFullIncludeFile);
        else
            WriteEscapedString(pszIncludeFile);

        Quote();
        NewLine();
    }
}



/************************************************************************
* WriteDialogHeader
*
* Writes out the dialog header lines.
*
* Arguments:
*    PRES - pointer to the resource
*    PDIALOGBOXHEADER - pointer to the dialog box header
*
************************************************************************/

STATICFN PCONTROLDATA WriteDialogHeader(
    PRES pRes,
    PDIALOGBOXHEADER pdbh)
{
    DWORD flStyle;
    DWORD flExtStyle;
    DWORD flStyleLeft;
    INT cdit;
    INT x;
    INT y;
    INT cx;
    INT cy;
    LPTSTR pszMenuName;
    LPTSTR pszClass;
    LPTSTR pszCaption;
    INT nPointSize;
    LPTSTR pszFontName;
    PCONTROLDATA pcd;
    PRES2 pRes2;
    BOOL fWritten;

    pRes2 = ResourcePart2(pRes);

    WriteNameOrd(ResourceName(pRes));
    Space();
    WriteString(ids(IDS_DIALOG));

    if (pRes2->MemoryFlags & MMF_PRELOAD) {
        Space();
        WriteString(ids(IDS_PRELOAD));
    }

    if (!(pRes2->MemoryFlags & MMF_MOVEABLE)) {
        Space();
        WriteString(ids(IDS_FIXED));
    }

    if (!(pRes2->MemoryFlags & MMF_PURE)) {
        Space();
        WriteString(ids(IDS_IMPURE));
    }

    /*
     * Parse out the dialog template.
     */
    pcd = ParseDialogBoxHeader(pdbh, &flStyle, &flExtStyle, &cdit, &x, &y,
            &cx, &cy, &pszMenuName, &pszClass, &pszCaption,
            &nPointSize, &pszFontName);

    Space();
    WriteCoords(x, y, cx, cy);

    NewLine();

    /*
     * Write the language.
     */
    WriteDialogHeaderLanguage(pRes2->LanguageId);

    /*
     * Print out the "STYLE" line for the dialog.
     */
    WriteString(ids(IDS_STYLE));
    Space();
    SetTab(TABLEVELCONTROL);
    WriteStyles(IC_DIALOG, NULL, flStyle, 0L, 0L, &flStyleLeft, TRUE, FALSE);
    SetTab(TABLEVELNONE);
    NewLine();

    /*
     * Print out the "EXSTYLE" line for the dialog, if necessary.
     */
    if (flExtStyle) {
        WriteString(ids(IDS_EXSTYLE));
        Space();
        SetTab(TABLEVELCONTROL);

        fWritten = WriteClassStyle(IC_EXSTYLE, flExtStyle, 0L, 0L,
                &flStyleLeft, FALSE, TRUE, FALSE);

        /*
         * If there is anything left (styles that the dialog editor
         * does not know about) write it out as a hex constant.
         */
        if (flStyleLeft) {
            if (fWritten)
                ORSymbol();

            WriteHexDWord(flStyleLeft);
        }

        SetTab(TABLEVELNONE);
        NewLine();
    }

    /*
     * If it has a caption, print it out.
     */
    if (*pszCaption) {
        WriteString(ids(IDS_CAPTION));
        Space();
        WriteText(pszCaption);
        NewLine();
    }

    /*
     * If it has a font specified, print it out.
     */
    if (flStyle & DS_SETFONT) {
        WriteString(ids(IDS_FONT));
        Space();

        WriteValue(nPointSize);
        Comma();
        WriteQuotedString(pszFontName);
        NewLine();
    }

    /*
     * If it has a class specified, print it out.
     */
    if (*pszClass) {
        WriteString(ids(IDS_CLASS));
        Space();
        WriteText(pszClass);
        NewLine();
    }

    /*
     * If it has a menu specified, print it out.
     */
    if (*pszMenuName) {
        WriteString(ids(IDS_MENU));
        Space();
        WriteNameOrd(pszMenuName);
        NewLine();
    }

    if (pRes2->Version) {
        WriteString(ids(IDS_VERSION));
        Space();
        WriteValue(pRes2->Version);
        NewLine();
    }

    if (pRes2->Characteristics) {
        WriteString(ids(IDS_CHARACTERISTICS));
        Space();
        WriteValue(pRes2->Characteristics);
        NewLine();
    }

    return pcd;
}



/************************************************************************
* WriteDialogHeaderLanguage
*
* Writes out the dialog header "LANGUAGE" line.
*
* Arguments:
*    WORD - language to write out
*
************************************************************************/

STATICFN VOID WriteDialogHeaderLanguage(
    WORD wLanguage)
{
    WORD wPrimary;
    WORD wSubLang;
    INT i;
    INT j;
    INT idsLang;
    INT idsSubLang;

    WriteString(ids(IDS_LANGUAGE));
    Space();

    idsLang = 0;
    idsSubLang = 0;
    wPrimary = (WORD)PRIMARYLANGID(wLanguage);
    wSubLang = SUBLANGID(wLanguage);
    for (i = 0; i < gcLanguages; i++) {
        if (gaLangTable[i].wPrimary == wPrimary) {
            idsLang = gaLangTable[i].idsLang;

            for (j = 0; j < gaLangTable[i].cSubLangs; j++) {
                if (gaLangTable[i].asl[j].wSubLang == wSubLang) {
                    idsSubLang = gaLangTable[i].asl[j].idsSubLang;
                    break;
                }
            }

            break;
        }
    }

    if (idsLang)
        WriteString(ids(idsLang));
    else
        WriteHexWord(wPrimary);

    Comma();

    if (idsSubLang)
        WriteString(ids(idsSubLang));
    else
        WriteHexWord(wSubLang);

    NewLine();
}



/************************************************************************
* WriteControl
*
* Writes out a control line.
*
* Arguments:
*    PCONTROLDATA - pointer to the control data
*
************************************************************************/

STATICFN PCONTROLDATA WriteControl(
    PCONTROLDATA pcd)
{
    INT x;
    INT y;
    INT cx;
    INT cy;
    INT id;
    DWORD flStyle;
    DWORD flExtStyle;
    LPTSTR pszClass;
    LPTSTR pszText;
    INT iClass;
    LPTSTR pszKeyword;
    BOOL fWriteText;
    BOOL fNotFound;
    DWORD flStylePredef;
    DWORD flStyleDefault;
    DWORD flStyleLeft;
    BOOL fWritten;

    pcd = ParseControlData(pcd, &flStyle, &flExtStyle, &x, &y, &cx, &cy,
            &id, &pszClass, &pszText);

    /*
     * Determine the class of the control.
     */
    iClass = GetiClass(pszClass);

    /*
     * Determine if there are any predefined RC keywords that we
     * can use instead of the generic "CONTROL" keyword for this
     * style of control.
     */
    pszKeyword = GetControlKeyword(iClass, flStyle, &flStylePredef,
            &flStyleDefault, &fWriteText, &fNotFound);

    SetTab(TABLEVELCONTROL);
    Tab();
    WriteString(pszKeyword);
    SetTab(TABLEVELCONTROLDESC);
    Tab();

    /*
     * Write out the text field, if this type of control has one.
     */
    if (fWriteText) {
        WriteText(pszText);
        Comma();
    }

    /*
     * Write out the id for the control.
     */
    WriteIDDlg(id, TRUE);

    /*
     * If we did not find a predefined keyword to use instead of "CONTROL",
     * we have to write out the fields in a different order, and specify
     * the class as well.
     */
    if (fNotFound) {
        WriteClass(pszClass);
        Comma();
        fWritten = WriteStyles(iClass, pszClass, flStyle, flStylePredef,
                flStyleDefault, &flStyleLeft, fNotFound, FALSE);

        if (!fWritten || flStyleLeft) {
            if (fWritten)
                ORSymbol();

            WriteHexWord(LOWORD(flStyleLeft));
        }

        Comma();
        WriteCoords(x, y, cx, cy);
    }
    else {
        Comma();
        WriteCoords(x, y, cx, cy);
        fWritten = WriteStyles(iClass, pszClass, flStyle, flStylePredef,
                flStyleDefault, &flStyleLeft, fNotFound, TRUE);

        if (flStyleLeft) {
            if (fWritten)
                ORSymbol();
            else
                Comma();

            WriteHexWord(LOWORD(flStyleLeft));
            fWritten = TRUE;
        }
    }

    /*
     * Write out the extended styles for the control, if necessary.
     */
    if (flExtStyle) {
        /*
         * If writing a predefined keyword (not CONTROL), and there
         * were no styles written out at the end of the line, write
         * a style of zero.  RC doesn't like consecutive comma's,
         * and we need to skip the styles field to get to the
         * extended styles field.
         */
        if (!fNotFound && !fWritten) {
            Comma();
            WriteValue(0);
        }

        Comma();

        fWritten = WriteClassStyle(IC_EXSTYLE, flExtStyle, 0L, 0L,
                &flStyleLeft, FALSE, TRUE, FALSE);

        /*
         * If there is anything left (styles that the dialog editor
         * does not know about) write it out as a hex constant.
         */
        if (flStyleLeft) {
            if (fWritten)
                ORSymbol();

            WriteHexDWord(flStyleLeft);
        }
    }

    SetTab(TABLEVELNONE);
    NewLine();

    return pcd;
}



/************************************************************************
* WriteNameOrd
*
* Writes out the name/ordinal.  Handles the case where the name
* is really an ordinal instead of a string.  When it is a string,
* it will not be quoted.
*
* This routine never writes the ordinal out in hex, because the
* items that it is intended to write are not parsed properly by
* the Windows RC.EXE if they are written in hex notation.
*
* Arguments:
*   LPTSTR pszNameOrd - The name/ordinal to write.
*
************************************************************************/

STATICFN VOID WriteNameOrd(
    LPTSTR pszNameOrd)
{
    if (IsOrd(pszNameOrd))
        /*
         * Write the name as a numeric ordinal.
         */
        WriteIDDlg(OrdID(pszNameOrd), FALSE);
    else
        WriteString(pszNameOrd);
}



/************************************************************************
* WriteText
*
* Writes out the text for a control or dialog.  This will either be
* an ordinal (icon's text field) or a quoted string.
*
* Arguments:
*    LPTSTR pszText - text to write out
*
************************************************************************/

STATICFN VOID WriteText(
    LPTSTR pszText)
{
    if (IsOrd(pszText))
        /*
         * Write the text as an ID.  Hex notation is allowed.
         */
        WriteIDDlg(OrdID(pszText), TRUE);
    else
        WriteQuotedString(pszText);
}



/************************************************************************
* WriteIDDlg
*
* Writes out the ID.  This may be written out as either a symbol
* or a numeric.
*
* Arguments:
*    INT id - id to write out
*    BOOL fHexOK - flag specifies whether the id is a hex numeric.
*
************************************************************************/

STATICFN VOID WriteIDDlg(
    INT id,
    BOOL fHexOK)
{
    TCHAR szID[CCHTEXTMAX];

    IDToLabel(szID, id, fHexOK);
    WriteString(szID);
}



/************************************************************************
* GetControlKeyword
*
* This routine does a lookup in the predefined RC keyword table
* associated with the given class for a keyword that can be used
* instead of "CONTROL".  The match is based on the style of the control
* that is passed in.  If a match is not found, it defaults all the
* returned values to use the "CONTROL" keyword.
*
* Arguments:
*   INT iClass              - The class of the control.
*   DWORD flStyle           - The style of the control.
*   DWORD *pflStylePredef   - Return for the bits of the predefined control
*                             (if found).  These can be removed later from
*                             the style flag.
*   DWORD *pflStyleDefault  - Return for the default styles.
*   BOOL *pfWriteText       - Return for the "Write Text" flag.  This will
*                             be TRUE if this control has a text field.
*   BOOL *pfNotFound        - Return for the "Not Found" flag.  This will
*                             be TRUE if no match was found and the "CONTROL"
*                             keyword was defaulted to.
*
* Returns:
*   A pointer to the control keyword to use.
*   If a match was found, *pflStylePredef is set to the bits for the match.
*       If not found, this is set to zero.
*   The default style bits for this keyword will be returned.
*   The "Write Text" flag will be set.
*   The "Not Found" flag will be set.
*
************************************************************************/

STATICFN LPTSTR GetControlKeyword(
    INT iClass,
    DWORD flStyle,
    DWORD *pflStylePredef,
    DWORD *pflStyleDefault,
    BOOL *pfWriteText,
    BOOL *pfNotFound)
{
    register INT i;
    INT iMax;
    PRCKEYWORD prckwd;

    if (gfUseNewKeywords && iClass != IC_UNKNOWN) {
        iMax = acsd[iClass].cKeywords;
        prckwd = acsd[iClass].parckwd;

        /*
         * Loop through all the keywords for this class.
         */
        for (i = 0; i < iMax; i++, prckwd++) {
            /*
             * Does the style (masked) exactly match the keywords style?
             */
            if ((flStyle & prckwd->flStyleMask) == prckwd->flStyle) {
                /*
                 * Yes.  Set the "Has Text" flag, we did find a match,
                 * put the found bits in the predefined style flag,
                 * set the default styles flag and return the found
                 * keyword.
                 */
                *pfWriteText = prckwd->fHasText;
                *pfNotFound = FALSE;
                *pflStylePredef = prckwd->flStyle;
                *pflStyleDefault = prckwd->flStyleDefault;
                return ids(prckwd->idsKeyword);
            }
        }
    }

    /*
     * A match was not found.  We must write text, we didn't find a
     * match, we will be using the "CONTROL" keyword and the default
     * styles that this keyword implies is the "child" and "visible"
     * bits (rc.exe OR's these styles in implicitly).
     */
    *pfWriteText = TRUE;
    *pfNotFound = TRUE;
    *pflStylePredef = 0L;
    *pflStyleDefault = WS_VISIBLE | WS_CHILD;
    return ids(IDS_CONTROL);
}



/************************************************************************
* WriteClass
*
* Writes out the class for a control.
*
* Arguments:
*    LPTSTR pszClass - pointer to the class string
*
************************************************************************/

STATICFN VOID WriteClass(
    LPTSTR pszClass)
{
    INT i;
    WORD idOrd;

    Comma();

    /*
     * Is this class a predefined type instead of a string?
     */
    if (IsOrd(pszClass)) {
        /*
         * Figure out which type it is and get the class string to
         * write.
         */
        idOrd = OrdID(pszClass);
        for (i = 0; i < IC_DIALOG; i++) {
            if (acsd[i].idOrd == idOrd) {
                pszClass = ids(acsd[i].idsClass);
                break;
            }
        }
    }

    WriteQuotedString(pszClass);
}



/************************************************************************
* WriteStyles
*
* This function writes the class and style info to the file
* for the control or dialog box in the RC format.
*
* Arguments:
*     INT iClass           = The class of the item.
*     LPTSTR pszClass      = Class name of the control.
*     DWORD flStyle        = The style of the item.
*     DWORD flStylePredef  = The styles bits implicit in the predefined
*                            keyword for this control.  This should be
*                            zero if this control doesn't have a predefined
*                            keyword for it.
*     DWORD flStyleDefault = The default styles implicit in the item.
*     PDWORD pflStyleLeft  = Where to  return any style bits that do not
*                            get written out.
*     BOOL fNullStyles     = TRUE if we should still write the style word
*                            even if the style flag is zero.
*     BOOL fCommaPrefix    = TRUE means that a comma will be written out
*                            before writing any styles.  If no styles
*                            are written, no comma will be written either.
*
* Returns:
*     TRUE => Something was written out.
*     FALSE => Nothing was written out.
*
************************************************************************/

STATICFN BOOL WriteStyles(
    INT iClass,
    LPTSTR pszClass,
    DWORD flStyle,
    DWORD flStylePredef,
    DWORD flStyleDefault,
    PDWORD pflStyleLeft,
    BOOL fNullStyles,
    BOOL fCommaPrefix)
{
    DWORD flStyleLeft;
    BOOL fWritten = FALSE;

    /*
     * Write the control specific styles.
     */
    if (iClass == IC_CUSTOM) {
        fWritten = WriteCustomStyle(pszClass, flStyle, &flStyleLeft);
    }
    else {
        fWritten = WriteClassStyle(iClass, flStyle, flStylePredef,
                flStyleDefault, &flStyleLeft, FALSE, fNullStyles,
                fCommaPrefix);
    }

    /*
     * If we are writing styles for the dialog, remove the WS_GROUP
     * and WS_TABSTOP bits from the style before proceeding.  This is
     * because the WS_MINIMIZEBOX and WS_MAXIMIZEBOX styles use the
     * same bits, and these keywords will have already been written
     * out by the preceding WriteClassStyle call if those bits are
     * present.
     */
    if (iClass == IC_DIALOG)
        flStyle &= ~(WS_GROUP | WS_TABSTOP);

    /*
     * Write the window styles that are common to the different
     * controls (the high word).
     */
    fWritten |= WriteClassStyle(IC_WINDOW, flStyleLeft, flStylePredef,
            flStyleDefault, &flStyleLeft, fWritten, fNullStyles, fCommaPrefix);

    /*
     * Pass back any styles that were not written.
     */
    *pflStyleLeft = flStyleLeft;

    return fWritten;
}



/************************************************************************
* WriteClassStyle
*
* This function writes the class style symbols to the file.  The styles
* to write out are passed in flStyle, and the styles that are implicitly
* set by this type of control already are passed in flStyleDefault.  The
* style keywords corresponding to the bits in flStyle are written out,
* separated by " | ", and any bits in flStyleDefault that are NOT set
* are written out preceded by a "NOT" to explicitly turn them off.  This
* is used in the case of the predefined RC keywords, which often have
* styles like WS_TABSTOP or WS_VISIBLE already implicit in them.  There
* is no need to explicitly specify them, but if they are not present, we
* must NOT them out.  The parameter flStylePredef contains the style bits
* that identified the predefined control keyword itself (if any) and
* thus are removed from the style before writing anything out.
*
* Arguments:
*     INT iClass           = The class of the control. See the
*                            IC_ constants defined in dlgedit.h.
*     DWORD flStyle        = The style of control.  This nails
*                            down the exact type of control.
*     DWORD flStylePredef  = The styles bits implicit in the predefined
*                            keyword for this control.  This should be
*                            zero if this control doesn't have a predefined
*                            keyword for it.
*     DWORD flStyleDefault = The default styles that are implicit with
*                            this control.  This will only be set if this
*                            control is using a predefined RC keyword. A
*                            value of zero means that there are no default
*                            styles implicitly specified.
*     PDWORD pflStyleLeft  = Where to  return any style bits that do not
*                            get written out.
*     BOOL fPrevWritten    = TRUE means a previous style symbol has
*                            been written and to put " | " before
*                            the next symbol.
*     BOOL fNullStyles     = TRUE if we should still write the style word
*                            even if the style flag is zero.  This is used
*                            to handle the case where a predefined keyword
*                            has been written out that implies a style that
*                            also happens to be zero.  Without this flag
*                            being FALSE the style flag implicit in the
*                            keyword would be redundantly written out again.
*                            In general, if we have written out a predefined
*                            keyword this flag should be FALSE.
*     BOOL fCommaPrefix    = TRUE means that a comma will be written out
*                            before writing any styles.  This will only
*                            happen if fPrevWritten is FALSE.  If no styles
*                            are written, no comma will be written either.
*
* Returns:
*     TRUE => Something was written out.
*     FALSE => Nothing was written out.
*
************************************************************************/

STATICFN BOOL WriteClassStyle(
    INT iClass,
    DWORD flStyle,
    DWORD flStylePredef,
    DWORD flStyleDefault,
    PDWORD pflStyleLeft,
    BOOL fPrevWritten,
    BOOL fNullStyles,
    BOOL fCommaPrefix)
{
    register WORD i;
    WORD iMax;
    DWORD flStyleMask;
    PCLASSSTYLE pcs;

    iMax = (WORD)acsd[iClass].cClassStyles;
    pcs = acsd[iClass].pacs;

    /*
     * Remove the bits that identified the predefined control keyword
     * from the style flag before proceeding.  For instance, if I already
     * am going to be writing out a "PUSHBUTTON", there is no reason
     * to write out the "BS_PUSHBUTTON" style.  If there is no predefined
     * control keyword, flStylePredef will be zero and this will do
     * nothing.
     */
    flStyle &= ~flStylePredef;

    /*
     * Go through all possible flags for this style.
     */
    for (i = 0; i < iMax; i++, pcs++) {
        flStyleMask = pcs->flStyleMask ? pcs->flStyleMask : pcs->flStyle;

        /*
         * Is this styles bits set?
         */
        if ((flStyle & flStyleMask) == pcs->flStyle) {
            /*
             * Remove these bits from the styles left.  Even if
             * we do not write them out, they are still accounted
             * for and can be removed from the styles remaining.
             */
            flStyle &= ~pcs->flStyle;

            /*
             * Skip this style if we don't want to write styles that are
             * zero, or if the style is already implicitly specified for
             * this control (a non-zero default style mask must be specified).
             */
            if ((!pcs->flStyle && !fNullStyles) ||
                    (flStyleDefault &&
                    (flStyleDefault & flStyleMask) == pcs->flStyle))
                continue;

            /*
             * If there is a string for this style, write it out, preceded
             * by an "|" symbol if necessary.
             */
            if (*ids(acsd[iClass].idsStylesStart + i)) {
                if (fPrevWritten) {
                    ORSymbol();
                }
                else {
                    if (fCommaPrefix)
                        Comma();

                    fPrevWritten = TRUE;
                }

                /*
                 * Write the string.
                 */
                WriteString(ids(acsd[iClass].idsStylesStart + i));
            }
        }
        /*
         * No the styles bit is not set.  Is it implicit in the keyword
         * being used?  If so, we need to explicitly NOT it
         * out in the dialog template.
         * Note that this should not be done in the case where the style
         * is zero.
         */
        else if (flStyleDefault &&
                (flStyleDefault & flStyleMask) == pcs->flStyle &&
                pcs->flStyle) {
            if (fPrevWritten) {
                ORSymbol();
            }
            else {
                if (fCommaPrefix)
                    Comma();

                fPrevWritten = TRUE;
            }

            WriteString(ids(IDS_NOT));
            Space();
            WriteString(ids(acsd[iClass].idsStylesStart + i));
        }
    }

    /*
     * Pass back the style bits that were not written out.
     */
    *pflStyleLeft = flStyle;

    return fPrevWritten;
}



/************************************************************************
* WriteCustomStyle
*
* Writes our the custom control information
*
* Arguments:
*    LPTSTR - class name of the control
*    DWORD - style of the control
*    PDWORD - styles left to be written
*
* Returns:
*    TRUE if custom styles were written out; otherwise, FALSE.
*
************************************************************************/

STATICFN BOOL WriteCustomStyle(
    LPTSTR pszClass,
    DWORD flStyle,
    PDWORD pflStyleLeft)
{
    PCUSTLINK pcl;
    LPCCSTYLEFLAG pStyleFlags;
    DWORD flStyleMask;
    INT i;
    BOOL fWritten = FALSE;

    /*
     * Search the list of installed custom controls for one
     * that matches the class.
     */
    for (pcl = gpclHead;
            pcl && lstrcmpi(pcl->pwcd->pszClass, pszClass) != 0;
            pcl = pcl->pclNext)
        ;

    /*
     * Was a match found and is this control from a DLL (not emulated)?
     */
    if (pcl && !pcl->pwcd->fEmulated) {
        for (i = 0, pStyleFlags = pcl->pwcd->aStyleFlags;
                i < pcl->pwcd->cStyleFlags;
                i++, pStyleFlags++) {
            flStyleMask = pStyleFlags->flStyleMask ?
                    pStyleFlags->flStyleMask : pStyleFlags->flStyle;

            /*
             * Is this styles bits set?
             */
            if ((flStyle & flStyleMask) == pStyleFlags->flStyle) {
                /*
                 * Remove these bits from the styles left.
                 */
                flStyle &= ~pStyleFlags->flStyle;

                if (fWritten)
                    ORSymbol();
                else
                    fWritten = TRUE;

                /*
                 * Write the string.
                 */
                WriteString(pStyleFlags->pszStyle);
            }
        }
    }

    /*
     * Return the styles that remain to be written.
     */
    *pflStyleLeft = flStyle;

    return fWritten;
}



/************************************************************************
* WriteCoords
*
* This function writes the coordinates out to the file as decimal
* ascii numbers separated by ", ".
*
* Arguments:
*     INT x, y, cx, cy = The coordinates.
*
************************************************************************/

STATICFN VOID WriteCoords(
    INT x,
    INT y,
    INT cx,
    INT cy)
{
    WriteValue(x);
    Comma();
    WriteValue(y);
    Comma();
    WriteValue(cx);
    Comma();
    WriteValue(cy);
}



/************************************************************************
* WriteValue
*
* This function writes the value of 'n' as a decimal ascii string to
* the file.
*
* Arguments:
*     INT n = The number to write.
*
************************************************************************/

STATICFN VOID WriteValue(
    INT n)
{
    TCHAR szNum[32];

    itoaw(n, szNum, 10);
    WriteString(szNum);
}



/************************************************************************
* WriteHexWord
*
* This function writes the value of 'w' as a hex constant to the file.
*
* Arguments:
*     WORD w - The word to write.
*
************************************************************************/

STATICFN VOID WriteHexWord(
    WORD w)
{
    TCHAR szNum[17];

    itoax(w, szNum);
    WriteString(szNum);
}



/************************************************************************
* WriteHexDWord
*
* This function writes the value of 'dw' as a hex constant to the file.
*
* Arguments:
*     DWORD dw - The dword to write.
*
************************************************************************/

STATICFN VOID WriteHexDWord(
    DWORD dw)
{
    TCHAR szNum[32];

    wsprintf(szNum, L"0x%8.8X", dw);
    WriteString(szNum);
}



/************************************************************************
* WriteString
*
* This function writes the given string to the file.  If the string
* would cause it to overflow the margin, a new line, with indenting
* to the current tab level, is forced before writing the string.
*
* Arguments:
*   LPTSTR psz = The string to write out.
*
************************************************************************/

STATICFN VOID WriteString(
    LPTSTR psz)
{
    register INT nLen;

    nLen = lstrlen(psz);

    if (!AtFirstTabColumn() && cColumn + nLen > CCHRIGHTMARGIN)
        NewLine();

    while (nLen--)
        WriteDlgChar(*psz++);
}



/************************************************************************
* WriteQuotedString
*
* This function writes the given string to the file.  If the string
* would cause it to overflow the margin, a new line, with indenting
* to the current tab level, is forced before writing the string.
* This function will also enclose the given string in double-quotes,
* and ensures that the string will not be broken when it is written.
* If there are any escape characters (backslashes or quotes) in the
* string, they will be escaped properly so that rc.exe can read them
* properly.
*
* Arguments:
*   LPTSTR psz = The string to write out.
*
************************************************************************/

STATICFN VOID WriteQuotedString(
    LPTSTR psz)
{
    register INT nLen;
    LPTSTR pszT;

    /*
     * Find the actual length of the string.  To do this, we must scan
     * for the characters that will be escaped later.
     */
    nLen = lstrlen(psz);
    pszT = psz;
    while (*pszT) {
        if (*pszT == CHAR_DBLQUOTE || *pszT == CHAR_BACKSLASH)
            nLen++;

        pszT++;
    }

    /*
     * Start a new line if necessary.  Add 2 for the quotes.
     */
    if (!AtFirstTabColumn() && cColumn + nLen + 2 > CCHRIGHTMARGIN)
        NewLine();

    Quote();
    WriteEscapedString(psz);
    Quote();
}



/************************************************************************
* WriteEscapedString
*
* This function writes the given string to the file.  It is different
* from WriteString in that it will add a '\' in front of other
* backslashes and a second double quote in front of double quotes.
* This is necessary when writing out a string which will be surrounded
* by quotes, such as the Text fields in the .DLG file.
*
* Arguments:
*   LPTSTR psz = The string to write out.
*
************************************************************************/

STATICFN VOID WriteEscapedString(
    LPTSTR psz)
{
    while (*psz) {
        if (*psz == CHAR_DBLQUOTE)
            WriteDlgChar(CHAR_DBLQUOTE);
        else if (*psz == CHAR_BACKSLASH)
            WriteDlgChar(CHAR_BACKSLASH);

        WriteDlgChar(*psz++);
    }
}



/************************************************************************
* WriteDlgChar
*
* Low level function to do an actual character write to the file.
* Some buffering is done then _lwrite is called.
*
* Because it is buffered, before closing the file any remaining
* characters in the buffer must be flushed to disk using WriteDlgFlush.
*
* If an error occurs on the write, Throw will be called to jump back
* up to WriteDlg and return the failure to the caller.
*
* The globals gachWriteBuffer and cbWritePos are updated by this routine.
*
* Arguments:
*     TCHAR ch - The character to write.
*
* Returns:
*   If an error occurs on the _lwrite, the execution will be thrown
*   back to the WriteDlg function.  Otherwise, nothing is returned.
*
************************************************************************/

STATICFN VOID WriteDlgChar(
    TCHAR ch)
{
    INT cbWritten;

    gachWriteBuffer[cbWritePos++] = ch;

    /*
     * Is the buffer full?
     */
    if (cbWritePos == CCHFILEBUFFER) {
        CHAR abWriteBuffer[CCHFILEBUFFER];
        BOOL fDefCharUsed;

        WideCharToMultiByte(CP_ACP, 0, gachWriteBuffer, CCHFILEBUFFER,
                abWriteBuffer, CCHFILEBUFFER, NULL, &fDefCharUsed);

        cbWritten = (INT)_lwrite((HFILE)hfDlg, abWriteBuffer, cbWritePos);
        if (cbWritten != cbWritePos)
            longjmp(jbWriteDlg, 1);

        cbWritePos = 0;
    }

    /*
     * Update the current column counter.
     */
    if (ch == CHAR_RETURN || ch == CHAR_NEWLINE) {
        /*
         * Carriage return or newline resets column position to 0.
         */
        cColumn = 0;
    }
    else {
        cColumn++;
    }
}



/************************************************************************
* WriteDlgFlush
*
* This routine flushes the write buffer.  This must be done before
* the file is closed or data can be lost.
*
* The global cbWritePos is updated by this routine.
*
* Returns:
*   If an error occurs on the _lwrite, the execution will be thrown
*   back to the WriteDlg function.  Otherwise, nothing is returned.
*
************************************************************************/

STATICFN VOID WriteDlgFlush(VOID)
{
    INT cbWritten;

    /*
     * Are any bytes remaining in the buffer?
     */
    if (cbWritePos) {
        CHAR abWriteBuffer[CCHFILEBUFFER];
        BOOL fDefCharUsed;

        WideCharToMultiByte(CP_ACP, 0, gachWriteBuffer, cbWritePos,
                abWriteBuffer, CCHFILEBUFFER, NULL, &fDefCharUsed);

        cbWritten = (INT)_lwrite((HFILE)hfDlg, abWriteBuffer, cbWritePos);
        if (cbWritten != cbWritePos)
            longjmp(jbWriteDlg, 1);

        cbWritePos = 0;
    }
}



/****************************************************************************
* Tab
*
* Writes spaces up to the current tab level setting.
*
****************************************************************************/

STATICFN VOID Tab(VOID)
{
    while (cColumn < cTabStop)
        WriteDlgChar(CHAR_SPACE);
}



/****************************************************************************
* NewLine
*
* Begins a new line by writing a carriage return and linefeed.  Also
* indents the following line up to the current tab level.
*
****************************************************************************/

STATICFN VOID NewLine(VOID)
{
    WriteDlgChar(CHAR_RETURN);
    WriteDlgChar(CHAR_NEWLINE);
    Tab();
}



/****************************************************************************
* Quote
*
* Writes a double quote.
*
****************************************************************************/

STATICFN VOID Quote(VOID)
{
    WriteDlgChar(CHAR_DBLQUOTE);
}



/****************************************************************************
* Comma
*
* Writes a comma then a space.
*
****************************************************************************/

STATICFN VOID Comma(VOID)
{
    WriteDlgChar(CHAR_COMMA);
    WriteDlgChar(CHAR_SPACE);
}



/****************************************************************************
* Space
*
* Writes a space.
*
****************************************************************************/

STATICFN VOID Space(VOID)
{
    WriteDlgChar(CHAR_SPACE);
}



/****************************************************************************
* ORSymbol
*
* Writes " | ".
*
****************************************************************************/

STATICFN VOID ORSymbol(VOID)
{
    WriteDlgChar(CHAR_SPACE);
    WriteDlgChar(CHAR_ORSYMBOL);
    WriteDlgChar(CHAR_SPACE);
}
