/*
 *	$Source: /u1/Xr/src/Xrlib/Editor/RCS/PushButton.c,v $
 *	$Header: PushButton.c,v 1.1 86/12/17 09:01:51 swick Exp $
 */

#ifndef lint
static char *rcsid_PushButton_c = "$Header: PushButton.c,v 1.1 86/12/17 09:01:51 swick Exp $";
#endif	lint


#include <Xr/xr-copyright.h>

/* $Header: PushButton.c,v 1.1 86/12/17 09:01:51 swick Exp $ */
/* Copyright 1986, Hewlett-Packard Company */
/* Copyright 1986, Massachussetts Institute of Technology */

static char rcsid[] = "$Header: PushButton.c,v 1.1 86/12/17 09:01:51 swick Exp $";
/*************************************<+>*************************************
 *****************************************************************************
 **
 **   File:        PushButton.c
 **
 **   Project:     X-ray Toolbox
 **
 **   Description: 
 **         Source code for the X-ray PushButton field editor.
 **
 **
 **   ------------------------ MODIFICATION RECORD   ------------------------
 *
 * $Log:	PushButton.c,v $
 * Revision 1.1  86/12/17  09:01:51  swick
 * Initial revision
 * 
 * Revision 7.0  86/11/13  08:23:45  08:23:45  fred ()
 * Final QA Release
 * 
 * Revision 6.0  86/11/10  15:28:49  15:28:49  fred ()
 * QA #2 release
 * 
 * Revision 5.2  86/11/07  14:20:24  14:20:24  fred ()
 * Added new copyright message.
 * 
 * Revision 5.1  86/10/30  13:26:29  13:26:29  fred ()
 * Added check for numCols <= 0, and changed the button
 * drawing algorithm.
 * 
 * Revision 5.0  86/10/28  08:28:45  08:28:45  fred ()
 * QA #1.1 release
 * 
 * Revision 4.1  86/10/22  11:47:00  11:47:00  fred ()
 * Filled in the procedure headers.
 * 
 * Revision 4.0  86/10/20  12:10:41  12:10:41  fred ()
 * QA #1 release
 * 
 * Revision 3.4  86/10/16  09:14:50  09:14:50  fred ()
 * Performance enhanced: added use of register variables.
 * 
 * Revision 3.3  86/10/13  10:05:30  10:05:30  fred ()
 * Added use of the default tile, if needed.
 * 
 * Revision 3.2  86/10/09  07:44:38  07:44:38  fred ()
 * Added default color check to create routine.
 * 
 * Revision 3.1  86/10/07  16:04:13  16:04:13  fred ()
 * Added check for invalid rectangle size.
 * 
 * Revision 3.0  86/10/02  15:58:40  15:58:40  fred ()
 * Alpha release set to 3.0
 * 
 * Revision 2.4  86/09/25  13:24:36  13:24:36  fred ()
 * Added the compile ifdef MVAX, for drawing real ovals.
 * 
 * Revision 2.3  86/09/22  13:10:37  13:10:37  fred ()
 * Added calls to XrEditorGroup().
 * 
 * Revision 2.2  86/09/19  12:13:52  12:13:52  fred ()
 * Changed pushbuttons so that they no longer act like checkboxes.
 * They turn on with a select, and off with a select up.
 * 
 * Revision 2.1  86/09/19  07:09:55  07:09:55  fred ()
 * Added a check for XrVISIBLE before doing any drawing.
 * 
 * Revision 2.0  86/09/16  08:05:53  08:05:53  fred ()
 * Updated input processing routine to use new SELECT strategy.
 * 
 * Revision 1.4  86/09/16  05:45:45  05:45:45  fred ()
 * Modified to swallow a select up event.
 * 
 * Revision 1.3  86/09/10  07:25:30  07:25:30  fred ()
 * Changed numRows to numCols in all data structures.
 * 
 * Revision 1.2  86/09/09  13:07:26  13:07:26  fred ()
 * Changed some variables from INT32 to INT16
 * 
 * Revision 1.1  86/09/03  13:57:08  13:57:08  fred ()
 * Initial revision
 * 
 *
 *****************************************************************************
 *************************************<+>*************************************/



#include <X/Xlib.h>
#include <Xr/defs.h>
#include <Xr/types.h>
#include <Xr/in_types.h>

extern INT32 pbFreeMemory();
extern INT32 createPButton();
extern INT32 drawPButton();
extern INT32 processPButton();
extern INT32 calcButtonComponents();


/*************************************<->*************************************
 *
 *  xrEditor *
 *  XrPushButton (pushButton, message, data)
 *
 *     xrEditor * pushButton;
 *     INT32      message;
 *     INT8     * data;
 *
 *   Description:
 *   -----------
 *     This is the main entry point and message handler for the push button
 *     field editor.  It takes all messgae requests, and passes them onto
 *     the appropriate handler;  invalid messages will fail and return an
 *     error.
 *
 *
 *   Inputs:
 *   ------
 *     pushButton = For all messages but MSG_NEW and MSG_SIZE, this
 *                  contains the instance pointer.
 *
 *     message = This indicates which action the editor should perform.
 *
 *     data = This is the message specific data.  It's usage depends upon
 *            the 'message' parameter, and may be a scalar or a pointer.
 * 
 *   Outputs:
 *   -------
 *     Upon successful completion of a command, the instance pointer
 *          is returned; additional information may be returned by means
 *          of the 'data' parameter.
 *
 *     Upon failure, NULL is returned, and xrErrno is set.
 *
 *   Procedures Called
 *   -----------------
 *   _MsgNew()            [MsgCommon.c]
 *   _MsgFree()           [MsgCommon.c]
 *   _MsgGetState()       [MsgCommon.c]
 *   _MsgSetState()       [MsgCommon.c]
 *   _MsgRedraw()         [MsgCommon.c]
 *   _MsgEdit()           [MsgCommon.c]
 *   _MsgGetItemStates()  [MsgItem.c]
 *   _MsgSetItemStates()  [MsgItem.c]
 *   _MsgGetItemCount()   [MsgItem.c]
 *   _MsgGetItemRects()   [MsgItem.c]
 *   _MsgSize()           [MsgItem.c]
 *   _MsgMove()           [MsgItem.c]
 *   XrCopyRect()         [calc.c]
 *   _XrMakeInvisible()   [editorUtil.c]
 *   XrEditorGroup()      [group.c]
 *   createPButton()
 *   drawPButton()
 *   processPButton()
 *
 *************************************<->***********************************/

