
/******************************************************************************\
*       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: dlgedit.c
*
* Main function and window procedure for the Dialog Box Editor.
*
* Functions:
*
*   MainWndProc()
*   ReadWindowPos()
*   WriteWindowPos()
*   InitApplication()
*   InitInstance()
*   PenWinRegister()
*   GetSystemValues()
*   ReadEnv()
*   WriteEnv()
*   LoadSysColorBitmaps()
*   LoadAlterBitmap()
*   RGBInvertRGB()
*   SizeRibbons()
*   DialogTerminate()
*
* Comments:
*
* Because of the need for a dialog in both work and test mode to be
* shown relative to the client area of its parent, and because the
* editor has a ribbon control along the top of its client area, there
* needed to be another window created that will be the actual parent
* of the dialog being edited.  This window, called the ghwndSubClient
* window, is sized to be the size of the editors client area minus
* the height of the ribbon window at the top.  This makes it so that
* a dialog that has an origin of 0,0 will have the top edge of its
* client area just below the bottom of the ribbon window in the
* editor.  This window does not need any special processing.  It simply
* paints its background with the app workspace color, and is used as
* the basis for coordinate conversion for the dialog.
*
****************************************************************************/

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

#include <commdlg.h>

#include <stdlib.h>
#include <string.h>

STATICFN BOOL InitApplication(HANDLE hInstance);
STATICFN BOOL InitInstance(HANDLE hInstance, INT nCmdShow);
STATICFN VOID PenWinRegister(VOID);
STATICFN VOID GetSystemValues(VOID);
STATICFN VOID ReadEnv(VOID);
STATICFN VOID WriteEnv(VOID);
STATICFN VOID LoadSysColorBitmaps(VOID);
STATICFN HBITMAP LoadAlterBitmap(INT idbm, DWORD rgbNew, DWORD rgbNew2);
STATICFN DWORD RGBInvertRGB(DWORD rgb);
STATICFN VOID SizeRibbons(HWND hwnd);
STATICFN VOID DialogTerminate(VOID);

static RECT grcAppPos;              // Saves the app's window pos.
static UINT gmsgHelp;               // Registered help message from commdlg.
static BOOL fStartAsIcon = FALSE;   // TRUE if app is started minimized.

/*
 * Contains the address of the Pen Windows callback.
 */
typedef VOID ( APIENTRY *LPFNPENWIN)(WORD, BOOL);
static LPFNPENWIN lpfnRegisterPenApp;



/************************************************************************
* WinMain
*
* This is the main function for the dialog editor.
*
************************************************************************/

INT WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    INT nCmdShow)
{
    MSG msg;

    if (!hPrevInstance) {
        if (!InitApplication(hInstance)) {
            Message(MSG_NOINIT);
            return FALSE;
        }
    }

    if (!InitInstance(hInstance, nCmdShow)) {
        Message(MSG_NOINIT);
        return FALSE;
    }

    while (GetMessage(&msg, NULL, 0, 0)) {
        if (!ghwndTestDlg || !IsDialogMessage(ghwndTestDlg, &msg)) {
            if (!hwndStatus || !IsDialogMessage(hwndStatus, &msg)) {
                if (!TranslateAccelerator(ghwndMain, ghAccTable, &msg)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
        }
    }

    DialogTerminate();

    /*
     * Return the value from PostQuitMessage.
     */
    return msg.wParam;
}



/************************************************************************
* InitApplication
*
* Registers the window classes.
*
* Arguments:
*   HANDLE hInstance - Instance handle from WinMain.
*
* Returns:
*   TRUE if all of the window classes were created; otherwise, FALSE.
*
************************************************************************/

STATICFN BOOL InitApplication(
    HANDLE hInstance)
{
    WNDCLASS wc;

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(DWORD);
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDICON_DLGEDIT));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    wc.lpszMenuName = MAKEINTRESOURCE(IDMENU_MAIN);
    wc.lpszClassName = szMainClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = 0;
    wc.lpfnWndProc = DefWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szSubClientClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = DragWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(DWORD);
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szDragClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = 0;
    wc.lpfnWndProc = ToolboxWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szToolboxClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = 0;
    wc.lpfnWndProc = ToolBtnWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szToolBtnClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = CustomWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szCustomClass;
    if (!RegisterClass(&wc))
        return FALSE;

    return TRUE;
}



