DLGEDIT.C


/******************************************************************************\
* This is a part of the Microsoft Source Code Samples.
* Copyright 1993 - 1998 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>
#if defined(DBCS) && !defined(UNICODE)
#define _MBCS
#include <mbstring.h>
#define strtok _mbstok
#endif

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);
}