xrEditor *
XrPushButton (pushButton, message, data)

   register xrEditor * pushButton;
            INT32      message;
            INT8     * data;

{
   /* Determine the action being requested */
   switch (message)
   {
      case MSG_NEW:
      {
           /*
            * Create a new push button editor instance.
            * The only parameter of interest is the 'data'
            * parameter, which is a pointer to a filled
            * instance of the xrPushButtonInfo structure.
            */
           return ((xrEditor *) _MsgNew (pushButton, data,
                                         sizeof(xrPushButtonData),
                                         createPButton, drawPButton,
                                         pbFreeMemory, XrPushButton,
                                         XrALLBUTTONS));
      }

      case MSG_FREE:
      {
           /*
            * Destroy the specified push button instance.
            * The 'pushButton' parameter specifies the instance to
            * be destroyed; the 'data' parameter is unused.
            */
           return ((xrEditor *) _MsgFree (pushButton, pbFreeMemory));
      }

      case MSG_GETSTATE:
      {
           /*
            * Return the settings of the state flags for the
            * specified push button instance.  The 'pushButton'
            * parameter specifies the instance to be queried, while
            * the 'data' parameter must point to an INT8 value, into
            * which the state flags will be placed.
            */
           return ((xrEditor *) _MsgGetState (pushButton, data));
      }

      case MSG_SETSTATE:
      {
           /*
            * Change the state flag settings for the specified
            * instance.  The 'pushButton' parameter specifies the
            * instance to be modified, and the 'data' parameter is
            * interpreted as an INT8 value, containing the new state
            * flag settings.
            */
           return ((xrEditor *) _MsgSetState (pushButton, data,
                                              drawPButton, XrALLBUTTONS));
      }

      case MSG_GETITEMSTATES:
      {
         /*
          * Return the state flags for the individual push buttons.
          * The 'pushButton' parameter specifies the instance to be
          * queried, while the 'data' parameter must point to an array
          * of INT8 values, into which the state flags will be placed.
          */
         return ((xrEditor *) _MsgGetItemStates(pushButton, data));
      }

      case MSG_SETITEMSTATES:
      {
         /*
          * Set the individual button states; redraw only those
          * whose state has then changed.  The 'pushButton' parameter
          * specifies the instance to be modified, while the 'data'
          * parameter points to an array of INT8 values, containing
          * the new state flags.
          */
         return ((xrEditor *)_MsgSetItemStates(pushButton, data, drawPButton));
      }

      case MSG_GETITEMCOUNT:
      {
         /*
          * Return the number of push buttons in the specified instance.
          * The 'pushButton' parameter specifies the instance to be
          * queried, while the 'data' parameter must point to an INT32
          * value, into which the item count will be placed.
          */
         return ((xrEditor *) _MsgGetItemCount (pushButton, data));
      }

      case MSG_GETITEMRECTS:
      {
         /*
          * Fill the array passed in by the application, with the
          * rectangle information describing each push button in
          * the specified instance.  The 'pushButton' parameter
          * indicates the instance to be queried, while the 'data'
          * parameter must point to an array of RECTANGLE structures,
          * into which the individual button rectangles will be placed.
          */
         return ((xrEditor *) _MsgGetItemRects (pushButton, data));
      }

      case MSG_SIZE:
      {
	   /*
            * Return the size of the rectangle needed to enclose
            * an instance of this editor, using the specifications
            * passed in by the application program.  The 'data'
            * parameter must point to a partially filled out instance
            * of the 'xrPushButtonInfo' structure; upon completion,
            * the 'editorRect' field of the 'info' structure will be
            * filled.
	    */
           return ((xrEditor *) _MsgSize (data, calcButtonComponents));
      }

      case MSG_MOVE:
      {
         /*
          * Relocate an instance of push buttons to a new location.
          * The 'pushButton' parameter specifies the instance to be
          * moved, while the 'data' parameter must point to a POINT
          * structure, containing the new editor rectangle origin.
          */
         return ((xrEditor *) _MsgMove (pushButton, data, drawPButton));
      }

      case MSG_RESIZE:
      {
         /*
          * Resize an existing instance of push buttons.
          * The 'pushButton' parameter indicates the instance to be
          * resized, while the 'data' parameter must point to a
          * RECTANGLE structure, containing the new editor rectangle
          * definition.
          */
                  RECTANGLE          workRect;
                  xrPushButtonInfo   pbInfo;
         register xrPushButtonData * pbDataPtr;
         register xrPushButtonInfo * pbInfoPtr;

         if (pushButton == NULL)
         {
            xrErrno = XrINVALIDID;
            return ((xrEditor *) NULL);
         }
         else if (data == NULL)
         {
            xrErrno = XrINVALIDPTR;
            return ((xrEditor *) NULL);
         }

         /* Create a pseudo Info structure */
         pbDataPtr = (xrPushButtonData *) pushButton->editorData;
         XrCopyRect (&pushButton->editorRect, &workRect);
         pbInfoPtr = &pbInfo;
         XrCopyRect ((RECTANGLE *) data, &pbInfoPtr->editorRect);
         pbInfoPtr->editorFont = pbDataPtr->pbFont.fontInfo;
         pbInfoPtr->numFields = pbDataPtr->pbNumFields;
         pbInfoPtr->numCols = pbDataPtr->pbNumCols;
         pbInfoPtr->labels = pbDataPtr->pbLabels;
         pbInfoPtr->borderWidth = pbDataPtr->pbBorder;
         pbInfoPtr->defaultButton = pbDataPtr->pbDefaultButton;

         if (createPButton (pbDataPtr, pbInfoPtr, MSG_RESIZE) == FALSE)
            return ((xrEditor *) NULL);
         XrCopyRect (&pbInfoPtr->editorRect, &pushButton->editorRect);
 
         if (pushButton->editorState & XrVISIBLE)
         {
            /* Remove the instance from the window */
            _XrMakeInvisible (pushButton->editorWindowId, &workRect, TRUE);

            /* Redisplay the instance */
            drawPButton (pushButton, XrALLBUTTONS);
         }

         /* Force the editor group rectangle to be recalculated */
         XrEditorGroup (NULL, MSG_ADJUSTGROUPRECT, pushButton);
         return (pushButton);
      }

      case MSG_REDRAW:
      {
         /*
          * Redraw a push button instance.
          * The 'pushButton' parameter specifies the instance to redraw,
          * while the 'data' parameter is interpreted as an INT32 value,
          * specifying the type of redraw to perform.
          */
         return ((xrEditor *) _MsgRedraw (pushButton, data,
                                          drawPButton, XrALLBUTTONS));
      }

      case MSG_EDIT:
      {
	 /*
          * Process the incoming event, and generate a return
          * event, to indicate to the application program
          * how the editor instance was modified.  The 'data'
          * parameter must point to the event to be processed.
	  */
         return ((xrEditor *) _MsgEdit (pushButton, data,
                                        processPButton, XrPUSHBUTTON));
      }

      default:
         /* All other commands are invalid */
         xrErrno = XrINVALIDMSG;
         return ((xrEditor *)NULL);

   }  /* end of switch */
}  /* end of XrPushButton() */