/************************************************************************
* InitInstance
*
* Initializes the dialog editor by loading resources, etc.
*
* Arguments:
*   HANDLE hInstance - Instance handle from WinMain.
*   int nCmdShow     - Show command from WinMain.
*
* Returns:
*   FALSE if any errors occurred during initialization
*
************************************************************************/

STATICFN BOOL InitInstance(
    HANDLE hInstance,
    INT nCmdShow)
{
    HDC hDC;
    TEXTMETRIC tm;
    INT x;
    INT y;
    INT cx;
    INT cy;
    BOOL fMaximized;
    INT i;
    TCHAR szArg1[CCHTEXTMAX];

    ghInst = hInstance;

    /*
     * We need a mouse - make sure we have one.
     */
    if (!GetSystemMetrics(SM_MOUSEPRESENT)) {
        Message(MSG_NOMOUSE);
        return FALSE;
    }

    /*
     * Register for Pen Windows, if it is present.
     */
    PenWinRegister();

    ghAccTable = LoadAccelerators(ghInst, MAKEINTRESOURCE(IDACCEL_MAIN));

    /*
     * Create a dark gray pen for use in borders later.
     */
    if (!(hpenDarkGray = CreatePen(PS_SOLID, 1, DARKGRAY)))
        return FALSE;

    /*
     * Get some system constants.
     */
    GetSystemValues();

    /*
     * Note that this must be done instead of using the text metrics,
     * because Windows internally generates a better average value for
     * proportional fonts, and we must match it or our dialogs will
     * be out of proportion.
     */
    gcxSysChar = LOWORD(GetDialogBaseUnits());
    gcySysChar = HIWORD(GetDialogBaseUnits());

    /*
     * Because some useful worker routines like WinToDUPoint use
     * the values in gcd.c*Char, set them to be the default font right
     * away.  When a dialog is loaded with a different font, they
     * will be modified.
     */
    gcd.cxChar = gcxSysChar;
    gcd.cyChar = gcySysChar;

    /*
     * Build the help file name path.  Assume the help file is in the
     * same directory as the executable.
     */
    GetModuleFileName(ghInst, gszHelpFile, CCHMAXPATH);
    *FileInPath(gszHelpFile) = CHAR_NULL;
    lstrcat(gszHelpFile, ids(IDS_HELPFILE));

    /*
     * Register the message for help from the common dialogs.
     */
    gmsgHelp = RegisterWindowMessage(HELPMSGSTRING);

    /*
     * Hook the message filter stream so that we can detect F1 keystrokes.
     */
    ghhkMsgFilter = SetWindowsHook(WH_MSGFILTER, (HOOKPROC)MsgFilterHookFunc);

    /*
     * Read the last position for the app.
     */
    if (!ReadWindowPos(szAppPos, &x, &y, &cx, &cy, &fMaximized)) {
        x = CW_USEDEFAULT;
        y = CW_USEDEFAULT;
        cx = CW_USEDEFAULT;
        cy = CW_USEDEFAULT;
        fMaximized = FALSE;
    }

    /*
     * Create the main window.
     */
    if (!(ghwndMain = CreateWindow(szMainClass, NULL,
            WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
            x, y, cx, cy, NULL, NULL, hInstance, NULL)))
        return FALSE;

    ShowFileStatus(TRUE);

    /*
     * Read the Preferences data.
     */
    ReadEnv();

    /*
     * If the app was saved when maximized (and they didn't start it up
     * with some kind of an option to have it minimized or in some
     * other funny initial state from the shell), then cause it to
     * be maximized when shown.
     */
    if (fMaximized && (nCmdShow == SW_SHOWNORMAL || nCmdShow == SW_SHOW))
        nCmdShow = SW_SHOWMAXIMIZED;

    ShowWindow(ghwndMain, nCmdShow);
    UpdateWindow(ghwndMain);

    /*
     * Did the user start this app minimized from the program manager?
     */
    if (IsIconic(ghwndMain)) {
        /*
         * Set a flag.  The showing of the toolbox will be deferred
         * until the app is restored.
         */
        fStartAsIcon = TRUE;
    }
    else {
        /*
         * If they had the Toolbox before, show it now.
         */
        if (gfShowToolbox)
            ToolboxShow(TRUE);
    }

    hcurArrow = LoadCursor(NULL, IDC_ARROW);
    hcurWait = LoadCursor(NULL, IDC_WAIT);
    hcurOutSel = LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_OUTSEL));
    hcurMove = LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_MOVE));
    hcurInsert = LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_INSERT));
    hcurDropTool = LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_DROPTOOL));
    hcurSizeNESW = LoadCursor(NULL, IDC_SIZENESW);
    hcurSizeNS = LoadCursor(NULL, IDC_SIZENS);
    hcurSizeNWSE = LoadCursor(NULL, IDC_SIZENWSE);
    hcurSizeWE = LoadCursor(NULL, IDC_SIZEWE);

    if (!hcurArrow ||
            !hcurWait ||
            !hcurOutSel ||
            !hcurMove ||
            !hcurDropTool ||
            !hcurInsert)
        return FALSE;

    if ((hDC = GetDC(ghwndMain)) == NULL)
        return FALSE;

    GetTextMetrics(hDC, &tm);

    gcyPixelsPerInch = GetDeviceCaps(hDC, LOGPIXELSY);

    /*
     * Create a memory DC for drawing bitmaps.
     */
    ghDCMem = CreateCompatibleDC(hDC);

    ReleaseDC(ghwndMain, hDC);

    /*
     * Load the bitmaps that depend on system colors.
     */
    LoadSysColorBitmaps();

    fmtDlg = RegisterClipboardFormat(L"DIALOG");

    /*
     * Initialize the icon control ordinal to the icon id from our exe
     * that we will use to show these kind of controls.
     */
    WriteOrd(&gordIcon, IDICON_ICON);

    /*
     * Initialize the default text fields in the awcd array.  Because
     * CCONTROLS does not include the dialog type, it has to be done
     * separately.
     */
    awcd[W_DIALOG].pszTextDefault = ids(awcd[W_DIALOG].idsTextDefault);
    for (i = 0; i < CCONTROLS; i++)
        awcd[i].pszTextDefault = ids(awcd[i].idsTextDefault);

    /*
     * If there was a command line argument specified, try and open
     * it as the initial file.
     */
    if (__argc > 1) {
        MultiByteToWideChar(CP_ACP, 0, __argv[1], -1, szArg1, CCHTEXTMAX);
        OpenCmdLineFile(szArg1);
    }

    /*
     * Be sure the focus is on the main window.  This corrects a
     * problem where the accelerators don't initially work because
     * the focus gets placed on the Properties Bar.
     */
    SetFocus(ghwndMain);

    return TRUE;
}



