/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/**************************************************************************
 *
 * SOURCE FILE NAME = FORMS.C
 *
 * DESCRIPTIVE NAME = Plotter driver source
 * DESCRIPTION = File Contains functions dealing with user defined forms
 *
 *
 * VERSION = V2.0
 *
 * DATE        11/30/93
 *
 *
 * FUNCTIONS
 *
 *     ---GLOBAL---
 *     user_defined_forms_dialog_proc
 *     display_user_form_paper_size
 *     add_user_forms_to_list
 *     delete_user_forms_from_list
 *     find_user_form_size
 *     number_user_forms_defined
 *     return_flat_user_forms_list
 *     user_form_exists_exists
 *
 *     ---PRIVATE---
 *     initialize_user_forms
 *     add_user_form
 *     delete_user_form
 *     modify_user_form
 *     save_user_form
 *     convert_list
 *     string_to_measurement
 *     measurement_to_string
 *     llCreateElement
 *     llAddElement
 *     llDeleteElement
 *     llFindElement
 *     llFreeAllElements
 *     id_conflicts
 *     name_conflicts
 *     display_form_id
 *     display_error
 *
 * NOTES
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
*/

#define INCL_DOS
#define INCL_WIN
#define INCL_GPI
#include <os2.h>


#define USERINTERFACE                   /* incl gpi stuff not ddi stuff     */
#include "plotters.h"
#include "dialog.h"
#include "profile.h"
#include "devmode.h"
#include "utils.h"
#include "forms.h"

/***********************************************************************/
/* Internal function prototypes ...                                    */
/***********************************************************************/
BOOL          initialize_user_forms          (PDEVMODE       pDevMode,
                                              HWND           hwnd,
                                              PUSHORT        pusNumForms,
                                              PPUSERFORMLIST ppuflHead);
VOID          add_user_form                  (PDEVMODE       pDevMode,
                                              HWND           hwnd,
                                              PUSHORT        pusNumForms,
                                              PPUSERFORMLIST ppuflHead,
                                              BOOL           bUseInches);
VOID          delete_user_form               (PDEVMODE       pDevMode,
                                              HWND           hwnd,
                                              PUSHORT        pusNumForms,
                                              PPUSERFORMLIST ppuflHead);
VOID          modify_user_form               (PDEVMODE pDevMode,
                                              HWND hwnd,
                                              PPUSERFORMLIST ppuflHead,
                                              BOOL bUseInches);
VOID          save_user_form                 (PDEVMODE pDevMode,
                                              USHORT usNumForms,
                                              PPUSERFORMLIST ppuflHead);
VOID          convert_list                   (PPUSERFORMLIST ppuflHead,
                                              BOOL           bUseInches);
ULONG         string_to_measurement          (PSZ            achStr,
                                              BOOL           bUseInches);
VOID          measurement_to_string          (PSZ            achStr,
                                              ULONG          ulValue,
                                              BOOL           ulInches);
PUSERFORMLIST llCreateElement                (PDEVMODE       pDevMode);
PUSERFORMLIST llAddElement                   (PDEVMODE       pDevMode,
                                              PPUSERFORMLIST ppuflHead,
                                              PSZ            pszFormName,
                                              ULONG          ulHeight,
                                              ULONG          ulWidth,
                                              USHORT         usFormID);
BOOL          llDeleteElement                (PDEVMODE       pDevMode,
                                              PPUSERFORMLIST ppuflHead,
                                              PUSERFORMLIST  pElm);
PUSERFORMLIST llFindElement                  (PPUSERFORMLIST ppuflHead,
                                              PSZ            pszFormName);
VOID          llFreeAllElements              (PDEVMODE       pDevMode,
                                              PUSERFORMLIST  puflHead);
PSZ           id_conflicts                   (PUSERFORMLIST  puflHead,
                                              PUSERFORMLIST  pElm,
                                              USHORT         usFormID);
PSZ           name_conflicts                 (PUSERFORMLIST  puflHead,
                                              PUSERFORMLIST  pElm,
                                              PSZ            pszName);
VOID          display_form_id                (HWND           hwnd,
                                              PUSERFORMLIST  pElm);
VOID          display_error                  (HWND           hwnd,
                                              SHORT          sErrorMessage,
                                              PSZ            pszErrorName);

/***********************************************************************/
/* Global variables ...                                                */
/***********************************************************************/
PFNWP         pfnwpIDEntrySubProc;
PFNWP         pfnwpHeightEntrySubProc;
PFNWP         pfnwpWidthEntrySubProc;

/***********************************************************************/
/* Global defines                                                      */
/***********************************************************************/
/* This function is not in the multi-threaded library (but it is in the
** single-threaded version).  So I created a define for it.
*/
#define isdigit(c) ('0' <= c && c <= '9')

/***************************************************************************
 *
 * FUNCTION NAME = IDNumericSubProc
 *
 * DESCRIPTION   = This is a subclassing procedure for an entry field.
 *                 Its purpose is to only allow numeric input.
 *
 * INPUT         = HWND   - Window handle
 *                 USHORT - Message id
 *                 MPARAM - Parameter
 *                 MPARAM - Parameter
 *
 * OUTPUT        = MPARAM - Result
 *
 **************************************************************************/
MPARAM
IDNumericSubProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
{
   PCHRMSG p;

   /* if this is a char msg */
   if (WM_CHAR == msg)
   {
      if (SHORT1FROMMP (mp1) &  KC_VIRTUALKEY)
      {
         switch (SHORT2FROMMP (mp2))
         {
         case VK_ESC:
         case VK_TAB:
         case VK_BACKTAB:
         case VK_INSERT:
         case VK_DELETE:
         case VK_BACKSPACE:
         case VK_NEWLINE:
         case VK_ENTER:
         case VK_END:
         case VK_HOME:
         case VK_UP:
         case VK_DOWN:
         case VK_LEFT:
         case VK_RIGHT:
         case VK_F1:
            // Allow these keys through to the entry field
            // call previous entry field proc
            return ((*pfnwpIDEntrySubProc)(hwnd, msg, mp1, mp2));
         }
      }

      p = CHARMSG (&msg);                          /* addres char structure */
      if ((p->fs & (KC_KEYUP|KC_CHAR))==KC_CHAR)   /* ONLY key down transit */
      {
         if (!isdigit(p->chr))                     /* if not numeric ONLY */
         {                                         /* make sure to ignore */
                                                   /* control keys */
            WinAlarm (HWND_DESKTOP,WA_WARNING);    /* beep */
            return (MRESULT)(TRUE);                /* continue */
         }
      }
   }

   /* call previous entry field proc */
   return ((*pfnwpIDEntrySubProc)(hwnd, msg, mp1, mp2));
}

/***************************************************************************
 *
 * FUNCTION NAME = HeightNumericSubProc
 * DESCRIPTION   = This is a subclassing procedure for an entry field.
 *                 Its purpose is to only allow numeric input.
 *
 * INPUT         = HWND   - Window handle
 *                 USHORT - Message id
 *                 MPARAM - Parameter
 *                 MPARAM - Parameter
 *
 * OUTPUT        = MPARAM - Result
 *
 **************************************************************************/