/*************************************<->*************************************
 *
 *  INT32
 *  pbFreeMemory (pbDataPtr)
 *
 *     xrPushButtonData * pbDataPtr;
 *
 *   Description:
 *   -----------
 *     This routine is called both when an existing instance is freed,
 *     and when a MSG_NEW request fails after createPButton() has been
 *     called.  It will free up all resources which createPButton()
 *     allocated for the instance.
 *
 *
 *   Inputs:
 *   ------
 *     pbDataPtr = Pointer to the instance's internal 'data' structure.
 * 
 *   Outputs:
 *   -------
 *
 *   Procedures Called
 *   -----------------
 *   XFreePixmap()  [libX.a]
 *
 *************************************<->***********************************/

static
INT32
pbFreeMemory (pbDataPtr)

   xrPushButtonData * pbDataPtr;

{
   if (pbDataPtr->pbTileId != xrDefaultTile)
      XFreePixmap (pbDataPtr->pbTileId);
   (*xrFree) (pbDataPtr->pbFields);
}


/*************************************<->*************************************
 *
 *  INT32
 *  calcButtonComponents (pbInfoPtr, fields, cmd)
 *
 *     xrPushButtonInfo * pbInfoPtr;
 *     xrItemData       * fields;
 *     INT32              cmd;
 *
 *   Description:
 *   -----------
 *     If the 'cmd' parameter is set to MSG_SIZE, then this routine will
 *     calculate the size of the editor rectangle needed to contain the
 *     instance described by the structure pointed to by 'pbInfoPtr; the
 *     editor rectangle information is returned in the 'editorRect' field
 *     within the structure pointed to by the 'pbInfoPtr' parameter.
 *     If the 'cmd' parameter is set to MSG_NEW, then this routine will
 *     calculate the location and size of each push button rectangle and
 *     corresponding button label; this information is then stored in the
 *     structure pointed to by the 'fields' parameter.
 *
 *
 *   Inputs:
 *   ------
 *     rbInfoPtr = This points to an instance of the push button 'info'
 *                 structure, which describes the instance being sized
 *                 or created.
 *
 *     fields = This must point to an array of structures, which will be
 *              used to store temporary information, if 'cmd' is set to
 *              MSG_SIZE, or will be used to store the component 
 *              information, if 'cmd' is set to MSG_NEW.
 *
 *     cmd = This indicates whether just the size of the editor rectangle
 *           needs to be calculated (MSG_SIZE), or whether the size and
 *           location of each component must be calculated (MSG_NEW).
 * 
 *   Outputs:
 *   -------
 *     Upon successful completion, TRUE is returned, along with some
 *          additional information in either the 'pbInfoPtr' structure,
 *          or the 'fields' structure.
 *
 *     Upon failure, FALSE is returned, and xrErrno is set.
 *
 *   Procedures Called
 *   -----------------
 *   _XrTextInfo()    [textUtil.c]
 *   XrCopyRect()     [calc.c]
 *   XrStringWidth()  [utilities.c]
 *   XrOffsetRect()   [calc.c]
 *
 *************************************<->***********************************/

static
INT32
calcButtonComponents (pbInfoPtr, fields, cmd)

   register xrPushButtonInfo  * pbInfoPtr;
   register xrItemData        * fields;
            INT32               cmd;