/************************************************************************
* PenWinRegister
*
* This function will register for Pen Windows, if it is present.
*
************************************************************************/

STATICFN VOID PenWinRegister(VOID)
{
    HANDLE hmod;

    if (!(hmod = (HANDLE)GetSystemMetrics(SM_PENWINDOWS)))
        return;

    if (lpfnRegisterPenApp =
            (LPFNPENWIN)GetProcAddress(hmod, "RegisterPenApp"))
        (*lpfnRegisterPenApp)(1, TRUE);     // Be Pen-Enhanced!
}



/************************************************************************
* GetSystemValues
*
* This function reads various system values.  It is called at init time,
* as well as if we are informed by a WM_SYSVALUECHANGED message that
* some of these values have been changed.
*
************************************************************************/

STATICFN VOID GetSystemValues(VOID)
{
    gcyBorder = GetSystemMetrics(SM_CYBORDER);

    /*
     * The distance that the mouse can move during a pre-drag operation
     * before starting to drag the control anyways is based on the
     * mouse double-click movement distances in the system.
     */
    gcxPreDragMax = GetSystemMetrics(SM_CXDOUBLECLK);
    gcyPreDragMax = GetSystemMetrics(SM_CYDOUBLECLK);

    /*
     * The number of milliseconds that the pre-drag debounce time lasts.
     */
    gmsecPreDrag = 250;
}