MPARAM
HeightNumericSubProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
{
   PCHRMSG p;

   /* if this is a char msg */
   if (WM_CHAR == msg)
   {
      if (SHORT1FROMMP (mp1) &  KC_VIRTUALKEY)
      {
         switch (SHORT2FROMMP (mp2))
         {
         case VK_ESC:
         case VK_TAB:
         case VK_BACKTAB:
         case VK_INSERT:
         case VK_DELETE:
         case VK_BACKSPACE:
         case VK_NEWLINE:
         case VK_ENTER:
         case VK_END:
         case VK_HOME:
         case VK_UP:
         case VK_DOWN:
         case VK_LEFT:
         case VK_RIGHT:
         case VK_F1:
            // Allow these keys through to the entry field
            // call previous entry field proc
            return ((*pfnwpHeightEntrySubProc)(hwnd, msg, mp1, mp2));
         }
      }

      p = CHARMSG (&msg);                          /* addres char structure */
      if ((p->fs & (KC_KEYUP|KC_CHAR))==KC_CHAR)   /* ONLY key down transit */
      {
         if (!(isdigit(p->chr) || '.' == p->chr))  /* if not numeric ONLY */
         {                                         /* make sure to ignore */
                                                   /* control keys */
            WinAlarm (HWND_DESKTOP,WA_WARNING);    /* beep */
            return (MRESULT)(TRUE);                /* continue */
         }
      }
   }

   /* call previous entry field proc */
   return ((*pfnwpHeightEntrySubProc)(hwnd, msg, mp1, mp2));
}

/***************************************************************************
 *
 * FUNCTION NAME = WidthNumericSubProc
 * DESCRIPTION   = This is a subclassing procedure for an entry field.
 *                 Its purpose is to only allow numeric input.
 *
 * INPUT         = HWND   - Window handle
 *                 USHORT - Message id
 *                 MPARAM - Parameter
 *                 MPARAM - Parameter
 *
 * OUTPUT        = MPARAM - Result
 *
 **************************************************************************/
MPARAM
WidthNumericSubProc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
{
   PCHRMSG p;

   /* if this is a char msg */
   if (WM_CHAR == msg)
   {
      if (SHORT1FROMMP (mp1) &  KC_VIRTUALKEY)
      {
         switch (SHORT2FROMMP (mp2))
         {
         case VK_ESC:
         case VK_TAB:
         case VK_BACKTAB:
         case VK_INSERT:
         case VK_DELETE:
         case VK_BACKSPACE:
         case VK_NEWLINE:
         case VK_ENTER:
         case VK_END:
         case VK_HOME:
         case VK_UP:
         case VK_DOWN:
         case VK_LEFT:
         case VK_RIGHT:
         case VK_F1:
            // Allow these keys through to the entry field
            // call previous entry field proc
            return ((*pfnwpWidthEntrySubProc)(hwnd, msg, mp1, mp2));
         }
      }

      p = CHARMSG (&msg);                          /* addres char structure */
      if ((p->fs & (KC_KEYUP|KC_CHAR))==KC_CHAR)   /* ONLY key down transit */
      {
         if (!(isdigit(p->chr) || '.' == p->chr))  /* if not numeric ONLY */
         {                                         /* make sure to ignore */
                                                   /* control keys */
            WinAlarm (HWND_DESKTOP,WA_WARNING);    /* beep */
            return (MRESULT)(TRUE);                /* continue */
         }
      }
   }

   /* call previous entry field proc */
   return ((*pfnwpWidthEntrySubProc)(hwnd, msg, mp1, mp2));
}

/***************************************************************************
 *
 * FUNCTION NAME = user_defined_forms_dialog_proc
 *
 * DESCRIPTION   = This is the dialog procedure for the user defined forms
 *
 * INPUT         = HWND   - Window handle
 *                 USHORT - Message id
 *                 MPARAM - Parameter
 *                 MPARAM - Parameter
 *
 * OUTPUT        = MPARAM - Result
 *
 **************************************************************************/