{
            INT16        numFields = pbInfoPtr->numFields;
            INT16        numCols = pbInfoPtr->numCols;
            INT16        borderWidth = pbInfoPtr->borderWidth;
            INT16        numRows;
            RECTANGLE  * rectPtr = &(pbInfoPtr->editorRect);
   register INT8       * end, * start;
            FontInfo   * fontPtr;
            xrTextInfo   fontData;
            RECTANGLE    workRect;
   register INT32        i, j;
            INT32        lineCount, maxLen;
            INT16        border;
            INT16        hPadding, vPadding;
            INT16        xSize, ySize;
   register INT16        maxX, maxY;
            INT16        xPos, yPos;
            INT16        colWid, rowHt, lineHt;
            INT16        stringWidth;
            INT16        dftButton;

   /* Check for invalid dimensions */
   if ((numFields < numCols) || (borderWidth <= 0) ||
       (numFields <= 0) || (numCols <= 0))
   {
      xrErrno = XrINVALIDPARM;
      return (FALSE);
   }

   /* Initialize variables we'll need for our calculations */
   numRows = (numFields + numCols - 1) / numCols;
   fontPtr = pbInfoPtr->editorFont ? pbInfoPtr->editorFont : xrBaseFontInfo;
   _XrTextInfo (fontPtr, &fontData);
   lineHt = fontData.ascent + fontData.descent + fontData.leading;
   XrCopyRect (&xrZeroRect, &workRect);
   rectPtr = &(pbInfoPtr->editorRect);
   border = borderWidth << 1;
   hPadding = 3 * borderWidth;
   vPadding = 3 * borderWidth;
   dftButton = pbInfoPtr->defaultButton;

   /*
    * For each button in this instance, determine the number of lines
    * of text in the label, along with the length of the longest line
    * in the label.  Then, we'll use this information to determine the
    * preliminary size of the rectangle needed to contain the button.
    */
   for (i = 0; i < numFields; i++)
   {
      if ((pbInfoPtr->labels) && (pbInfoPtr->labels[i]))
      {
         /* Determine the number of lines in the label, and the length */
         for (lineCount = 0, end = pbInfoPtr->labels[i], maxLen = 0,
              start = end;;)
         {
            /* Check for the end of a line */
            if ((*end == '\n') || (*end == NULL))
            {
               /* End of line encountered */
               lineCount++;

               /* See if this is the longest line yet in the label */
               stringWidth = XrStringWidth (fontPtr, start, 
                                            end - start, 0, 0);
               if (stringWidth > maxLen)
                  maxLen = stringWidth;

               /* Stop if the end of the string was encountered */
               if (*end == NULL)
                  break;

               /* Skip over the Newline, and continue */
               start = ++end;
               continue;
            }

            /* Advance to next character */
            end++;
         }
      }
      else
      {
         /* This button has no label */
         lineCount = 0;
         maxLen = 0;
      }

      /* We can now determine the preliminary size of the button */
      ySize = (i == dftButton ? border << 1 : border) + 
              (lineHt * lineCount) + vPadding;
      xSize = (i == dftButton ? border << 1 : border) + 
              maxLen + ySize + hPadding;
      workRect.width = xSize;
      workRect.height = ySize;
      XrCopyRect (&workRect, &((fields + i)->rectangle));
   }

   /*
    * At this point, we will adjust each button within a row, so that
    * they all have the same height.
    */
   for (i = 0; i < numFields; i += numCols)
   {
      /* Find the button with the highest height in each row */
      maxY = 0;
      for (j = i; j < i + numCols && j < numFields; j++)
      {
         if (maxY < (fields + j)->rectangle.height)
            maxY = (fields + j)->rectangle.height;
      }

      /* Set the height of all buttons in the row to this max value */
      for (j = i; j < i + numCols && j < numFields; j++)
         (fields + j)->rectangle.height = maxY;
   }

   /*
    * Now, we need to adjust each button in a column, so that they
    * all have the same width.
    */
   for (i = 0; i < numCols; i++)
   {
      /* Find the button with the largest width in each column */
      maxX = maxY = 0;
      for (j = i; j < numFields; j += numCols)
      {
         if (maxX < (fields + j)->rectangle.width)
            maxX = (fields + j)->rectangle.width;
         if (maxY < (fields + j)->rectangle.height)
            maxY = (fields + j)->rectangle.height;
      }

      /* Make sure the buttons end up proportionally sized */
      if (maxX < (maxY << 1))
         maxX = maxY << 1;

      /* Adjust the width of all buttons in this column */
      for (j = i; j < numFields; j+= numCols)
         (fields + j)->rectangle.width = maxX;
   }

   /*
    * Determine the total height and width of the instance, by
    * adding up the width of one row, and the height of one column.
    */
   for (i = maxX = 0; i < numCols; i++)
      maxX += (fields + i)->rectangle.width;

   for (i = maxY = 0; i < numFields; i += numCols)
      maxY += (fields + i)->rectangle.height;

   /*
    * If this is only a size request, then use some default padding
    * values to space out the buttons, and then return the size
    * of the rectangle which would enclose the whole instance, int
    * the editorRect portion of the Init structure.
    */
   if (cmd == MSG_SIZE)
   {
      /* Calculate the default padding values */
      hPadding = (fontData.maxWidth << 1) * (numCols - 1);
      vPadding = lineHt * (numRows - 1);

      /* Set the rectangle values */
      XrCopyRect (&xrZeroRect, rectPtr);
      rectPtr->width = hPadding + maxX;
      rectPtr->height = vPadding + maxY;
      return (TRUE);
   }

   /*
    * Otherwise, this is a create request, so determine how much extra
    * space is available in the specified editorRect, and use it as
    * padding between each of the buttons.
    */
   hPadding = (rectPtr->width - maxX) / ((numCols == 1) ? 1 : numCols - 1);
   vPadding = (rectPtr->height - maxY) / ((numRows == 1) ? 1 : numRows - 1);

   /* Position each button within a row, starting at top left corner */
   xPos = rectPtr->x;
   yPos = rectPtr->y;

   for (i = 0; i < numFields; i+= numCols)
   {
      rowHt = (fields + i)->rectangle.height;
      colWid = 0;

      for (j = i; j < i + numCols && j < numFields; j++)
      {
         XrOffsetRect (&((fields + j)->rectangle), colWid + xPos, yPos);
         colWid += hPadding + ((fields + j)->rectangle.width);
      }

      /* Repeat for the next row */
      yPos += vPadding + rowHt;
   }

   return (TRUE);
}


/*************************************<->*************************************
 *
 *  INT32
 *  createPButton (pbDataPtr, pbInfoPtr, message)
 *
 *     xrPushButtonData * pbDataPtr;
 *     xrPushButtonInfo * pbInfoPtr;
 *     INT32              message;
 *
 *   Description:
 *   -----------
 *     if (message == MSG_NEW)
 *     This routine is responsible for creating a new push button instance.
 *     The description of the new instance is contained within the structure
 *     pointed to by the 'pbInfoPtr' parameter.  Besides allocating the
 *     resources needed for the new instance, this procedure will also fill
 *     in the structure pointed to by the 'pbDataPtr' parameter with the
 *     state information for the new instance.
 *
 *     if (message == MSG_RESIZE)
 *     This routine will take the information contained within the 
 *     'pbInfoPtr' structure, and will recalculate the location of each 
 *     component within an existing instance of push buttons, to match
 *     the new data.  This will do no resource allocation.
 *
 *
 *   Inputs:
 *   ------
 *     pbDataPtr = Points to the 'data' structure for the instance being
 *                 created or resized.
 *
 *     pbInfoPtr = Points to the 'info' structure, which describes how the
 *                 instance is to be laid out.
 *
 *     message = This can be set to MSG_NEW or MSG_RESIZE.
 * 
 *   Outputs:
 *   -------
 *     Upon successful completion, TRUE is returned, and the 'pbDataPtr'
 *          structure is filled out.
 *
 *     Upon failure, FALSE is returned, and xrErrno is set.
 *
 *   Procedures Called
 *   -----------------
 *   XrResource()   [resource.c]
 *   XMakePixmap()  [libX.a]
 *   XFreePixmap()  [libX.a]
 *   XrCopyRect()   [calc.c]
 *   XrSetPt()      [calc.c]
 *   _XrTextInfo()  [textUtil.c]
 *   calcButtonComponents()
 *
 *************************************<->***********************************/