/************************************************************************
* ReadWindowPos
*
* This function retrieves the saved window position for a window and
* returns it in the specified variables.  It is used between sessions
* to restore the application windows to the position they had when
* the editor was last exited.
*
* Arguments:
*   LPTSTR pszKeyName  - KeyName the position was saved under.
*   PINT px            - Saved x position.
*   PINT py            - Saved y position.
*   PINT pcx           - Saved width.
*   PINT pcy           - Saved height.
*   BOOL *pfMaximized  - Set to TRUE if window was maximized when saved.
*
* Returns: 
*   TRUE if the position could be read, or FALSE otherwise.
*   If FALSE is returned, the values in the specified variables are
*   not valid!  The caller must be able to handle a FALSE return and
*   supply a default position for the window.
*
************************************************************************/

BOOL ReadWindowPos(
    LPTSTR pszKeyName,
    PINT px,
    PINT py,
    PINT pcx,
    PINT pcy,
    BOOL *pfMaximized)
{
    static CHAR szSep[] = " ,";
    TCHAR szBuf[CCHTEXTMAX];
    CHAR szBufAnsi[CCHTEXTMAX];
    PSTR psz;
    BOOL fDefCharUsed;

    if (!GetPrivateProfileString(ids(IDS_APPNAME),
            pszKeyName, szEmpty, szBuf, CCHTEXTMAX, ids(IDS_DLGEDITINI)))
        return FALSE;

    WideCharToMultiByte(CP_ACP, 0, szBuf, -1, szBufAnsi, CCHTEXTMAX,
            NULL, &fDefCharUsed);

    if (!(psz = strtok(szBufAnsi, szSep)))
        return FALSE;

    *px = atoi(psz);

    if (!(psz = strtok(NULL, szSep)))
        return FALSE;

    *py = atoi(psz);

    if (!(psz = strtok(NULL, szSep)))
        return FALSE;

    *pcx = atoi(psz);

    if (!(psz = strtok(NULL, szSep)))
        return FALSE;

    *pcy = atoi(psz);

    /*
     * If there is a "1" following the coordinates, the window was
     * maximized when it was saved.
     */
    *pfMaximized = FALSE;
    if ((psz = strtok(NULL, szSep)) && atoi(psz) == 1)
        *pfMaximized = TRUE;

    /*
     * Don't allow a zero sized window.
     */
    if (*pcx == 0 || *pcy == 0)
        return FALSE;

    /*
     * Return success.
     */
    return TRUE;

}



/************************************************************************
* WriteWindowPos
*
* This function writes the position of a window to the
* editor's profile file under the specified keyname.
* The ReadWindowPos function is the counterpart of this
* function.
*
* Arguments:
*   PRECT prc          - Rectangle for the "restored" window size.
*   BOOL fMaximized    - TRUE if the window is maximized.
*   LPTSTR pszKeyName  - KeyName to save the position under.
*
************************************************************************/

VOID WriteWindowPos(
    PRECT prc,
    BOOL fMaximized,
    LPTSTR pszKeyName)
{
    TCHAR szBuf[CCHTEXTMAX];

    wsprintf(szBuf, L"%d %d %d %d", prc->left, prc->top,
            prc->right - prc->left, prc->bottom - prc->top);

    if (fMaximized)
        lstrcat(szBuf, L" 1");

    WritePrivateProfileString(ids(IDS_APPNAME),
            pszKeyName, szBuf, ids(IDS_DLGEDITINI));
}



/*************************************************************************
* ReadEnv
*
* This function initializes variables from their counterparts
* in the private profile file for DlgEdit.  The application
* merely needs to construct an array of INIENTRY structures
* to describe the variables that must be initialized.
*
* Note that the original value read from the profile is saved when
* it is read.  This allows us to optimize what needs to be written
* out with WriteEnv.
*
*************************************************************************/

STATICFN VOID ReadEnv(VOID)
{
    register INT i;

    for (i = 0; gaie[i].pszKeyName; i++) {
        *gaie[i].pnVar = gaie[i].nSave =
                GetPrivateProfileInt(ids(IDS_APPNAME),
                gaie[i].pszKeyName, gaie[i].nDefault,
                ids(IDS_DLGEDITINI));
    }

    ReadCustomProfile();
}



/*************************************************************************
* WriteEnv
*
* This function is the counterpart to ReadEnv.  It saves values
* in the profile file.
*
*************************************************************************/