MPARAM
user_defined_forms_dialog_proc (HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
{
   static PDEVMODE      pDevMode      = NULL;
   static PUSERFORMLIST puflHead      = NULL;
   static BOOL          bUseInches    = FALSE;
   static BOOL          bShouldFilter = FALSE;
   static USHORT        usNumForms    = 0;
   BOOL                 bChange       = FALSE;

   switch (msg)
   {
   case WM_INITDLG:
      {
         HWND  hwndID;
         HWND  hwndHeight;
         HWND  hwndWidth;

         pDevMode = (PDEVMODE)mp2;

         // Subclass the entryfields
         hwndID     = WinWindowFromID (hwnd, USER_FORM_ID);
         hwndHeight = WinWindowFromID (hwnd, USER_FORM_HEIGHT);
         hwndWidth  = WinWindowFromID (hwnd, USER_FORM_WIDTH);
         pfnwpIDEntrySubProc     = (PFNWP)WinSubClassWindow (hwndID,
                                                             (PFNWP)IDNumericSubProc);
         pfnwpHeightEntrySubProc = (PFNWP)WinSubClassWindow (hwndHeight,
                                                             (PFNWP)HeightNumericSubProc);
         pfnwpWidthEntrySubProc  = (PFNWP)WinSubClassWindow (hwndWidth,
                                                             (PFNWP)WidthNumericSubProc);

         // Set the length of the entry fields...
         WinSendMsg (WinWindowFromID (hwnd, USER_FORM_NAME),
                     EM_SETTEXTLIMIT,
                     (MPARAM)(NAME_LENGTH-1),
                     (MPARAM)0);
         WinSendMsg (WinWindowFromID (hwnd, USER_FORM_WIDTH),
                     EM_SETTEXTLIMIT,
                     (MPARAM)(NAME_LENGTH-1),
                     (MPARAM)0);
         WinSendMsg (WinWindowFromID (hwnd, USER_FORM_HEIGHT),
                     EM_SETTEXTLIMIT,
                     (MPARAM)(NAME_LENGTH-1),
                     (MPARAM)0);
         WinSendMsg (WinWindowFromID (hwnd, USER_FORM_ID),
                     EM_SETTEXTLIMIT,
                     (MPARAM)(NAME_LENGTH-1),
                     (MPARAM)0);

         // read in the user defined forms in the ini file
         bUseInches = initialize_user_forms (pDevMode,
                                             hwnd,
                                             &usNumForms,
                                             (PPUSERFORMLIST)&pDevMode->puflHead);

         // Select the first item in the list box
         WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                     LM_SELECTITEM,
                     MPFROMSHORT (0),
                     (MPARAM)TRUE);
         bChange = TRUE;
         break;
      }

   case WM_COMMAND:
      switch (LOUSHORT (mp1))
      {
      case ADD_USER_FORM:
         add_user_form (pDevMode,
                        hwnd,
                        &usNumForms,
                        (PPUSERFORMLIST)&pDevMode->puflHead,
                        bUseInches);
         bChange = TRUE;
         break;

      case DELETE_USER_FORM:
         delete_user_form (pDevMode,
                           hwnd,
                           &usNumForms,
                           (PPUSERFORMLIST)&pDevMode->puflHead);
         // Simulate a button press on the "clear" button...
         WinSendMsg (hwnd,
                     WM_COMMAND,
                     MPFROM2SHORT (USER_CLEAR_FIELDS, 0),
                     MPFROM2SHORT (CMDSRC_PUSHBUTTON, 0));

         bChange = TRUE;
         break;

      case MODIFY_USER_FORM:
         modify_user_form (pDevMode,
                           hwnd,
                           (PPUSERFORMLIST)&pDevMode->puflHead,
                           bUseInches);
         break;

      case USER_CLEAR_FIELDS:
         // Deselect any selected item in the list box;
         WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                     LM_SELECTITEM,
                     MPFROMSHORT (LIT_NONE),
                     (MPARAM)0);
         // ... and clear out the entry fields
         WinSetWindowText (WinWindowFromID (hwnd, USER_FORM_NAME),   "");
         WinSetWindowText (WinWindowFromID (hwnd, USER_FORM_HEIGHT), "");
         WinSetWindowText (WinWindowFromID (hwnd, USER_FORM_WIDTH),  "");
         WinSetWindowText (WinWindowFromID (hwnd, USER_FORM_ID),     "");
         break;

      case DID_OK:
         save_user_form (pDevMode, usNumForms, (PPUSERFORMLIST)&pDevMode->puflHead);
         if (bUseInches)
            PrfWriteProfileString (HINI_SYSTEMPROFILE,
                                   pDevMode->achAppName,
                                   UNITS_KEYNAME,
                                   "1");
         else
            PrfWriteProfileString (HINI_SYSTEMPROFILE,
                                   pDevMode->achAppName,
                                   UNITS_KEYNAME,
                                   "0");
         WinDismissDlg (hwnd, TRUE);
         break;

      case DID_CANCEL:
         llFreeAllElements (pDevMode, (PUSERFORMLIST)pDevMode->puflHead);
         WinDismissDlg (hwnd, FALSE);
         break;
      }
      break;

   case WM_CONTROL:
      if (USER_FORM_LIST == SHORT1FROMMP (mp1) &&
          LN_SELECT      == SHORT2FROMMP (mp1)  )
      {
         CHAR          achTemp[NAME_LENGTH];
         SHORT         sIndex;
         PUSERFORMLIST pElm = NULL;

         sIndex = (SHORT)WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                                     LM_QUERYSELECTION,
                                     MPFROMSHORT (LIT_FIRST),
                                     (MPARAM)0);
         if (LIT_NONE == sIndex)
            break;

         pElm = WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                             LM_QUERYITEMHANDLE,
                             (MPARAM)sIndex,
                             (MPARAM)0);

         WinSetWindowText (WinWindowFromID (hwnd, USER_FORM_NAME),
                           pElm->ufi.achFormName);
         WinSetWindowText (WinWindowFromID (hwnd, USER_FORM_HEIGHT),
                           pElm->achHeight);
         WinSetWindowText (WinWindowFromID (hwnd, USER_FORM_WIDTH),
                           pElm->achWidth);
         display_form_id (WinWindowFromID (hwnd, USER_FORM_ID), pElm);
      }

      if (USER_INCHES == SHORT1FROMMP (mp1) &&
          BN_CLICKED  == SHORT2FROMMP (mp1))
      {
         if (FALSE == bUseInches)
         {
            // has changed...
            bUseInches = TRUE;
            convert_list ((PPUSERFORMLIST)&pDevMode->puflHead, bUseInches);
            bChange = TRUE;
         }
      }

      if (USER_CENTIMETERS == SHORT1FROMMP (mp1) &&
          BN_CLICKED       == SHORT2FROMMP (mp1))
      {
         if (TRUE == bUseInches)
         {
            // has changed...
            bUseInches = FALSE;
            convert_list ((PPUSERFORMLIST)&pDevMode->puflHead, bUseInches);
            bChange = TRUE;
         }
      }
      // We are going to use bChange for a different job here but this is
      // only for this section
      if (bChange)
      {
         SHORT         sIndex;

         bChange = FALSE;

         // Find out what list box item has been selected and reselect it
         sIndex = (SHORT)WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                                     LM_QUERYSELECTION,
                                     MPFROMSHORT (LIT_FIRST),
                                     (MPARAM)0);
         WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                     LM_SELECTITEM,
                     (MPARAM)LIT_NONE,
                     (MPARAM)FALSE);
         WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                     LM_SELECTITEM,
                     (MPARAM)sIndex,
                     (MPARAM)TRUE);
      }

      if ((EN_SETFOCUS == SHORT2FROMMP (mp1))       &&
          (USER_FORM_WIDTH  == SHORT1FROMMP (mp1) ||
           USER_FORM_HEIGHT == SHORT1FROMMP (mp1)  ) )
         bShouldFilter = TRUE;

      if ((EN_KILLFOCUS == SHORT2FROMMP (mp1))      &&
          (USER_FORM_WIDTH  == SHORT1FROMMP (mp1) ||
           USER_FORM_HEIGHT == SHORT1FROMMP (mp1)  ) )
         bShouldFilter = FALSE;
      break;

   default:
      return WinDefDlgProc (hwnd, msg, mp1, mp2);
   }

   if (bChange)
   {
      BOOL fEnable;

      fEnable = (0 == usNumForms) ? FALSE : TRUE;
      WinEnableWindow (WinWindowFromID (hwnd, DELETE_USER_FORM), fEnable);
      WinEnableWindow (WinWindowFromID (hwnd, MODIFY_USER_FORM), fEnable);
   }

   return FALSE;  // default return code...
}

/***************************************************************************
 *
 * FUNCTION NAME = number_user_forms_defined
 *
 * DESCRIPTION   = This searches the OS2SYS.INI for the AppName entry
 *                 and returns the number of forms that have been defined.
 *
 * INPUT         = PSZ  pszAppName - Key name
 *                 HWND hwnd       - Window handle
 *
 * OUTPUT        = USHORT - The number of forms that have been defined
 *
 **************************************************************************/
USHORT
number_user_forms_defined (PSZ pszAppName, HWND hwnd)
{
   ULONG   ulSize;
   BOOL    bRc;

   bRc = PrfQueryProfileSize (HINI_SYSTEMPROFILE,
                              pszAppName,
                              FORMS_KEYNAME,
                              &ulSize);
   if (bRc)
   {
      if (0 != ulSize % sizeof (USERFORMINFO))
      {
         display_error (hwnd, IDS_CoruptIniData, (PSZ)NULL);
         return 0;
      }

      return ulSize/sizeof (USERFORMINFO);
   }
   else
      // Oops... an error... but it can be a good one, PMERR_NOT_IN_IDX
      return 0;
}

/***************************************************************************
 *
 * FUNCTION NAME = initialize_user_forms
 *
 * DESCRIPTION   = This function reads in the user defined forms into a linked
 *                 list.  It will return the current unit of measurement
 *                 and the number of forms that have been defined.
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *                 HWND           hwnd        - Window handle
 *                 PUSHORT        pusNumForms - OUTPUT
 *                 PPUSERFORMLIST ppuflHead   - Linked list handle
 *
 * OUTPUT        = BOOL - TRUE if inches are the unit, FALSE for centimeters
 *                 PUSHORT        pusNumForms - The number of forms found
 *
 **************************************************************************/