static
INT32
createPButton (pbDataPtr, pbInfoPtr, message)

   register xrPushButtonData * pbDataPtr;
   register xrPushButtonInfo * pbInfoPtr;
            int                message;

{
            FontInfo       * fontPtr;
   register xrItemData     * aButton;
   register INT16            i;
            xrResourceInfo   bitmapInfo;
            RECTANGLE        editorRect;
            RECTANGLE        minRect;
            xrItemData     * tempItems;

   /*
    * Allocate some space to hold the calculations performed
    * when we check the rectangle size.
    */
   if ((tempItems = (xrItemData *) (*xrMalloc) 
       (sizeof (xrItemData) * pbInfoPtr->numFields)) == NULL)
   {
      /* Unable to allocate the memory we need */
      xrErrno = XrOUTOFMEM;
      return (FALSE);
   }

   if (message == MSG_NEW)
   {
      /* Allocate space to hold the individual button information */
      if ((pbDataPtr->pbFields = (xrItemData *) (*xrMalloc) 
          (sizeof (xrItemData) * pbInfoPtr->numFields)) == NULL)
      {
         /* Unable to allocate the memory we need */
         (*xrFree)(tempItems);
         xrErrno = XrOUTOFMEM;
         return (FALSE);
      }

      pbDataPtr->pbFGColor = (pbInfoPtr->editorFGColor == -1) ?
                     xrForegroundColor : pbInfoPtr->editorFGColor;
      pbDataPtr->pbBGColor = (pbInfoPtr->editorBGColor == -1) ?
                     xrBackgroundColor : pbInfoPtr->editorBGColor;

      /* Create the 50% tile we will need when drawing insensitive buttons */
      if ((pbInfoPtr->editorFGColor == -1) &&
          (pbInfoPtr->editorBGColor == -1))
      {
         /* Use the default 50% tile */
         pbDataPtr->pbTileId = xrDefaultTile;
      }
      else
      {
         bitmapInfo.resourceType = XrTYPE_BITMAPID;
         bitmapInfo.resourceId = XrPERCENT50;
         if ((XrResource (MSG_FIND, &bitmapInfo) == FALSE) ||
            ((pbDataPtr->pbTileId = 
             XMakePixmap (((xrBitmapId *) bitmapInfo.resourceObject)->bitmapId,
                         pbDataPtr->pbFGColor, pbDataPtr->pbBGColor)) == 0))
         {
            /* Unable to create the tile */
            (*xrFree)(tempItems);
            (*xrFree) (pbDataPtr->pbFields);
            xrErrno = XrXCALLFAILED;
            return (FALSE);
         }
      }
   }

   /* Check the rectangle size */
   XrCopyRect (&pbInfoPtr->editorRect, &editorRect);
   if (calcButtonComponents (pbInfoPtr, tempItems, MSG_SIZE) == FALSE)
   {
      /* The application must have supplied invalid information */
      /* xrErrno is set by calcButtonComponents */
      XrCopyRect (&editorRect, &pbInfoPtr->editorRect);
      (*xrFree)(tempItems);
      if (message == MSG_NEW)
      {
         if (pbDataPtr->pbTileId != xrDefaultTile)
            XFreePixmap (pbDataPtr->pbTileId);
         (*xrFree) (pbDataPtr->pbFields);
      }
      return (FALSE);
   }
   (*xrFree)(tempItems);
   XrCopyRect (&pbInfoPtr->editorRect, &minRect);
   XrCopyRect (&editorRect, &pbInfoPtr->editorRect);
   if ((editorRect.height < minRect.height) ||
       (editorRect.width < minRect.width))
   {
      /* The application supplied an invalid rectangle */
      xrErrno = XrINVALIDRECT;
      if (message == MSG_NEW)
      {
         if (pbDataPtr->pbTileId != xrDefaultTile)
            XFreePixmap (pbDataPtr->pbTileId);
         (*xrFree) (pbDataPtr->pbFields);
      }
      return (FALSE);
   }

   /* Generate the location and size information associated with each button */
   calcButtonComponents (pbInfoPtr, pbDataPtr->pbFields, MSG_NEW);

   if (message == MSG_NEW)
   {
      /* Fill in the rest of the fields in the button structures */
      pbDataPtr->pbNumFields = pbInfoPtr->numFields;
      pbDataPtr->pbNumCols = pbInfoPtr->numCols;
      fontPtr = pbInfoPtr->editorFont ? pbInfoPtr->editorFont : xrBaseFontInfo;
      _XrTextInfo (fontPtr, &pbDataPtr->pbFont);
      pbDataPtr->pbBorder = pbInfoPtr->borderWidth;
      pbDataPtr->pbLabels = pbInfoPtr->labels;
      pbDataPtr->pbActiveButton = -1;
      pbDataPtr->pbStates = pbInfoPtr->stateFlags;
      pbDataPtr->pbDefaultButton = pbInfoPtr->defaultButton;
      aButton = pbDataPtr->pbFields;

      for (i = 0; i < pbDataPtr->pbNumFields; i++, aButton++)
      {
         /* Copy over each button label */
         if (pbInfoPtr->labels)
            aButton->label = pbInfoPtr->labels[i];
         else
            aButton->label = NULL;
   
         /* Copy the initial state flags */
         if (pbInfoPtr->stateFlags)
            aButton->state = pbInfoPtr->stateFlags[i];
         else
            aButton->state =  (XrVISIBLE | XrSENSITIVE);

         /* Set unused fields to zeros */
         XrCopyRect (&xrZeroRect, &(aButton->subRectangle));
         XrSetPt (&(aButton->labelPt), 0, 0);
      }
   }

   return (TRUE);
}