STATICFN VOID WriteEnv(VOID)
{
    register INT i;
    TCHAR szBuf[17];

    for (i = 0; gaie[i].pszKeyName; i++) {
        /*
         * Has the user changed the value since it was read?
         */
        if (gaie[i].nSave != *gaie[i].pnVar) {
            /*
             * If the new value is the same as the default value,
             * erase the entry from the ini file.  Otherwise,
             * write the user-specified value out.
             */
            if (*gaie[i].pnVar == gaie[i].nDefault) {
                WritePrivateProfileString(ids(IDS_APPNAME),
                        gaie[i].pszKeyName, NULL, ids(IDS_DLGEDITINI));
            }
            else {
                itoaw(*gaie[i].pnVar, szBuf, 10);
                WritePrivateProfileString(ids(IDS_APPNAME),
                        gaie[i].pszKeyName, szBuf, ids(IDS_DLGEDITINI));
            }
        }
    }

    WriteCustomProfile();
}



/************************************************************************
* MainWndProc
*
* This is the window procedure for the "dlgedit" class.  This is the
* class of the main dialog editor "client" window.
*
************************************************************************/

WINDOWPROC MainWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_CREATE:
            {
                RECT rc;

                /*
                 * Create the status window.
                 */
                CreateDialog(ghInst, MAKEINTRESOURCE(DID_STATUS),
                        hwnd, StatusDlgProc);

                /*
                 * Save away its height for sizing later (like when
                 * the app is minimized then restored).
                 */
                GetWindowRect(hwndStatus, &rc);
                gcyStatus = rc.bottom - rc.top;

                ghwndSubClient = CreateWindow(szSubClientClass, NULL,
                        WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
                        hwnd, NULL, ghInst, NULL);

                ghMenuMain = GetMenu(hwnd);
                LoadMenuBitmaps(ghMenuMain);
            }

            break;

        case WM_ACTIVATE:
            /*
             * If the main window is getting activated, there is no
             * currently active dialog.
             */
            if (LOWORD(wParam))
                gidCurrentDlg = 0;

            goto DoDefault;

        case WM_INITMENU:
            if (GetMenu(ghwndMain) == (HMENU)wParam)
                InitMenu((HMENU)wParam);

            break;

        case WM_MENUSELECT:
            if (HIWORD(wParam) &
                    (MF_POPUP | MF_SYSMENU))
                gMenuSelected = 0;
            else
                gMenuSelected = LOWORD(wParam);

            break;

        case WM_COMMAND:
            DialogMenu(LOWORD(wParam));
            break;

        case WM_KEYDOWN:
            switch (wParam) {
                case VK_UP:
                case VK_DOWN:
                case VK_LEFT:
                case VK_RIGHT:
                    if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                            (GetKeyState(VK_CONTROL) & 0x8000))
                        break;

                    /*
                     * Ignore it if we are not in a normal state
                     * (don't allow when dragging).
                     */
                    if (gState != STATE_NORMAL)
                        break;

                    /*
                     * Be sure any outstanding changes get applied
                     * without errors.
                     */
                    if (!StatusApplyChanges())
                        break;

                    /*
                     * Move the control in the specified direction.
                     */
                    MoveControl(wParam);
                    break;

                case VK_TAB:
                    if (GetKeyState(VK_CONTROL) & 0x8000)
                        break;

                    /*
                     * Ignore it if we are not in a normal state
                     * (don't allow when dragging).
                     */
                    if (gState != STATE_NORMAL)
                        break;

                    /*
                     * Be sure any outstanding changes get applied
                     * without errors.
                     */
                    if (!StatusApplyChanges())
                        break;

                    /*
                     * Is the shift key pressed also?
                     */
                    if (GetKeyState(VK_SHIFT) & 0x8000)
                        SelectPrevious();
                    else
                        SelectNext();

                    break;

                case VK_ESCAPE:
                    if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                            (GetKeyState(VK_CONTROL) & 0x8000))
                        break;

                    /*
                     * Be sure any outstanding changes get applied
                     * without errors.
                     */
                    if (!StatusApplyChanges())
                        break;

                    if (gState == STATE_SELECTING)
                        OutlineSelectCancel();

                    /*
                     * Cancel any drag operation they might have been doing.
                     */
                    if (gState != STATE_NORMAL)
                        DragCancel();

                    break;

                case VK_RETURN:
                    if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                            (GetKeyState(VK_CONTROL) & 0x8000))
                        break;

                    /*
                     * Be sure any outstanding changes get applied
                     * without errors.
                     */
                    if (!StatusApplyChanges())
                        break;

                    switch (gState) {
                        POINTS mpt;
                        POINT pt;
                        DWORD dwPos;

                        case STATE_SELECTING:
                            /*
                             * In outline selection mode.  Map the
                             * location of the mouse at the time that
                             * the user pressed Enter into a point
                             * relative to the dialog client and complete
                             * the selection operation.
                             */
                            dwPos = GetMessagePos();
                            mpt = (*((POINTS *)&(dwPos)));
                            ((pt).x = (mpt).x, (pt).y = (mpt).y);
                            ScreenToClient(gcd.npc->hwnd, &pt);
                            OutlineSelectEnd(pt.x, pt.y);

                            break;

                        case STATE_DRAGGING:
                        case STATE_DRAGGINGNEW:
                            /*
                             * We are dragging something.  Map the
                             * location of the mouse at the time
                             * that the user pressed Enter into a
                             * point relative to the proper window
                             * and complete the drag operation.
                             */
                            dwPos = GetMessagePos();
                            mpt = (*((POINTS *)&(dwPos)));
                            ((pt).x = (mpt).x, (pt).y = (mpt).y);

                            /*
                             * The point must be changed to be relative to
                             * the window that the ending mouse up message
                             * would have come through, which will be the
                             * capture window for the drag.  This will be
                             * the dialog if we are adding a new control,
                             * or it will be the selected control if we are
                             * dragging an existing control.
                             */
                            ScreenToClient((gState == STATE_DRAGGING) ?
                                    gnpcSel->hwnd : gcd.npc->hwnd, &pt);

                            /*
                             * If the dialog is selected, map the points from
                             * the client area to the window.
                             */
                            if (gfDlgSelected)
                                MapDlgClientPoint(&pt, TRUE);

                            DragEnd(pt.x, pt.y);

                            break;
                    }

                    break;
            }

            break;

        case WM_NCCALCSIZE:
            /*
             * Save away what is going to be the new window position.
             */
            if (!IsIconic(hwnd) && !IsZoomed(hwnd))
                grcAppPos = *((LPRECT)lParam);

            /*
             * Now let the DefWindowProc calculate the client area normally.
             */
            goto DoDefault;

        case WM_MOVE:
            if (gfEditingDlg)
                RepositionDialog();

            break;

        case WM_SIZE:
            SizeRibbons(hwnd);

            /*
             * Did the app start minimized and is it being restored
             * for the first time?  If so, show the toolbox if
             * the user has requested it.
             */
            if (fStartAsIcon && !IsIconic(hwnd)) {
                if (gfShowToolbox)
                    ToolboxShow(TRUE);

                fStartAsIcon = FALSE;
            }

            break;

        case WM_SYSCOLORCHANGE:
            LoadSysColorBitmaps();
            break;

        case WM_CLOSE:
            if (ghwndTestDlg)
                DestroyTestDialog();

            if (DoWeSave(FILE_INCLUDE) == IDCANCEL ||
                    DoWeSave(FILE_RESOURCE) == IDCANCEL)
                break;

            /*
             * First destroy the Properties Bar.
             */
            DestroyWindow(hwndStatus);
            hwndStatus = NULL;

            DestroyWindow(hwnd);
            break;

        case WM_QUERYENDSESSION:
            if (ghwndTestDlg)
                DestroyTestDialog();

            if (DoWeSave(FILE_INCLUDE) == IDCANCEL ||
                    DoWeSave(FILE_RESOURCE) == IDCANCEL)
                return FALSE;
            else
                return TRUE;

        case WM_DESTROY:
            /*
             * Save the position of the app's window.
             */
            WriteWindowPos(&grcAppPos, IsZoomed(hwnd), szAppPos);

            WinHelp(hwnd, gszHelpFile, HELP_QUIT, 0L);
            FreeMenuBitmaps();
            PostQuitMessage(0);
            break;

        default:
            /*
             * Is this the registered help message from one of the common
             * dialogs?  If so, show the help for it.
             *
             * The check to be sure gmsgHelp is non-zero is just in
             * case the call to register the help message failed
             * (it will return zero) and there happens to be a zero
             * message that gets sent to this window somehow.
             */
            if (msg == gmsgHelp && gmsgHelp) {
                ShowHelp(FALSE);
                return 0;
            }

        DoDefault:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}



