/****************************************************************************
**
**  Name:  msgboxex.c
**
**  Description:
**     An enhanced message box with help, for use by the error text
**     server (at least).  The code is originally from the article
**     by Steven Palmer titled "Add Options to Your Windows Message
**     Boxes" from the December 1992 edition of the Windows/DOS
**     Developer's Journal (page 37).  Rather than risk introducing
**     errors, I've left this mostly alone, except for adding comments
**     (there was a one-line comment containing the name of the main
**     function, and nothing else) and attempting to make the indentation
**     and brace placement compatible with our coding standards.
**
**  Status:  CODED
**
**  $Log:   S:/tbird/mt2_186/errtext/msgboxex.c_v  $
** 
**    Rev 1.0   16 Dec 1996 14:29:34   Judy
** Initial revision.
** 
**    Rev 1.6   14 Jul 1994 14:22:40   nghia
** Removed REgisterHelpEntry() prototype (it's in hlpentry.h).
** 
**    Rev 1.5   20 Apr 1994 15:45:20   nghia
** Removed the SYSTEM_MODAL style for Error message box. Help window failed to
** open with this style.
** 
**    Rev 1.4   18 Apr 1994 17:52:02   nghia
** Force the error message box to be a SYSTEM MODAL style.  This will ensure 
** that PV will not run into re-entrancy problem.
** 
**    Rev 1.3   09 Jul 1993 16:36:40   ron
** added call to RegisterHelpEntry (in cliulib)
** 
**    Rev 1.2   03 Jun 1993 16:35:06   ron
** actor error consolidation -- working checkpoint
** 
**    Rev 1.1   25 May 1993 16:59:26   ron
** Checking in to get into build: may still have to change (but tested!)
** 
**    Rev 1.0   19 May 1993 09:46:32   ron
** Initial revision.
** 
**  $Header:   S:/tbird/mt2_186/errtext/msgboxex.c_v   1.0   16 Dec 1996 14:29:34   Judy  $
**
**  Copyright (C) 1993 Microtek International.  All rights reserved.
**
*****************************************************************************/


                  /****************************
                  *                           *
                  *      INCLUDE FILES        *
                  *                           *
                  ****************************/
#include <windows.h>
#ifndef _BASEWIND_
#include "basewind.h"
#endif
#ifndef _MSGBOXEX_
#include "msgboxex.h"
#endif

#ifndef _HLPENTRY_
#include "hlpentry.h"
#endif
                  /****************************
                  *                           *
                  *    LOCAL DEFINITIONS      *
                  *                           *
                  ****************************/


#define MSGBOXEX_SPACING        18
#define MSGBOXEX_BUTTONWIDTH    58
#define MSGBOXEX_BUTTONHEIGHT   28
#define MSGBOXEX_BUTTONSPACING  16
#define MSGBOXEX_BOTTOMMARGIN   10
#define MSGBOXEX_MAXWIDTH       240
#define MSGBOXEX_MINHEIGHT      70

#define IDD_ICON                0xA90

typedef struct {
   long  dtStyle;
   BYTE  dtItemCount;
   int   dtX;
   int   dtY;
   int   dtCX;
   int   dtCY;
   char  dtMenuName[1];
   char  dtClassName[1];
   char  dtCaptionText[1];
} DLGTEMPLATE;

typedef struct {
   int   dtilX;
   int   dtilY;
   int   dtilCX;
   int   dtilCY;
   int   dtilID;
   long  dtilStyle;
   char  dtilClass[1];
   char  dtilText[1];
   BYTE  dtilInfo;
} DLGITEMTEMPLATE;

                  /****************************
                  *                           *
                  *   EXTERNAL VARIABLES      *
                  *                           *
                  ****************************/

                  /****************************
                  *                           *
                  *    LOCAL PROTOTYPES       *
                  *                           *
                  ****************************/

int AddDlgItem(HANDLE, int, int, int, int,
                       int, int, long, LPSTR, LPSTR);

int AddButtonItem(HANDLE, int, int, int, int, LPSTR);


BOOL FAR PASCAL _export MsgBoxDlgProc(HWND, unsigned, WORD, LONG);

LONG FAR PASCAL _export IconProc(HWND, unsigned, WORD, LONG);

WNDPROC lpfnHookProc = NULL;   /* MessageBox hook procedure */
HICON hIcon;                   /* Handle of icon */
int nDefButton;                /* Default button */