/*************************************<->*************************************
 *
 *  INT32
 *  drawPButton (pushButton, buttonNum)
 *
 *     xrEditor * pushButton;
 *     INT32      buttonNum;
 *
 *   Description:
 *   -----------
 *     This routine will display either a single push button, or all
 *     of the push buttons, depending upon the value specified in the
 *     'buttonNum' parameter.
 *
 *
 *   Inputs:
 *   ------
 *     pushButton = Points to the editor instance structure.
 *
 *     buttonNum = This can be set to the index of the button to be drawn,
 *                 or to the define XrALLBUTTONS, if all the buttons should
 *                 be redrawn.
 * 
 *   Outputs:
 *   -------
 *
 *   Procedures Called
 *   -----------------
 *   _XrMakeInvisible()  [editorUtil.c]
 *   XrCopyRect()        [rectUtil.c]
 *   _XrFillOval()       [ovalUtil.c]
 *   _XrOval()           [ovalUtil.c]
 *   displayButtonLabel()
 *   pbSetUpGCs()
 *
 *************************************<->***********************************/

static
INT32
drawPButton (pushButton, buttonNum)

   xrEditor * pushButton;
   INT32      buttonNum;

{
   register xrPushButtonData   * pbDataPtr;
            Window               windowId;
            INT32                sensitive;
   register INT16                firstButton, lastButton;
   register xrItemData         * aButton;
            RECTANGLE            workRect;
   register RECTANGLE          * workRectPtr;
   register INT16                buttonBorder;
            INT8                 makeGCFlag;
            INT16                dftButton;
            INT32                borderGC;
            INT8                 drawLabelSensitive;
            INT8                 activeFlag;

   /* Initialize variables */
   pbDataPtr = (xrPushButtonData *) pushButton->editorData;
   windowId = pushButton->editorWindowId;
   workRectPtr = &workRect;
   makeGCFlag = TRUE;
   dftButton = pbDataPtr->pbDefaultButton;

   /*
    * If the instance is not visible, then fill its area with the
    * background tile for the port, thus making the instance invisible.
    */
   if (!(pushButton->editorState & XrVISIBLE))
   {
      _XrMakeInvisible (windowId, &pushButton->editorRect, TRUE);
      return;
   }

   /* Set up the graphic contexts needed for drawing */
   pbSetUpGCs (pbDataPtr);

   sensitive = (pushButton->editorState & XrSENSITIVE) ? TRUE : FALSE;

   /* Determine the range of buttons to be drawn */
   if (buttonNum == XrALLBUTTONS)
   {
      firstButton = 0;
      lastButton = pbDataPtr->pbNumFields - 1;
   }
   else
      firstButton = lastButton = buttonNum;

   while (firstButton <= lastButton)
   {
      activeFlag = (firstButton == pbDataPtr->pbActiveButton) ? TRUE:FALSE;
      aButton = (pbDataPtr->pbFields) + firstButton;
      XrCopyRect (&(aButton->rectangle), workRectPtr);

      /* Determine how to draw the button, depending upon it's state */
      if (aButton->state & XrVISIBLE)
      {
         /* Select which graphics context to use for the border */
         if (dftButton == firstButton)
         {
            /* Dft button has double high border */
            buttonBorder = pbDataPtr->pbBorder << 1;
            borderGC = xrEditorGC4;
         }
         else
         {
            /* Regular buttons have single high border */
            buttonBorder = pbDataPtr->pbBorder;
            borderGC = xrEditorGC3;
         }

         /*
          * Modify the oval definition, so that the borders
          * will be properly drawn.
          */
         workRectPtr->height -= buttonBorder;
         workRectPtr->width -= buttonBorder;

         if (sensitive && (aButton->state & XrSENSITIVE))
         {
            /* Determine if the button is active or inactive */
            if (activeFlag)
            {
               /*
                * The state of the button is 'ACTIVE', so draw
                * it as a filled oval, using the foreground color.
                */
               draw1Button (windowId, xrEditorGC1, borderGC, workRectPtr);
            }
            else
            {
               /*
                * The state of the button is 'INACTIVE', so draw
                * draw the oval with the border in the foreground
                * color, and the interior in the background color.
                */
               draw1Button (windowId, xrEditorGC2, borderGC, workRectPtr);
            }

            /* Flag that this label should be drawn as sensitive */
            drawLabelSensitive = TRUE;
         }
         else
         {
            /*
             * The button is insensitive, so we will draw it as a
             * filled oval, using the 50% tile.
             */
            draw1Button (windowId, xrEditorGC3, borderGC, workRectPtr);

            /* Flag that the label should not be drawn as sensitive */
            drawLabelSensitive = FALSE;
         }

         /*
          * If the button has a label, then we will draw that
          * now.  The pens to be used for drawing the label
          * have already been set up above, when the oval
          * was drawn.
          */
         if ((aButton->label) && (strlen(aButton->label) > 0))
            displayButtonLabel (windowId, pbDataPtr, aButton, 
                                drawLabelSensitive, activeFlag);
      }
      else
      {
         /*
          * The button is not visible, so fill the area it would occupy
          * with the window's background color.
          */
         _XrMakeInvisible (windowId, workRectPtr, makeGCFlag);
          makeGCFlag = FALSE;
      }

      /* Go on and process the next button */
      firstButton++;
   }
}


/*************************************<->*************************************
 *
 *  INT32
 *  displayButtonLabel (windowId, pbDataPtr, aButton, drawSensitive,
 *                      activeFlag)
 *
 *   Description:
 *   -----------
 *     This routine displays the label within a particular push button.
 *     The 'aButton' parameter points to a structure containing the label
 *     and the button location information.  If the button is not sensitive
 *     then the label is drawn as hatched.  This routine assumes that all
 *     of the necessary graphic contexts have already been initialized.
 *
 *
 *   Inputs:
 *   ------
 *     windowId = Id of window to which the instance is attached.
 *
 *     pbDataPtr = Points to the instance's internal 'data' structure.
 *                 This is used to access the font information.
 *
 *     aButton = Points to the 'item' structure associated with the
 *               push button whose label is being displayed.
 *
 *     drawSensitive = TRUE if the button is sensitive; FALSE otherwise.
 *
 *     activeFlag = TRUE if the push button is currently active; FALSE
 *                  otherwise.
 * 
 *   Outputs:
 *   -------
 *
 *   Procedures Called
 *   -----------------
 *   XrStringWidth()  [utilities.c]
 *   _XrImageText8()  [textUtil.c]
 *
 *************************************<->***********************************/

static
INT32
displayButtonLabel (windowId, pbDataPtr, aButton, drawSensitive, activeFlag)

            Window             windowId;
            xrPushButtonData * pbDataPtr;
   register xrItemData       * aButton;
            INT8               drawSensitive;
            INT8               activeFlag;