BOOL
initialize_user_forms (PDEVMODE pDevMode,
                       HWND hwnd,
                       PUSHORT pusNumForms,
                       PPUSERFORMLIST ppuflHead)
{
   SHORT         sUnits;
   ULONG         ulInchChecked, ulCMChecked;
   ULONG         ulSize;
   BOOL          bRc;
   SHORT         sIndex;
   PUSERFORMLIST pElm = NULL;
   CHAR          achListName[256];

   /***********************************************************************/
   /* Set up which units to use ...                                       */
   /***********************************************************************/
   sUnits = PrfQueryProfileInt (HINI_SYSTEMPROFILE,
                                pDevMode->achAppName,
                                UNITS_KEYNAME,
                                -1);
   if (-1 == sUnits)
   {
      // Prefered units to use was not found... make one!
      PrfWriteProfileString (HINI_SYSTEMPROFILE,
                             pDevMode->achAppName,
                             UNITS_KEYNAME,
                             "1");
      sUnits = 1;
   }

   if (1 == sUnits)
   {
      ulInchChecked = 1;
      ulCMChecked   = 0;
   }
   else
   {
      ulInchChecked = 0;
      ulCMChecked   = 1;
   }

   // Set up the unit group radio buttons
   WinSendDlgItemMsg (hwnd, USER_INCHES,
                      BM_SETCHECK, (MPARAM)ulInchChecked, (MPARAM)FALSE);
   WinSendDlgItemMsg (hwnd, USER_CENTIMETERS,
                      BM_SETCHECK, (MPARAM)ulCMChecked,   (MPARAM)FALSE);

   // Initialize the head of the linked list
   pDevMode->puflHead = (PVOID)NULL;

   /***********************************************************************/
   /* See how many user forms are defined...                              */
   /***********************************************************************/
   *pusNumForms = number_user_forms_defined (pDevMode->achAppName, hwnd);
   if (*pusNumForms)
   {
      register INT  i;
      ULONG         cbData;
      PUSERFORMINFO pufi;
      PBYTE         pbPtr;

      pbPtr = return_flat_user_forms_list (pDevMode->hmcbHeap, pDevMode->achAppName);
      if ((PBYTE)NULL == pbPtr)
         return (BOOL)ulInchChecked;

      pufi = (PUSERFORMINFO)pbPtr;

      for (i = 1; i <= *pusNumForms; i++)
      {
         // Add the item to the linked list
         pElm = llAddElement (pDevMode,
                               ppuflHead,
                               pufi->achFormName,
                               pufi->ulHeight,
                               pufi->ulWidth,
                               pufi->usFormID);

         measurement_to_string (pElm->achHeight, pufi->ulHeight, ulInchChecked);
         measurement_to_string (pElm->achWidth,  pufi->ulWidth,  ulInchChecked);

         // And finally add it to the list box
         sprintf (achListName,
                  "%d - %s",
                  (int)pufi->usFormID,
                  pufi->achFormName);
         sIndex = (SHORT)WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                                     LM_INSERTITEM,
                                     MPFROMSHORT (LIT_END),
                                     MPFROMP (achListName));

         // And set its handle
         WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                     LM_SETITEMHANDLE,
                     (MPARAM)sIndex,
                     (MPARAM)pElm);

         pufi++;
      }

      // Clean up
      GplMemoryFree ((PVOID)pbPtr);
   }

   return (BOOL)ulInchChecked;
}