/************************************************************************
* LoadSysColorBitmaps
*
* This function loads bitmaps that depend on the system window and
* highlight colors.  As it loads them, it replaces two special colors
* in them with some system colors.
* This is used for the control type bitmaps that appear in lines
* in the listbox in the Order/Group dialog.
*
************************************************************************/

STATICFN VOID LoadSysColorBitmaps(VOID)
{
    DWORD rgbWindow;
    DWORD rgbWindowText;
    DWORD rgbHighlight;
    DWORD rgbHighlightText;
    INT i;

    rgbWindow = GetSysColor(COLOR_WINDOW);
    rgbWindowText = GetSysColor(COLOR_WINDOWTEXT);
    rgbHighlight = GetSysColor(COLOR_HIGHLIGHT);
    rgbHighlightText = GetSysColor(COLOR_HIGHLIGHTTEXT);

    if (hbmTabStop)
        DeleteObject(hbmTabStop);

    hbmTabStop = LoadAlterBitmap(IDBM_TABSTOP, rgbWindow, rgbWindowText);

    if (hbmTabStopSel)
        DeleteObject(hbmTabStopSel);

    hbmTabStopSel = LoadAlterBitmap(IDBM_TABSTOP,
            rgbHighlight, rgbHighlightText);

    for (i = 0; i < CCONTROLS; i++) {
        if (awcd[i].hbmCtrlType)
            DeleteObject(awcd[i].hbmCtrlType);

        awcd[i].hbmCtrlType = LoadAlterBitmap(
                awcd[i].idbmCtrlType, rgbWindow, rgbWindowText);

        if (awcd[i].hbmCtrlTypeSel)
            DeleteObject(awcd[i].hbmCtrlTypeSel);

        awcd[i].hbmCtrlTypeSel = LoadAlterBitmap(
                awcd[i].idbmCtrlType, rgbHighlight, rgbHighlightText);
    }

    if (ghbmDragHandle)
        DeleteObject(ghbmDragHandle);

    ghbmDragHandle = LoadAlterBitmap(IDBM_DRAGHANDLE,
            rgbWindow, rgbHighlight);

    if (ghbmDragHandle2)
        DeleteObject(ghbmDragHandle2);

    ghbmDragHandle2 = LoadAlterBitmap(IDBM_DRAGHANDLE2,
            rgbWindow, rgbHighlight);
}