{
   register INT8     * start; 
   register INT8     * end;
            xrTextInfo * fontPtr = &(pbDataPtr->pbFont);
            INT16        lineHt;
            INT16        xCenter, yCenter;
   register INT16        penX, penY;
            INT32        lineCount;
            INT32        gc;

   /* Find the center of the button */
   lineHt = fontPtr->ascent + fontPtr->descent + fontPtr->leading;
   xCenter = (aButton->rectangle.width >> 1) + aButton->rectangle.x;
   yCenter = (aButton->rectangle.height >> 1) + aButton->rectangle.y;

   /* Determine the number of lines in the label */
   lineCount = 1;
   start = aButton->label;
   while (*start != NULL)
   {
      if (*start == '\n')
         lineCount ++;
      start++;
   }

   /* Set the pen position for the start of the label */
   yCenter -= (lineHt * lineCount) >> 1;
   penX = xCenter;
   penY = yCenter;

   /* Display the label */
   for (start = end = aButton->label;;)
   {
      if ((*end == '\n') || (*end == NULL))
      {
         penX -= (XrStringWidth (fontPtr->fontInfo, start, end-start,0,0)) >> 1;
  
         /*
          * Draw the button label.  The technique used to do this
          * will depend upon whether the label is drawn in a sensitive
          * or an insensitive push button.  If the button is sensitive,
          * then the label is simple drawn using the foreground and
          * background colors.  If the button is not sensitive, then
          * the label is drawn several times, so that it is patterned
          * on top of the 50% background pattern.
          */
         if (drawSensitive)
         {
            /* Draw the label as sensitive */
            if (activeFlag)
               gc = xrEditorGC2;
            else
               gc = xrEditorGC1;

            _XrImageText8 (windowId, gc, end-start, penX, penY, start);
         }
         else
         {
            /* Draw the label as insensitive */
            _XrImageText8 (windowId, xrEditorGC5, end-start, 
                           penX, penY, start);
            _XrImageText8 (windowId, xrEditorGC6, end-start, 
                           penX, penY, start);
         }
       
         if (*end == NULL)
            break;

         start = ++end;
         penY += lineHt;
         penX = xCenter;
         continue;
      }

      end++;
   }
}


/*************************************<->*************************************
 *
 *  INT32
 *  processPButton (pushButton, input, returnEvent)
 *
 *     xrEditor     * pushButton;
 *     XButtonEvent * input;
 *     xrEvent      * returnEvent;
 *
 *   Description:
 *   -----------
 *     This is the event processing routine for the push button editor.
 *     It takes an event, and determines which, if any, of the push
 *     buttons was selected.  If a push button was selected, then it
 *     will be draw as 'active', and we will then wait for the user to
 *     release the select button; at that time, the button will be redrawn
 *     as 'inactive'.
 *
 *
 *   Inputs:
 *   ------
 *     pushButton = This is the instance pointer for the instance in
 *                  which the event occurred.
 *
 *     input = This points to the event to be processed.
 *
 *     returnEvent = This points to a partially filled out X-ray event
 *                   structure.  It can be used by this routine when it
 *                   needs to generate a return event for the application.
 *                   Every field is already filled out, except for the
 *                   'value' fields.  When this routine returns to _MsgEdit()
 *                   this event is pushed onto the front of the input queue.
 * 
 *   Outputs:
 *   -------
 *     Although a value is not directly returned, an input event will be
 *         generated, which tells the applicating which push button was
 *         selected.  This event is then pushed onto the input queue by
 *         _MsgEdit().
 *
 *   Procedures Called
 *   -----------------
 *   XrSetPt()  [calc.c]
 *   XrInput()  [input.c]
 *   drawPButton()
 *
 *************************************<->***********************************/

static
INT32
processPButton (pushButton, input, returnEvent)

   xrEditor     * pushButton;
   XButtonEvent * input;
   xrEvent      * returnEvent;

{
   register xrPushButtonData  * pbDataPtr;
            POINT               spritePt;
   register INT16               i;
   register xrItemData        * aButton;
            XEvent              exitEvent;

   pbDataPtr = (xrPushButtonData *) pushButton->editorData;
   XrSetPt (&spritePt, input->x, input->y);
   returnEvent->value1 = NULL;

   /* Check each button to see if one was selected */
   for (i = 0; i < pbDataPtr->pbNumFields; i++)
   {
      aButton = (pbDataPtr->pbFields) + i;

      if (XrPtInRect (&spritePt, &(aButton->rectangle)) &&
         ((aButton->state & (XrVISIBLE | XrSENSITIVE)) ==
         (XrVISIBLE | XrSENSITIVE)))
      {
         /* A button was selected; modify its value and redraw it */
         pbDataPtr->pbActiveButton = i;
         drawPButton (pushButton, i);

         /* Wait for the select up event */
         while (XrInput (NULL, MSG_BLKREAD, &exitEvent) == FALSE);
         XrInput (NULL, MSG_PUSHEVENT, &exitEvent);
         pbDataPtr->pbActiveButton = -1;
         drawPButton (pushButton, i);
  
         /* Save the index of the button which changed, and return */
         returnEvent->value2 = i;
         returnEvent->value1 = XrSELECT;
      }
   }
}

/*************************************<->*************************************
 *
 *  INT32
 *  pbSetUpGCs (pbDataPtr)
 *
 *     xrPushButtonData * pbDataPtr;
 *
 *   Description:
 *   -----------
 *     This routine sets up the 6 graphic contexts which may be needed
 *     whenever a push button instance is drawn.
 *
 *
 *   Inputs:
 *   ------
 *     pbDataPtr = Points to the instance's 'data' structure; this 
 *                 contains the color and font information.
 * 
 *   Outputs:
 *   -------
 *
 *   Procedures Called
 *   -----------------
 *   _XrInitEditorGCs()  [gcUtil.c]
 *   _XrCopyGC()         [gcUtil.c]
 *   _XrChangeGC()       [gcUtil.c]
 *
 *************************************<->***********************************/

static
INT32
pbSetUpGCs (pbDataPtr)

   register xrPushButtonData * pbDataPtr;