/***************************************************************************
 *
 * FUNCTION NAME = display_user_form_paper_size
 *
 * DESCRIPTION   = This function takes an element in a linked list and
 *                 updates the dialog with the correct information.
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *                 HWND           hwnd        - Window handle
 *                 PUSERFORMINFO  pufi        - Element in a linked list
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
display_user_form_paper_size (PDEVMODE pDevMode,
                              HWND hwnd,
                              PUSERFORMINFO pufi)
{
   CHAR   achText[255];
   CHAR   achHeight[255];
   CHAR   achWidth[255];
   SHORT  sUnits;

   // Hide items pertaining to roll feed
   WinShowWindow (WinWindowFromID (hwnd, ROLL_SCROLL), FALSE);
   WinShowWindow (WinWindowFromID (hwnd, ROLL_TEXT1), FALSE);
   WinShowWindow (WinWindowFromID (hwnd, ROLL_TEXT2), FALSE);
   WinShowWindow (WinWindowFromID (hwnd, ROLL_LENGTH), FALSE);

   // Show the text field
   WinShowWindow (WinWindowFromID (hwnd, MEDIA_SIZE), TRUE);

   // Find out the current units
   sUnits = PrfQueryProfileInt (HINI_SYSTEMPROFILE,
                                pDevMode->achAppName,
                                UNITS_KEYNAME,
                                -1);
   if (-1 == sUnits)
      return;

   measurement_to_string (achHeight, pufi->ulHeight, sUnits);
   measurement_to_string (achWidth,  pufi->ulWidth,  sUnits);

   sprintf (achText, "#%d %s (%s x %s %s)",
            (int)pufi->usFormID,
            pufi->achFormName, achWidth, achHeight,
            (1 == sUnits) ? "in." : "cm.");

   WinSetDlgItemText (hwnd, MEDIA_SIZE, achText);

   // Save the current form
   pDevMode->pSetup->usFlags |= FL_USERDEFINEDFORMS;
   pDevMode->pSetup->Size = pufi->usFormID;
}

/***************************************************************************
 *
 * FUNCTION NAME = add_user_form
 *
 * DESCRIPTION   = This function querys the dialog and if the information
 *                 is valid, it will then insert that information into the
 *                 linked list.
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *                 HWND           hwnd        - Window handle
 *                 PUSHORT        pusNumForms - The number of user defined forms
 *                 PPUSERFORMLIST ppuflHead   - Linked list handle
 *                 BOOL           bUseInches  - TRUE inches FALSE centimeters
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
add_user_form (PDEVMODE pDevMode,
               HWND hwnd,
               PUSHORT pusNumForms,
               PPUSERFORMLIST ppuflHead,
               BOOL bUseInches)
{
   HWND          hwndFormName;
   HWND          hwndWidth;
   HWND          hwndHeight;
   HWND          hwndFormID;
   CHAR          achFormName[NAME_LENGTH];
   CHAR          achWidth[NAME_LENGTH];
   CHAR          achHeight[NAME_LENGTH];
   CHAR          achFormID[NAME_LENGTH];
   USHORT        usFormID;
   PUSERFORMLIST pElm = NULL;
   SHORT         sIndex;

   // Find the window handles
   hwndFormName = WinWindowFromID (hwnd, USER_FORM_NAME);
   hwndWidth    = WinWindowFromID (hwnd, USER_FORM_WIDTH);
   hwndHeight   = WinWindowFromID (hwnd, USER_FORM_HEIGHT);
   hwndFormID   = WinWindowFromID (hwnd, USER_FORM_ID);

   // First, check to see if all of the fields have been filled in...
   if (0 == WinQueryWindowTextLength (hwndFormName) ||
       0 == WinQueryWindowTextLength (hwndWidth)    ||
       0 == WinQueryWindowTextLength (hwndHeight)   ||
       0 == WinQueryWindowTextLength (hwndFormID)    )
   {
      // ...An error
      display_error (hwnd, IDS_FillInAllFields, (PSZ)NULL);
   }
   else
   {
      PSZ   pszName;

      // Find out what is in them
      WinQueryWindowText (hwndFormName, sizeof (achFormName), achFormName);
      WinQueryWindowText (hwndHeight,   sizeof (achHeight),   achHeight);
      WinQueryWindowText (hwndWidth,    sizeof (achWidth),    achWidth);
      WinQueryWindowText (hwndFormID,   sizeof (achFormID),   achFormID);

      usFormID = (USHORT)atoi (achFormID);
      pszName = id_conflicts (*ppuflHead, (PUSERFORMLIST)NULL, usFormID);
      if (pszName)
      {
         display_error (hwnd, IDS_FormConflictWithID, pszName);
         return;
      }

      pszName = name_conflicts (*ppuflHead, (PUSERFORMLIST)NULL, (PSZ)achFormName);
      if (pszName)
      {
         display_error (hwnd, IDS_FormConflictWithName, pszName);
         return;
      }

      pElm = llAddElement (pDevMode,
                            ppuflHead,
                            achFormName,
                            string_to_measurement (achHeight, bUseInches),
                            string_to_measurement (achWidth,  bUseInches),
                            usFormID);

      // Change the string to a formalized notation
      measurement_to_string (pElm->achHeight, pElm->ufi.ulHeight, bUseInches);
      measurement_to_string (pElm->achWidth,  pElm->ufi.ulWidth,  bUseInches);

      // Increment the number of user forms that are defined...
      (*pusNumForms)++;

      // Change to entry fields to show new formalized notation
      WinSetWindowText (WinWindowFromID (hwnd, USER_FORM_HEIGHT),
                        pElm->achHeight);
      WinSetWindowText (WinWindowFromID (hwnd, USER_FORM_WIDTH),
                        pElm->achWidth);

      // And finally add it to the list box
      sIndex = (SHORT)WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                                  LM_INSERTITEM,
                                  MPFROMSHORT (LIT_END),
                                  MPFROMP (achFormName));

      // And set its handle
      WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                  LM_SETITEMHANDLE,
                  (MPARAM)sIndex,
                  (MPARAM)pElm);
   }
}

/***************************************************************************
 *
 * FUNCTION NAME = delete_user_form
 *
 * DESCRIPTION   = This function querys the dialog for the current form.  It
 *                 will then search the linked list for that form.  If it finds
 *                 that form, it will delete it from the linked list.
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *                 HWND           hwnd        - Window handle
 *                 PUSHORT        pusNumForms - The number of user defined forms
 *                 PPUSERFORMLIST ppuflHead   - Linked list handle
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
delete_user_form (PDEVMODE pDevMode,
                  HWND hwnd,
                  PUSHORT pusNumForms,
                  PPUSERFORMLIST ppuflHead)
{
   CHAR          achFormName[NAME_LENGTH];
   SHORT         sIndex;
   PUSERFORMLIST pElm = NULL;

   sIndex = (SHORT)WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                               LM_QUERYSELECTION,
                               MPFROMSHORT (LIT_FIRST),
                               (MPARAM)0);

   if (LIT_NONE == sIndex)
   {
      display_error (hwnd, IDS_NoFormSelected, (PSZ)NULL);
   }
   else
   {
      WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                  LM_QUERYITEMTEXT,
                  MPFROM2SHORT (sIndex, NAME_LENGTH),
                  MPFROMP (achFormName));

      pElm = llFindElement (ppuflHead, (PSZ)achFormName);
      llDeleteElement (pDevMode, ppuflHead, pElm);
      if (*pusNumForms)
         (*pusNumForms)--;

      WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                  LM_DELETEITEM,
                  (MPARAM)sIndex,
                  (MPARAM)0);
   }
}

/***************************************************************************
 *
 * FUNCTION NAME = modify_user_form
 *
 * DESCRIPTION   = This function will query the dialog for the information
 *                 that the user has entered.  It will then validate that
 *                 information.  Next, it will search the linked list for
 *                 that form.  Finally, it will change the data that was
 *                 stored in the linked list element to the data that the
 *                 user has entered.
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *                 HWND           hwnd        - Window handle
 *                 PUSHORT        pusNumForms - The number of user defined forms
 *                 PPUSERFORMLIST ppuflHead   - Linked list handle
 *                 BOOL           bUseInches  - TRUE inches FALSE centimeters
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
modify_user_form (PDEVMODE pDevMode,
                  HWND hwnd,
                  PPUSERFORMLIST ppuflHead,
                  BOOL bUseInches)
{
   HWND          hwndFormName;
   HWND          hwndWidth;
   HWND          hwndHeight;
   HWND          hwndFormID;
   CHAR          achOldFormName[NAME_LENGTH];
   CHAR          achFormName[NAME_LENGTH];
   CHAR          achWidth[NAME_LENGTH];
   CHAR          achHeight[NAME_LENGTH];
   CHAR          achFormID[NAME_LENGTH];
   USHORT        usFormID;
   PUSERFORMLIST pElm = NULL;
   SHORT         sIndex;

   // First, see if an item is selected in the list box
   sIndex = (SHORT)WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                               LM_QUERYSELECTION,
                               MPFROMSHORT (LIT_FIRST),
                               (MPARAM)0);
   if (LIT_NONE == sIndex)
   {
      display_error (hwnd, IDS_NoFormSelected, (PSZ)NULL);
      return;
   }

   // Find the window handles
   hwndFormName = WinWindowFromID (hwnd, USER_FORM_NAME);
   hwndWidth    = WinWindowFromID (hwnd, USER_FORM_WIDTH);
   hwndHeight   = WinWindowFromID (hwnd, USER_FORM_HEIGHT);
   hwndFormID   = WinWindowFromID (hwnd, USER_FORM_ID);

   // First, check to see if all of the fields have been filled in...
   if (0 == WinQueryWindowTextLength (hwndFormName) ||
       0 == WinQueryWindowTextLength (hwndWidth)    ||
       0 == WinQueryWindowTextLength (hwndHeight)   ||
       0 == WinQueryWindowTextLength (hwndFormID)    )
   {
      // ...An error
      display_error (hwnd, IDS_FillInAllFields, (PSZ)NULL);
   }
   else
   {
      PSZ   pszName;

      // Find the old user form name
      WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                  LM_QUERYITEMTEXT,
                  MPFROM2SHORT (sIndex, NAME_LENGTH),
                  MPFROMP (achOldFormName));

      // Find the new info
      WinQueryWindowText (hwndFormName, sizeof (achFormName), achFormName);
      WinQueryWindowText (hwndHeight,   sizeof (achHeight),   achHeight);
      WinQueryWindowText (hwndWidth,    sizeof (achWidth),    achWidth);
      WinQueryWindowText (hwndFormID,   sizeof (achFormID),   achFormID);

      // Find the element in the linked list
      pElm = llFindElement (ppuflHead, (PSZ)achOldFormName);

      // Check for conflicts
      usFormID = (USHORT)atoi (achFormID);
      pszName = id_conflicts (*ppuflHead, pElm, usFormID);
      if (pszName)
      {
         display_error (hwnd, IDS_FormConflictWithID, pszName);
         return;
      }

      // Check for conflicts
      pszName = name_conflicts (*ppuflHead, pElm,  (PSZ)achFormName);
      if (pszName)
      {
         display_error (hwnd, IDS_FormConflictWithName, pszName);
         return;
      }

      // and modify it!
      strcpy (pElm->ufi.achFormName, achFormName);
      strcpy (pElm->achHeight, achHeight);
      strcpy (pElm->achWidth,  achWidth);
      pElm->ufi.ulHeight = string_to_measurement (achHeight, bUseInches);
      pElm->ufi.ulWidth  = string_to_measurement (achWidth,  bUseInches);
      pElm->ufi.usFormID = usFormID;

      // Set the list box item's new text title
      WinSendMsg (WinWindowFromID (hwnd, USER_FORM_LIST),
                  LM_SETITEMTEXT,
                  MPFROMSHORT (sIndex),
                  MPFROMP (achFormName));

      // Force a repaint
      WinInvalidateRect (WinWindowFromID (hwnd, USER_FORM_LIST),
                         (PRECTL)NULL,
                         TRUE);
   }
}

/***************************************************************************
 *
 * FUNCTION NAME = save_user_form
 *
 * DESCRIPTION   = This function will take a linked list and write out the
 *                 user defined forms into the OS2SYS.INI file.
 *                 If the number of forms is 0, then it will delete the
 *                 user defined form information from the OS2SYS.INI file.
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *                 PUSHORT        pusNumForms - The number of user defined forms
 *                 PPUSERFORMLIST ppuflHead   - Linked list handle
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
save_user_form (PDEVMODE pDevMode,
                USHORT usNumForms,
                PPUSERFORMLIST ppuflHead)
{
   PBYTE         pbPtr, pbCopy;
   PUSERFORMLIST pElm = NULL;
   ULONG         cbData;
   BOOL          bRc;
   SHORT         sIndex;

   // Are there forms?
   if (usNumForms)
   {
      // How much data do they occupy?
      cbData = usNumForms*sizeof (USERFORMINFO);

      // allocate a flat buffer
      pbPtr = (PBYTE)GplMemoryAlloc (pDevMode->hmcbHeap, cbData);
      pbCopy = pbPtr;

      // walk through the linked list
      pElm = *ppuflHead;
      while (pElm)
      {
         // Write this element into the flat buffer
         memcpy (pbCopy, &pElm->ufi, sizeof (USERFORMINFO));
         pbCopy += sizeof (USERFORMINFO);

         // move to the next element
         pElm = pElm->pNext;
      }

      // Write the flat buffer out to the ini file
      bRc = PrfWriteProfileData (HINI_SYSTEMPROFILE,
                                 pDevMode->achAppName,
                                 FORMS_KEYNAME,
                                 (PVOID)pbPtr,
                                 cbData);

      // Clean up
      GplMemoryFree ((PVOID)pbPtr);
   }
   else
   {
      // No forms any more ;-(  Delete any existing forms from the ini file.
      CHAR  c;

      // Delete the ini file information
      cbData = 0;
      bRc = PrfWriteProfileData (HINI_SYSTEMPROFILE,
                                 pDevMode->achAppName,
                                 FORMS_KEYNAME,
                                 (PVOID)&c,
                                 cbData);
   }

   // Clean up the linked list by deleting all the elements.
   llFreeAllElements (pDevMode, *ppuflHead);
}

/***************************************************************************
 *
 * FUNCTION NAME = convert_list
 *
 * DESCRIPTION   = This function walks through the linked list and converts
 *                 the integer measurements into a string form for the
 *                 user to eventually see.
 *
 * INPUT         = PPUSERFORMLIST ppuflHead   - Linked list handle
 *                 BOOL           bUseInches  - TRUE inches FALSE centimeters
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
convert_list (PPUSERFORMLIST ppuflHead,
              BOOL bUseInches)
{
   PUSERFORMLIST pElm = NULL;

   // walk through the linked list
   pElm = *ppuflHead;
   while (pElm)
   {
      // Convert the height to string form
      measurement_to_string (pElm->achHeight,
                             pElm->ufi.ulHeight,
                             bUseInches);

      // Convert the width to string form
      measurement_to_string (pElm->achWidth,
                             pElm->ufi.ulWidth,
                             bUseInches);

      // Goto the next element
      pElm = pElm->pNext;
   }
}

/***************************************************************************
 *
 * FUNCTION NAME = string_to_measurement
 *
 * DESCRIPTION   = This function will convert a string representation of
 *                 a real number into an integer representation.  This
 *                 integer will be in hundreds of a millimeter.
 *
 * INPUT         = PSZ            pszStr      - ASCII string
 *                 BOOL           bUseInches  - TRUE inches FALSE centimeters
 *
 * OUTPUT        = ULONG - resulting hundredths of a millimeter.
 *
 **************************************************************************/