/************************************************************************
* LoadAlterBitmap
*
* This function loads a single bitmap.  As it does, it replaces a
* couple special RGB colors (REPLACECOLOR1 and REPLACECOLOR2) with
* the passed in RGB colors.
*
* Arguments:
*   INT idbm      - Integer ID of the bitmap to load.
*   DWORD rgbNew  - Color to replace the special color with.
*   DWORD rgbNew2 - A second color to replace the second special color with.
*
* Returns:
*   The handle to the bitmap, or NULL if an error occurs.
*
************************************************************************/

STATICFN HBITMAP LoadAlterBitmap(
    INT idbm,
    DWORD rgbNew,
    DWORD rgbNew2)
{
    register INT i;
    LPBITMAPINFOHEADER lpbihInfo;
    HDC hdcScreen;
    HANDLE hresLoad;
    HANDLE hres;
    DWORD FAR *qlng;
    LPBYTE lpbBits;
    HANDLE hbmp;
    DWORD rgbReplace1;
    DWORD rgbReplace2;

    hresLoad = FindResource(ghInst, MAKEINTRESOURCE(idbm), RT_BITMAP);
    if (!hresLoad)
        return NULL;

    hres = LoadResource(ghInst, hresLoad);
    if (!hresLoad)
        return NULL;

    rgbNew = RGBInvertRGB(rgbNew);
    rgbNew2 = RGBInvertRGB(rgbNew2);
    rgbReplace1 = RGBInvertRGB(REPLACECOLOR1);
    rgbReplace2 = RGBInvertRGB(REPLACECOLOR2);
    lpbihInfo = (LPBITMAPINFOHEADER)LockResource(hres);
    qlng = (LPDWORD)((PBYTE)(lpbihInfo) + lpbihInfo->biSize);

    for (i = 0; i < (1 << lpbihInfo->biBitCount); i++, qlng++) {
        if (*qlng == rgbReplace1)
            *qlng = rgbNew;
        else if (*qlng == rgbReplace2)
            *qlng = rgbNew2;
    }

    /*
     * First skip over the header structure.
     */
    lpbBits = (LPBYTE)(lpbihInfo + 1);

    /*
     * Skip the color table entries, if any.
     */
    lpbBits += (1 << (lpbihInfo->biBitCount)) * sizeof(RGBQUAD);

    /*
     * Create a color bitmap compatible with the display device.
     */
    if (hdcScreen = GetDC(NULL)) {
        hbmp = CreateDIBitmap(hdcScreen, lpbihInfo, (LONG)CBM_INIT,
                lpbBits, (LPBITMAPINFO)lpbihInfo, DIB_RGB_COLORS);
        ReleaseDC(NULL, hdcScreen);
    }

    UnlockResource(hres);
    FreeResource(hres);

    return hbmp;
}