STATIC char * szOK    = "&OK";
STATIC char * szCancel = "&Cancel";
STATIC char * szIgnore = "&Ignore";
STATIC char * szRetry  = "&Retry";
STATIC char * szAbort  = "&Abort";
STATIC char * szHelp   = "&Help";
STATIC char * szYes   = "&Yes";
STATIC char * szNo    = "&No";

extern DWORD ErrHelpEntry; /* needed for F1 windows hook */

                  /****************************
                  *                           *
                  *     EXECUTABLE CODE       *
                  *                           *
                  ****************************/


/*****************************************************************************
** MessageBoxEx
**
** Description:
**   Put up an "extended" message box, sized to fit the text in lpcszStr.
**   This is intended for use displaying error dialogs with a help button.
**
** Parameters:
**   hwnd (in): the handle of the parent window
**   lpcszStr (in): the text to display in the message box
**   lpcszTitle (in): the caption of the message box
**   fuStyle (in): the style (as in the normal MessageBox)
**   fuStyleEx (in): the "extended" style bits - one or both of:
**                 MB_EX_HELP -- it has a "Help" button
**                 MB_EX_DEFBUTTON4 -- "Help" button is the default
**                   if it is the fourth button in the message box.
**   lpfMsgProc (in): the function to call if MB_EX_HELP is set, for ALL
**                 messages sent to the dialog box.
**      
*****************************************************************************/
long FAR PASCAL MessageBoxEx(HWND hwnd, LPSTR lpcszStr,
   LPSTR lpcszTitle, unsigned fuStyle,
   unsigned fuStyleEx, WNDPROC lpfMsgProc) {

   DLGTEMPLATE FAR * lpDlgTmp;
   HINSTANCE hInst;
   FARPROC lpMsgBoxDlgProc;
   HGLOBAL hDlgTmp;
   int nRetCode, cwButtonRow, cbTemplate, cxMsgBox, cyMsgBox;
   int cItems, dxLeft, x, y;
   HDC hdc;
   RECT rc;

   if(fuStyle == (MB_ICONHAND|MB_SYSTEMMODAL) && fuStyleEx == 0)
      return(MessageBox(hwnd, lpcszStr, lpcszTitle, fuStyle));
   
   hdc = CreateDC("DISPLAY", NULL, NULL, NULL);
   SelectObject(hdc, GetStockObject(SYSTEM_FONT));
   SetRect(&rc, 0, 0, MSGBOXEX_MAXWIDTH, 0);
   DrawText(hdc, lpcszStr, -1, &rc, DT_LEFT|
             DT_NOPREFIX|DT_WORDBREAK|DT_CALCRECT);
   rc.right += 10;
   DeleteDC(hdc);

   if(!lpcszTitle)
      lpcszTitle = "Error";

   cyMsgBox = rc.bottom + (MSGBOXEX_SPACING * 2);
   cxMsgBox = rc.right + (MSGBOXEX_SPACING * 2);
   dxLeft = MSGBOXEX_SPACING;
   hIcon = NULL;

   cbTemplate = sizeof(DLGTEMPLATE) + lstrlen(lpcszTitle);
   hDlgTmp = GlobalAlloc(GHND, cbTemplate);
   if(!hDlgTmp)
      return(0);

   lpDlgTmp = (DLGTEMPLATE FAR *)GlobalLock(hDlgTmp);
   if(!lpDlgTmp) {
      GlobalFree(hDlgTmp);
      return(0);
   }
   lpDlgTmp->dtStyle = WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU;
   lpDlgTmp->dtItemCount = 0;
   lpDlgTmp->dtX = 0;
   lpDlgTmp->dtY = 0;
   lpDlgTmp->dtMenuName[0] = '\0';
   lpDlgTmp->dtClassName[0] = '\0';
   lstrcpy(lpDlgTmp->dtCaptionText, lpcszTitle);
   GlobalUnlock(hDlgTmp);

   if(fuStyle & MB_SYSTEMMODAL)
      lpDlgTmp->dtStyle |= DS_SYSMODAL;

   if(cyMsgBox < MSGBOXEX_MINHEIGHT)
      cyMsgBox = MSGBOXEX_MINHEIGHT;

   if(fuStyle & MB_ICONMASK) {
      int cxIcon;
      int cyIcon;
      LPSTR lpIcon;
      switch(fuStyle & MB_ICONMASK) {
         case MB_ICONEXCLAMATION:
            lpIcon = (LPSTR)IDI_EXCLAMATION;
            break;
         case MB_ICONHAND:
            lpIcon = (LPSTR)IDI_HAND;
            break;
         case MB_ICONQUESTION:
            lpIcon = (LPSTR)IDI_QUESTION;
            break;
         case MB_ICONASTERISK:
            lpIcon = (LPSTR)IDI_ASTERISK;
            break;
      }
      hIcon = LoadIcon(NULL, lpIcon);

      cxIcon = GetSystemMetrics(SM_CXICON);
      cyIcon = GetSystemMetrics(SM_CYICON);
      dxLeft += MSGBOXEX_SPACING + cxIcon;
      cxMsgBox += dxLeft;
      y = (cyMsgBox - cyIcon) / 2;

      cbTemplate += AddDlgItem(hDlgTmp, cbTemplate,
            MSGBOXEX_SPACING, y, cxIcon, cyIcon,
            IDD_ICON, WS_CHILD|SS_ICON,"static", "");
   }
   y = (cyMsgBox - rc.bottom) / 2;
   cbTemplate += AddDlgItem(hDlgTmp, cbTemplate,
            dxLeft, y, rc.right, rc.bottom, -1,
            WS_CHILD|SS_LEFT,"static", lpcszStr);
   switch(fuStyle & MB_TYPEMASK) {
      case MB_OK:               cItems = 1;   break;
      case MB_OKCANCEL:         cItems = 2;   break;
      case MB_YESNO:            cItems = 2;   break;
      case MB_YESNOCANCEL:      cItems = 3;   break;
      case MB_ABORTRETRYIGNORE: cItems = 3;   break;
      case MB_RETRYCANCEL:      cItems = 2;   break;
   }
   if(fuStyleEx & MB_EX_HELP)
      ++cItems;
   cwButtonRow = MSGBOXEX_BUTTONWIDTH * cItems +
         (MSGBOXEX_BUTTONSPACING * (cItems - 1));
   if(cwButtonRow > (cxMsgBox - dxLeft))
      cxMsgBox = dxLeft + cwButtonRow +
                 MSGBOXEX_BUTTONSPACING * 2;
   lpfnHookProc = lpfMsgProc;
   nDefButton = 1;
   switch(fuStyle & MB_DEFMASK) {
      case MB_DEFBUTTON1:   nDefButton = 1;   break;
      case MB_DEFBUTTON2:   nDefButton = 2;   break;
      case MB_DEFBUTTON3:   nDefButton = 3;   break;
   }
   if(fuStyleEx & MB_EX_DEFBUTTON4)
      nDefButton = 4;
   x = (cxMsgBox - cwButtonRow) / 2;
   switch(fuStyle & MB_TYPEMASK) {
      case MB_OK:
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x, cyMsgBox, IDOK, szOK);
         break;
      case MB_OKCANCEL:
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x, cyMsgBox, IDOK, szOK);
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x += MSGBOXEX_BUTTONSPACING +
               MSGBOXEX_BUTTONWIDTH, cyMsgBox, IDCANCEL,
               szCancel);
         break;
      case MB_YESNO:
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x, cyMsgBox, IDYES, szYes);
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x += MSGBOXEX_BUTTONSPACING +
               MSGBOXEX_BUTTONWIDTH, cyMsgBox, IDNO,
               szNo);
         break;
      case MB_YESNOCANCEL:
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x, cyMsgBox, IDYES, szYes);
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x += MSGBOXEX_BUTTONSPACING +
               MSGBOXEX_BUTTONWIDTH, cyMsgBox, IDNO, szNo);
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x += MSGBOXEX_BUTTONSPACING +
               MSGBOXEX_BUTTONWIDTH, cyMsgBox, IDCANCEL,
               szCancel);
         break;
      case MB_ABORTRETRYIGNORE:
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x, cyMsgBox, IDABORT,
               szAbort);
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x += MSGBOXEX_BUTTONSPACING +
               MSGBOXEX_BUTTONWIDTH, cyMsgBox, IDRETRY,
               szRetry);
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x += MSGBOXEX_BUTTONSPACING +
               MSGBOXEX_BUTTONWIDTH, cyMsgBox, IDIGNORE,
               szIgnore);
         break;
      case MB_RETRYCANCEL:
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x, cyMsgBox, IDRETRY,
               szRetry);
         cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x += MSGBOXEX_BUTTONSPACING +
               MSGBOXEX_BUTTONWIDTH, cyMsgBox,
               IDCANCEL, szCancel);
         break;
   }
   if(fuStyleEx & MB_EX_HELP)
      cbTemplate += AddButtonItem(hDlgTmp,
               cbTemplate, x += MSGBOXEX_BUTTONSPACING +
               MSGBOXEX_BUTTONWIDTH, cyMsgBox, IDEXHELP,
               szHelp);
   lpDlgTmp = (DLGTEMPLATE FAR *)GlobalLock(hDlgTmp);
   if(!lpDlgTmp) {
      GlobalFree(hDlgTmp);
      return(0);
   }
   cyMsgBox += MSGBOXEX_BOTTOMMARGIN +
               MSGBOXEX_BUTTONHEIGHT;
   cyMsgBox = (cyMsgBox * 8) /
                  HIWORD(GetDialogBaseUnits());
   cxMsgBox = (cxMsgBox * 4) /
                  LOWORD(GetDialogBaseUnits());
   lpDlgTmp->dtCX = cxMsgBox;
   lpDlgTmp->dtCY = cyMsgBox;
   GlobalUnlock(hDlgTmp);

   hInst = (HINSTANCE)GetWindowWord(hwnd, GWW_HINSTANCE);
   lpMsgBoxDlgProc = MakeProcInstance((FARPROC)MsgBoxDlgProc, hInst);
   nRetCode = DialogBoxIndirect(hInst, hDlgTmp,
                        hwnd, (DLGPROC)lpMsgBoxDlgProc);
   FreeProcInstance(lpMsgBoxDlgProc);
   GlobalFree(hDlgTmp);
   return(nRetCode);
}