ULONG
string_to_measurement (PSZ pszStr, BOOL bUseInches)
{
   float  f;

   sscanf (pszStr, "%f", &f);
   if (bUseInches)
      // convert to centimeters
      f *= (float)2.540;    // 2.54 cm/1 inch
   f *= (float)1000.0;      // 10 mm/1 cm, 100 hundredths mm/1 mm

   return (ULONG)f;
}

/***************************************************************************
 *
 * FUNCTION NAME = measurement_to_string
 *
 * DESCRIPTION   = This function will take an integer (in hundredths of a
 *                 millimeter) and convert that into a string of a real
 *                 number.
 *
 * INPUT         = PSZ            pszStr      - OUTPUT
 *                 ULONG          ulValue     - integer value
 *                 BOOL           bUseInches  - TRUE inches FALSE centimeters
 *
 * OUTPUT        = VOID - pszStr will now be valid
 *
 **************************************************************************/
VOID
measurement_to_string (PSZ pszStr, ULONG ulValue, BOOL bUseInches)
{
   float  f;

   f = (float)ulValue;
   f /= (float)1000.0;      // 1mm/100 hundredths mm, 1 cm/10 mm
   if (bUseInches)
      // convert to inches
      f /= (float)2.54;     // 1 inch/2.54 cm

   sprintf (pszStr, "%.2f", f);
}

/***************************************************************************
 *
 * FUNCTION NAME = llCreateElement
 *
 * DESCRIPTION   = This function will allocate a linked list element off of
 *                 the heap in the instance data.  It will then initialize
 *                 that element.
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *
 * OUTPUT        = PUSERFORMLIST - linked list element pointer
 *                 NULL          - if error
 *
 **************************************************************************/
PUSERFORMLIST
llCreateElement (PDEVMODE pDevMode)
{
   PUSERFORMLIST pElm = NULL;

   // allocate memory
   pElm = (PUSERFORMLIST)GplMemoryAlloc (pDevMode->hmcbHeap, sizeof (USERFORMLIST));

   if ((PUSERFORMLIST)NULL == pElm)
      // Error!
      return pElm;

   // Initialize the structure...
   pElm->pPrev = NULL;
   pElm->pNext = NULL;

   return pElm;
}

/***************************************************************************
 *
 * FUNCTION NAME = llAddElement
 *
 * DESCRIPTION   = This function will add an element in the linked list that
 *                 contains the given information.  It does this by creating
 *                 an element, copying the information to it, and then adding
 *                 it to the linked list.
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *                 PPUSERFORMLIST ppuflHead   - Linked list handle
 *                 PSZ            pszFormName - The user defined form name
 *                 ULONG          ulHeight    - Its height
 *                 ULONG          ulWidth     - Its width
 *                 USHORT         usFormID    - Its ID
 *
 * OUTPUT        = PUSERFORMLIST  - linked list element pointer
 *                 NULL           - if error
 *
 **************************************************************************/
PUSERFORMLIST
llAddElement (PDEVMODE       pDevMode,
              PPUSERFORMLIST ppuflHead,
              PSZ            pszFormName,
              ULONG          ulHeight,
              ULONG          ulWidth,
              USHORT         usFormID)
{
   USERFORMINFO  ufi;
   PUSERFORMLIST pElm = NULL;

   // Create a linked list element.
   pElm = llCreateElement (pDevMode);
   if ((PUSERFORMLIST)NULL == pElm)
      return NULL;

   // Copy the information
   strcpy (pElm->ufi.achFormName, pszFormName);
   pElm->ufi.ulHeight = ulHeight;
   pElm->ufi.ulWidth  = ulWidth;
   pElm->ufi.usFormID = usFormID;

   // Add it to the linked list.
   if ((PUSERFORMLIST)NULL == *ppuflHead)
   {
      // The linked list is empty.  Place it at the head.
      *ppuflHead = pElm;
   }
   else
   {
      PUSERFORMLIST  puflEnd;

      // Find the end of the list.
      puflEnd = *ppuflHead;
      while (puflEnd->pNext)
         puflEnd = puflEnd->pNext;

      // Add the element to the end of the list.
      puflEnd->pNext = pElm;
      pElm->pPrev   = puflEnd;
   }

   return pElm;
}

/***************************************************************************
 *
 * FUNCTION NAME = llFindElement
 *
 * DESCRIPTION   = This function will search through the linked list for
 *                 an element whose form name matches the form name that was
 *                 given.  If successful, then it will return a pointer to
 *                 that element.
 *
 * INPUT         = PPUSERFORMLIST ppuflHead   - Linked list handle
 *                 PSZ            pszFormName - The user defined form name
 *
 * OUTPUT        = PUSERFORMLIST  - linked list element pointer
 *                 NULL           - if error
 *
 **************************************************************************/
PUSERFORMLIST
llFindElement (PPUSERFORMLIST ppuflHead, PSZ pszFormName)
{
   PUSERFORMLIST pElm = NULL;
   BOOL          bFound = FALSE;

   // Walk through the linked list
   pElm = *ppuflHead;
   while (!bFound && pElm)
   {
      // Does this element's name match the one that was given?
      if (0 == strcmp (pElm->ufi.achFormName, pszFormName))
         // Stop the search
         bFound = TRUE;
      else
         // Move to the next element
         pElm = pElm->pNext;
   }

   return pElm;
}