/************************************************************************
* RGBInvertRGB
*
* Reverses the RGB order of a color.  This needs to be done to match
* the resource file format of the color table.
*
************************************************************************/

STATICFN DWORD RGBInvertRGB(
    DWORD rgb)
{
    return (DWORD)RGB(GetBValue(rgb), GetGValue(rgb), GetRValue(rgb));
}



/************************************************************************
* SizeRibbons
*
* This function positions and sizes the child ribbon and subclient
* windows in the dialog editor.  It needs to be called any time the
* size of the main windows changes.
*
* Arguments:
*   HWND hwnd - Parent window handle.
*
************************************************************************/

STATICFN VOID SizeRibbons(
    HWND hwnd)
{
    RECT rcClient;

    if (hwndStatus && !IsIconic(hwnd)) {
        /*
         * Get the client area.
         */
        GetClientRect(hwnd, &rcClient);

        /*
         * Size/move the status and subclient window to fit
         * the new client area.
         */
        SetWindowPos(hwndStatus, NULL,
                0, 0,
                rcClient.right - rcClient.left,
                min(rcClient.bottom - rcClient.top, gcyStatus),
                SWP_NOACTIVATE | SWP_NOZORDER);

        SetWindowPos(ghwndSubClient, NULL,
                0, gcyStatus,
                rcClient.right - rcClient.left,
                max((rcClient.bottom - rcClient.top) - gcyStatus, 0),
                SWP_NOACTIVATE | SWP_NOZORDER);
    }
}



/****************************************************************************
* DialogTerminate
*
* This undoes what DialogInit does.  It should be called before terminating
* and after a DialogInit.
*
****************************************************************************/

STATICFN VOID DialogTerminate(VOID)
{
    register INT i;

    /*
     * Save the Preferences data.
     */
    WriteEnv();

    if (hbmTabStop)
        DeleteObject(hbmTabStop);

    if (hbmTabStopSel)
        DeleteObject(hbmTabStopSel);

    if (ghbmDragHandle)
        DeleteObject(ghbmDragHandle);

    if (ghbmDragHandle2)
        DeleteObject(ghbmDragHandle2);

    if (ghDCMem)
        DeleteDC(ghDCMem);

    /*
     * Free the control type bitmaps.
     */
    for (i = 0; i < CCONTROLS; i++) {
        if (awcd[i].hbmCtrlType)
            DeleteObject(awcd[i].hbmCtrlType);

        if (awcd[i].hbmCtrlTypeSel)
            DeleteObject(awcd[i].hbmCtrlTypeSel);
    }

    /*
     * Free all the custom control links.  This must be done before the
     * app exits so that any loaded DLL's get unloaded!
     */
    while (gpclHead)
        RemoveCustomLink(gpclHead);

    if (hpenDarkGray)
        DeleteObject(hpenDarkGray);

    if (ghhkMsgFilter)
        UnhookWindowsHookEx(ghhkMsgFilter);

    if (lpfnRegisterPenApp)
        (*lpfnRegisterPenApp)((WORD)1, FALSE);
}