/*****************************************************************************
** AddButtonItem
**
** Description:
**   Add a button to a dynamically sized dialog box
**
** Parameters:
**   hDlgTmp (in): the handle of the dialog box
**   cbTemplate (in): lower bound on the number of bytes of memory needed
**   x (in): the x coordinate in dialog coordinates of the button
**   y (in): the y coordinate in dialog coordinates of the button
**   dtilID (in): the ID (IDOK, IDEXHELP) of the button
**   lpszStr (in): the button text
**      
*****************************************************************************/
PRIVATE int AddButtonItem(HANDLE hDlgTmp,
   int cbTemplate, int x, int y,
   int dtilID, LPSTR lpszStr)
{
   long lStyle;

   lStyle = WS_CHILD | WS_TABSTOP |
            (--nDefButton ? BS_PUSHBUTTON : BS_DEFPUSHBUTTON);
   return(AddDlgItem(hDlgTmp, cbTemplate, x, y,
               MSGBOXEX_BUTTONWIDTH,
               MSGBOXEX_BUTTONHEIGHT,
               dtilID, lStyle, "button", lpszStr));
}


/*****************************************************************************
** MsgBoxDlgProc
**
** Description:
**   This is the procedure that handles the messages sent to the
**   dialog box.  If MB_EX_HELP is used in the "extended Style", 
**   the function passed as the lpfMsgProc argument to MessageBoxEx
**   will be called first, and if the return code is non-zero, this
**   function will return that value (except if the message is
**   WM_INITDIALOG).
**
** Parameters:
**   hwnd (in): the handle of the dialog box
**   message (in): the normal windows message type
**   wParam (in): the normal windows wParam
**   lParam (in): the normal windows lParam
**      
*****************************************************************************/
BOOL FAR PASCAL _export MsgBoxDlgProc(HWND hwnd,
   unsigned message, WORD wParam, LONG lParam)
{
   if(lpfnHookProc) {
      LONG lResult;

      lResult = (*lpfnHookProc)(hwnd, message,
                                 wParam, lParam);
      if(lResult && message != WM_INITDIALOG)
         return((BOOL)lResult);
   }
   switch(message) {
      case WM_INITDIALOG: {
         RECT rc;
         if(hIcon) {
            HWND hwndIcon;
            hwndIcon = GetDlgItem(hwnd, IDD_ICON);
            SetWindowLong(hwndIcon, GWL_WNDPROC,
                           (LONG)IconProc);
         }
         RegisterHelpEntry(HI_MESSAGEBOXEX, hwnd, (WORD) ErrHelpEntry);
         GetWindowRect(hwnd, &rc);
         SetWindowPos(hwnd, NULL,
               (GetSystemMetrics(SM_CXSCREEN) - (rc.right - rc.left)) / 2,
               (GetSystemMetrics(SM_CYSCREEN) - (rc.bottom - rc.top)) / 3,
               0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
         return(TRUE);
      }
      case WM_COMMAND:
         if(wParam != IDEXHELP)
            EndDialog(hwnd, wParam);
         break;
      default:
         return(FALSE);
   }
   return(FALSE);
}


/*****************************************************************************
** IconProc
**
** Description:
**   Draw the icon, if any, in the dialog box.
**
** Parameters:
**   hwnd (in): the handle of the icon
**   message (in): the normal windows message type
**   wParam (in): the normal windows wParam
**   lParam (in): the normal windows lParam
**      
*****************************************************************************/
LONG FAR PASCAL _export IconProc(HWND hwnd, unsigned message,
   WORD wParam, LONG lParam)
{
   if(message == WM_PAINT) {
      PAINTSTRUCT ps;
      HDC hdc;

      hdc = BeginPaint(hwnd, &ps);
      DrawIcon(hdc, 0, 0, hIcon);
      EndPaint(hwnd, &ps);
      /* 
       * This return is here only to avoid compilation warnings
       * that wParam and lParam are unused.
       */
      return (message != WM_PAINT && wParam && lParam);
   }
   return(FALSE);
}

/*****************************************************************************
** AddDlgItem
**
** Description:
**   Add an item (button, icon, etc) to the dialog box template
**
** Parameters:
**   hDlgTmp (in): the handle of the dialog box
**   cbTemplate (in): lower bound on the number of bytes of memory needed
**   dtilX (in): the upper x coordinate in dialog coordinates 
**   dtilY (in): the left y coordinate in dialog coordinates 
**   dtilCX (in): the lower x coordinate in dialog coordinates 
**   dtilCY (in): the right y coordinate in dialog coordinates
**   dtilID (in): the ID (IDOK, IDEXHELP) of the button (or whatever)
**   dtilStyle (in): the style of the dialog item
**   lpdtilClass (in): the class of the dialog item
**   lpdtilText (in): the text of the dialog item
**      
*****************************************************************************/
PRIVATE int AddDlgItem(HANDLE hDlgTmp, int cbTemplate,
   int dtilX, int dtilY, int dtilCX, int dtilCY,
   int dtilID, long dtilStyle, LPSTR lpdtilClass,
   LPSTR lpdtilText)
{
   DLGTEMPLATE FAR * lpDlgTmp;
   DLGITEMTEMPLATE FAR * lpDlgItemTmp;
   int cbItem;
   LPSTR lpstr;

   cbItem = sizeof(DLGITEMTEMPLATE) + lstrlen(lpdtilClass) +
            lstrlen(lpdtilText);
   hDlgTmp = GlobalReAlloc(hDlgTmp, cbTemplate +
            cbItem, GMEM_MOVEABLE);
   lpDlgTmp = (DLGTEMPLATE FAR *)GlobalLock(hDlgTmp);
   ++lpDlgTmp->dtItemCount;
   dtilY = (dtilY * 8) / HIWORD(GetDialogBaseUnits());
   dtilX = (dtilX * 4) / LOWORD(GetDialogBaseUnits());
   dtilCY = (dtilCY * 8) / HIWORD(GetDialogBaseUnits());
   dtilCX = (dtilCX * 4) / LOWORD(GetDialogBaseUnits());
   lpDlgItemTmp = (DLGITEMTEMPLATE FAR *)((LPSTR)lpDlgTmp + cbTemplate);
   lpDlgItemTmp->dtilX = dtilX;
   lpDlgItemTmp->dtilY = dtilY;
   lpDlgItemTmp->dtilCX = dtilCX;
   lpDlgItemTmp->dtilCY = dtilCY;
   lpDlgItemTmp->dtilID = dtilID;
   lpDlgItemTmp->dtilStyle = dtilStyle | WS_VISIBLE;

   /* Copy in the class name. */
   lpstr = (LPSTR)&lpDlgItemTmp->dtilClass;
   lstrcpy(lpstr, lpdtilClass);

   /* Copy in the item text */
   lpstr += lstrlen(lpdtilClass) + 1;
   lstrcpy(lpstr, lpdtilText);

   lpstr += lstrlen(lpdtilText) + 1;
   *lpstr = 0;
   GlobalUnlock(hDlgTmp);
   return(cbItem);
}

/************************************EOF************************************/