/***************************************************************************
 *
 * FUNCTION NAME = llDeleteElement
 *
 * DESCRIPTION   = This function takes a linked list and a pointer to one
 *                 of its elements and deletes that element from the linked
 *                 list.  It also cleans up any resources used by that element.
 *                 NOTE:
 *                    It does no validation checking!  Caveat Emptor!
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *                 PPUSERFORMLIST ppuflHead   - Linked list handle
 *                 PUSERFORMLIST  pElm        - Linked list element
 *
 * OUTPUT        = TRUE if successful, FALSE otherwise.
 *
 **************************************************************************/
BOOL
llDeleteElement (PDEVMODE       pDevMode,
                 PPUSERFORMLIST ppuflHead,
                 PUSERFORMLIST  pElm)
{
   // Does the linked list contain any elements?
   if ((PUSERFORMLIST)NULL == *ppuflHead)
   {
      // Nope...
      return FALSE;
   }
   else
   {
      // Find the element
      if (*ppuflHead == pElm)
      {
         // Its at the head of the list
         *ppuflHead   = pElm->pNext;
         pElm->pPrev = NULL;
      }
      else
      {
         // It is somewhere towards the end
         pElm->pPrev->pNext = pElm->pNext;
         if (pElm->pNext)
            pElm->pNext->pPrev = pElm->pPrev;
      }

      // And then free it!
      GplMemoryFree ((PVOID)pElm);
   }

   return TRUE;
}

/***************************************************************************
 *
 * FUNCTION NAME = llFreeAllElements
 *
 * DESCRIPTION   = This function takes a linked list and deletes every element
 *                 in that linked list.
 *
 * INPUT         = PDEVMODE       pDevMode    - instance data
 *                 PPUSERFORMLIST ppuflHead   - Linked list handle
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
llFreeAllElements (PDEVMODE pDevMode, PUSERFORMLIST puflHead)
{
   PUSERFORMLIST pElm = NULL, pTmp;

   // walk through the linked list
   pElm = puflHead;
   while (pElm)
   {
      pTmp  = pElm;
      pElm = pElm->pNext;
      // Free its memory
      GplMemoryFree ((PVOID)pTmp);
   }
}

/***************************************************************************
 *
 * FUNCTION NAME = return_flat_user_forms_list
 *
 * DESCRIPTION   = This function will search the OS2SYS.INI for the application
 *                 name and the user defined forms entry.  If there are no
 *                 user defined forms, then it will return NULL.  Otherwise,
 *                 it will return the contents of that ini file entry.
 *
 * INPUT         = PVOID hmcbHeap     - A heap handle
 *                 PSZ   pszAppName - The application name in OS2SYS.INI
 *
 * OUTPUT        = PBYTE - A pointer to a flat buffer that contains
 *                         what the ini file held.  It should be an array of
 *                         USERFORMINFOs.
 *                 NULL  - No user defined forms or an error occured.
 *
 **************************************************************************/
PBYTE
return_flat_user_forms_list (PVOID hmcbHeap, PSZ pszAppName)
{
   PBYTE         pbPtr;
   ULONG         cbData;
   BOOL          bRc;

   // See if our forms exist
   bRc = PrfQueryProfileSize (HINI_SYSTEMPROFILE,
                              pszAppName,
                              FORMS_KEYNAME,
                              &cbData);
   if (!bRc      || // It does not exist
       0 == cbData) // There is nothing in there
      return NULL;

   // Allocate memory
   pbPtr = (PBYTE)GplMemoryAlloc (hmcbHeap, cbData);
   if ((PBYTE)NULL == pbPtr)
      return pbPtr;

   // Read the data in
   bRc = PrfQueryProfileData (HINI_SYSTEMPROFILE,
                              pszAppName,
                              FORMS_KEYNAME,
                              (PVOID)pbPtr,
                              &cbData);

   return pbPtr;
}

/***************************************************************************
 *
 * FUNCTION NAME = add_user_forms_to_list
 *
 * DESCRIPTION   = This function will see how many user defined forms exist
 *                 for an application name.  It will then insert each user
 *                 defined form name into the given list box.  It will also
 *                 allocate a user defined form element and associate it to
 *                 the newly created list box item.
 *
 * INPUT         = PDEVMODE pDevMode - instance data
 *                 HWND     hwnd     - List box window handle
 *
 * OUTPUT        = SHORT - The list index of the form ID that was in the
 *                         instance data.
 *
 **************************************************************************/
SHORT
add_user_forms_to_list (PDEVMODE pDevMode, HWND hwnd)
{
   INT           i;
   USHORT        usSize;
   PBYTE         pbPtr, pbItem;
   PUSERFORMINFO pufi;
   SHORT         sIndex;
   SHORT         sRetVal = 0;

   // See how many user defined forms exist
   usSize = number_user_forms_defined (pDevMode->achAppName, hwnd);
   if (0 == usSize)
      return;

   // Get their bytes
   pbPtr = return_flat_user_forms_list (pDevMode->hmcbHeap, pDevMode->achAppName);
   if ((PBYTE)NULL == pbPtr)
      return;

   // Walk through the array
   pufi = (PUSERFORMINFO)pbPtr;
   for (i = 1; i <= usSize; i++)
   {
      // insert the name into the main dialogs' list of forms
      sIndex = (SHORT)WinSendMsg (hwnd,
                                  LM_INSERTITEM,
                                  MPFROMSHORT (LIT_END),
                                  MPFROMP (pufi->achFormName));

      // Create a handle
      pbItem = GplMemoryAlloc (pDevMode->hmcbHeap, sizeof (USERFORMINFO));
      if (pbItem)
      {
         *(PUSERFORMINFO)pbItem = *pufi;

         // And set its handle
         WinSendMsg (hwnd,
                     LM_SETITEMHANDLE,
                     (MPARAM)sIndex,
                     (MPARAM)pbItem);

         if ((pDevMode->pSetup->usFlags & FL_USERDEFINEDFORMS) &&
             pDevMode->pSetup->Size == pufi->usFormID)
            sRetVal = sIndex;
      }

      pufi++;
   }

   // Free the memory
   GplMemoryFree ((PVOID)pbPtr);

   return sRetVal;
}

/***************************************************************************
 *
 * FUNCTION NAME = delete_user_forms_from_list
 *
 * DESCRIPTION   = This function will delete any item in the given list box
 *                 that has a handle.  Only user defined forms should have
 *                 this property!  Any user defined form will be deleted from
 *                 the list box and its associated memory will be freed.
 *
 * INPUT         = PDEVMODE pDevMode - instance data
 *                 HWND     hwnd     - List box window handle
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
delete_user_forms_from_list (PDEVMODE pDevMode, HWND hwnd)
{
   SHORT         sIndex, sNumItems;
   ULONG         ulHandle;

   // Walk through the list box
   sIndex = 0;
   do
   {
      // Ask for the current list item's handle
      ulHandle = (ULONG)WinSendMsg (hwnd,
                                    LM_QUERYITEMHANDLE,
                                    (MPARAM)sIndex, (MPARAM)0);

      // If it has a handle then it is a user form (ours)!
      if (NULLHANDLE != ulHandle)
      {
         // Delete it and don't increment the index because the index stays
         // the same
         WinSendMsg (hwnd,
                     LM_DELETEITEM,
                     (MPARAM)sIndex, (MPARAM)0);

         // Free the memory
         GplMemoryFree ((PVOID)ulHandle);
      }
      else
         sIndex++;

      // Find out how many items are left in the list box
      sNumItems = (SHORT)WinSendMsg (hwnd, LM_QUERYITEMCOUNT, (MPARAM)0, (MPARAM)0);
   } while (sNumItems > sIndex);
}

/***************************************************************************
 *
 * FUNCTION NAME = id_conflicts
 *
 * DESCRIPTION   = This function will walk though the linked list and search
 *                 for an element that has the same ID (of course, it will
 *                 not check against itself).  If there is a conflict, then
 *                 it will return that element's form name.
 *
 * INPUT         = PPUSERFORMLIST ppuflHead - Linked list handle
 *                 PUSERFORMLIST  pSelf     - The linked list element to check
 *                 USHORT         usFormID  - The ID to check against
 *
 * OUTPUT        = PSZ  - The form name of the identical ID
 *                 NULL - No form exists with this ID
 *
 **************************************************************************/