{
   INT32                changeList[21];
   INT32                changeMask;

   /* Initialize the standard graphic contexts (1 & 2) we will be using */
   _XrInitEditorGCs (pbDataPtr->pbFGColor, pbDataPtr->pbBGColor,
                     pbDataPtr->pbFont.fontInfo->id);

   /* Set the values we need for the border graphics context */
   _XrCopyGC (xrDefaultGC, xrEditorGC3);
   changeList[XrFOREGROUNDVAL] = pbDataPtr->pbFGColor;
   changeList[XrBACKGROUNDVAL] = pbDataPtr->pbBGColor;
   changeList[XrFONTVAL] = pbDataPtr->pbFont.fontInfo->id;
   changeList[XrLINEWIDTHVAL] = pbDataPtr->pbBorder;
   changeList[XrTILEVAL] = pbDataPtr->pbTileId;
   changeList[XrFILLSTYLEVAL] = Tiled;
   changeMask = (XrFOREGROUND | XrBACKGROUND | XrFONT | XrLINEWIDTH |
                 XrTILE | XrFILLSTYLE);
   _XrChangeGC (xrEditorGC3, changeMask, changeList);

   /*
    * If a default button is specified, then we will need an
    * additional graphics context, to handle drawing the double
    * wide button border.
    */
   _XrCopyGC (xrEditorGC3, xrEditorGC4);
   changeList[XrLINEWIDTHVAL] = pbDataPtr->pbBorder << 1;
   _XrChangeGC (xrEditorGC4, XrLINEWIDTH, changeList);

   /*
    * We will need 2 additional graphic contexts, which will be
    * used to draw the label for an insensitive push button.
    */
   _XrCopyGC (xrDefaultGC, xrEditorGC5);
   changeList[XrFOREGROUNDVAL] = 0;
   changeList[XrBACKGROUNDVAL] = AllPlanes;
   changeList[XrFONTVAL] = pbDataPtr->pbFont.fontInfo->id;
   changeList[XrALUVAL] = GXand;
   changeMask = (XrFOREGROUND | XrBACKGROUND | XrFONT | XrALU);
   _XrChangeGC (xrEditorGC5, changeMask, changeList);
   _XrCopyGC (xrDefaultGC, xrEditorGC6);
   changeList[XrFOREGROUNDVAL] = pbDataPtr->pbFGColor;
   changeList[XrBACKGROUNDVAL] = 0;
   changeList[XrFONTVAL] = pbDataPtr->pbFont.fontInfo->id;
   changeList[XrALUVAL] = GXor;
   changeMask = (XrFOREGROUND | XrBACKGROUND | XrFONT | XrALU);
   _XrChangeGC (xrEditorGC6, changeMask, changeList);
}


/*************************************<->*************************************
 *
 *  draw1Button (windowId, fillGC, borderGC, rectPtr)
 *
 *     Window      windowId;
 *     INT32       fillGC;
 *     INT32       borderGC;
 *     RECTANGLE * rectPtr;
 *
 *   Description:
 *   -----------
 *     This routines draws and fills a single push button.  The button
 *     is drawn as a 12 sided polygon.
 *
 *
 *   Inputs:
 *   ------
 *     windowId = Id of window in which button is to be drawn.
 *
 *     fillGC = Graphic context to use to fill the interior of the button.
 *
 *     borderGC = Graphic context to use to draw the button border.
 *
 *     rectPtr = Describes the rectangle in which the button will be drawn.
 * 
 *   Outputs:
 *   -------
 *
 *   Procedures Called
 *   -----------------
 *   _XrPoly()      [polyUtil.c]
 *   _XrFillPoly()  [polyUtil.c]
 *
 *************************************<->***********************************/

static
INT32
draw1Button (windowId, fillGC, borderGC, rectPtr)

            Window      windowId;
            INT32       fillGC;
            INT32       borderGC;
   register RECTANGLE * rectPtr;

{
   INT16 rightEdge, bottom;
   INT16 oneEighthH, threeEighthsH;
   INT16 halfHeight, halfWidth;
   INT32 i;

   /* Calculate our working values */
   rightEdge = rectPtr->x + rectPtr->width - 1;
   bottom = rectPtr->y + rectPtr->height - 1;
   oneEighthH = rectPtr->height >> 3;
   threeEighthsH = (rectPtr->height * 3) >> 3;
   halfHeight = rectPtr->y + (rectPtr->height >> 1);
   halfWidth = (rectPtr->height >> 1);

   /* Fill in the 'flags' field of the polygon structure */
   for (i = 0; i < 13; i++)
      xr_PolyList[i].flags = 0;

   /* Calculate the polygon endpoints */
   xr_PolyList[0].x = rectPtr->x + halfWidth - oneEighthH;
   xr_PolyList[0].y = rectPtr->y;
   xr_PolyList[1].x = rightEdge - halfWidth + oneEighthH;
   xr_PolyList[1].y = rectPtr->y;
   xr_PolyList[2].x = rightEdge - oneEighthH;
   xr_PolyList[2].y = halfHeight - threeEighthsH;
   xr_PolyList[3].x = rightEdge;
   xr_PolyList[3].y = halfHeight - oneEighthH;
   xr_PolyList[4].x = rightEdge;
   xr_PolyList[4].y = halfHeight + oneEighthH;
   xr_PolyList[5].x = rightEdge - oneEighthH;
   xr_PolyList[5].y = halfHeight + threeEighthsH;
   xr_PolyList[6].x = rightEdge - halfWidth + oneEighthH;
   xr_PolyList[6].y = bottom;
   xr_PolyList[7].x = rectPtr->x + halfWidth - oneEighthH;
   xr_PolyList[7].y = bottom;
   xr_PolyList[8].x = rectPtr->x + oneEighthH;
   xr_PolyList[8].y = halfHeight + threeEighthsH;
   xr_PolyList[9].x = rectPtr->x;
   xr_PolyList[9].y = halfHeight + oneEighthH;
   xr_PolyList[10].x = rectPtr->x;
   xr_PolyList[10].y = halfHeight - oneEighthH;
   xr_PolyList[11].x = rectPtr->x + oneEighthH;
   xr_PolyList[11].y = halfHeight - threeEighthsH;
   xr_PolyList[12].x = rectPtr->x + halfWidth - oneEighthH;
   xr_PolyList[12].y = rectPtr->y;

   /* Draw the button */
   _XrFillPoly (windowId, fillGC, 13, xr_PolyList);
   _XrPoly (windowId, borderGC, 13, xr_PolyList);
}