PSZ
id_conflicts (PUSERFORMLIST puflHead, PUSERFORMLIST pSelf, USHORT usFormID)
{
   PUSERFORMLIST pElm = NULL;

   // walk through the linked list
   for (pElm = puflHead; pElm; pElm = pElm->pNext)
      if ((pElm != pSelf)                &&
          (usFormID == pElm->ufi.usFormID))
         return pElm->ufi.achFormName;

   return (PSZ)NULL;
}

/***************************************************************************
 *
 * FUNCTION NAME = name_conflicts
 *
 * DESCRIPTION   = This function will walk though the linked list and search
 *                 for an element that has the same form name (of course, it
 *                 will not check against itself).  If there is a conflict,
 *                 then it will return that element's form name.
 *
 * INPUT         = PPUSERFORMLIST ppuflHead - Linked list handle
 *                 PUSERFORMLIST  pSelf     - The linked list element to check
 *                 USHORT         usFormID  - The ID to check against
 *
 * OUTPUT        = PSZ  - The form name of the identical form name
 *                 NULL - No form exists with this name
 *
 **************************************************************************/
PSZ
name_conflicts (PUSERFORMLIST puflHead, PUSERFORMLIST pSelf, PSZ pszName)
{
   PUSERFORMLIST pElm = NULL;

   // walk through the linked list
   for (pElm = puflHead; pElm; pElm = pElm->pNext)
      if ((pElm != pSelf)                           &&
          (0 == strcmp (pszName, pElm->ufi.achFormName)))
         return pElm->ufi.achFormName;

   return (PSZ)NULL;
}

/***************************************************************************
 *
 * FUNCTION NAME = display_form_id
 *
 * DESCRIPTION   = This function will take a linked list element and update
 *                 the window's name with that element's ID.
 *
 * INPUT         =  HWND          hwnd - Window handle
 *                  PUSERFORMLIST pElm - User defined form element
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
display_form_id (HWND hwnd, PUSERFORMLIST pElm)
{
   CHAR achTemp[255];

   if (pElm)
   {
      // Create an ASCII representation of the form ID
      sprintf (achTemp, "%d", (int)pElm->ufi.usFormID);
      WinSetWindowText (hwnd, achTemp);
   }
}

/***************************************************************************
 *
 * FUNCTION NAME = display_error
 *
 * DESCRIPTION   = This function will display a message box that contains
 *                 the string ErrorName with the id ErrorMessage.
 *
 * INPUT         = HWND  hwnd          - Window handle
 *                 SHORT sErrorMessage - Error resource id
 *                 PSZ   pszErrorName  - Error name
 *
 * OUTPUT        = VOID
 *
 **************************************************************************/
VOID
display_error (HWND hwnd, SHORT sErrorMessage, PSZ pszErrorName)
{
   HAB    hab;
   CHAR   achText[MSGBUFSIZE];
   CHAR   achTitle[MSGBUFSIZE];
   CHAR   achBuffer[MSGBUFSIZE];

   // Get the strings from the resource table
   WinLoadString(hab, get_mod_handle(), sErrorMessage, MSGBUFSIZE, achText);
   WinLoadString(hab, get_mod_handle(), IDS_ErrorTitle, MSGBUFSIZE, achTitle);

   // Build the string
   // achText should have an sprintf %s command in it
   sprintf (achBuffer, achText, pszErrorName);

   // Display the message box
   WinMessageBox (HWND_DESKTOP, hwnd, achBuffer, achTitle, 0, MB_OK);
}

/***************************************************************************
 *
 * FUNCTION NAME = find_user_form_size
 *
 * DESCRIPTION   = This function will search the ini file for that user defined
 *                 form id.  It will then update the printable X and Y mm sizes.
 *
 * INPUT         = PVOID hmcbHeap         - The heap handle
 *                 PSZ   pszAppName     - The ini application name
 *                 SHORT Size           - the plotter form id
 *                 PLONG plPrintableXmm - OUTPUT
 *                 PLONG plPrintableYmm - OUTPUT
 *
 * OUTPUT        = VOID - plPrintableXmm & plPrintableYmm will be valid
 *
 **************************************************************************/
VOID
find_user_form_size (PVOID hmcbHeap,
                     PSZ   pszAppName,
                     SHORT Size,
                     PLONG plPrintableXmm,
                     PLONG plPrintableYmm)
{
   INT           i;
   USHORT        usSize;
   PBYTE         pbPtr;
   PUSERFORMINFO pufi;

   usSize = number_user_forms_defined (pszAppName, HWND_DESKTOP);
   if (0 == usSize)
      // Error!
      return;

   pbPtr = return_flat_user_forms_list (hmcbHeap, pszAppName);
   if ((PBYTE)NULL == pbPtr)
      // Error!
      return;

   // Walk through the array of elements
   pufi = (PUSERFORMINFO)pbPtr;
   for (i = 1; i <= usSize; i++)
   {
      if (Size == pufi->usFormID)
      {
         // Values are stored in hundredths of mm ... round up to nearest mm
         *plPrintableXmm = (pufi->ulWidth+50)/100;
         *plPrintableYmm = (pufi->ulHeight+50)/100;
         break;
      }

      pufi++;
   }

   // Free the memory
   GplMemoryFree ((PVOID)pbPtr);
}

/***************************************************************************
 *
 * FUNCTION NAME = user_form_exists_exists
 *
 * DESCRIPTION   = This function will search the user defined forms in the
 *                 ini file under the application name for the given form name.
 *
 * INPUT         = PVOID         hmcbHeap     - The heap handle
 *                 PSZ           pszAppName - The ini application name
 *                 PUSERFORMINFO pUfi       - User defined form element
 *
 * OUTPUT        = BOOL - TRUE if the form name exists, FALSE otherwise
 *
 **************************************************************************/
BOOL
user_form_exists_exists (PVOID         hmcbHeap,
                         PSZ           pszAppName,
                         PUSERFORMINFO pUfi)
{
   INT           i;
   USHORT        usSize;
   PBYTE         pbPtr;
   PUSERFORMINFO pufi;
   BOOL          bRetVal = FALSE;

   usSize = number_user_forms_defined (pszAppName, HWND_DESKTOP);
   if (0 == usSize)
      return FALSE;

   pbPtr = return_flat_user_forms_list (hmcbHeap, pszAppName);
   if ((PBYTE)NULL == pbPtr)
      return FALSE;

   pufi = (PUSERFORMINFO)pbPtr;
   for (i = 1; i <= usSize; i++)
   {
      if (0 == strcmp (pUfi->achFormName, pufi->achFormName))
         bRetVal = TRUE;

      pufi++;
   }

   // Free the memory
   GplMemoryFree ((PVOID)pbPtr);

   return bRetVal;
}
