home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 5 / 05.iso / a / a009 / 5.ddi / SYS.LIF / FRMRUN.PRG < prev    next >
Encoding:
Text File  |  1991-04-14  |  21.5 KB  |  710 lines

  1. /***
  2. *   Frmrun.prg
  3. *   Clipper 5.0 REPORT FORM runtime system
  4. *   Copyright (c) 1990 Nantucket Corp.  All rights reserved.
  5. *
  6. *   Compile: /n/w/m
  7. */
  8.  
  9.  
  10. #include "frmdef.ch"
  11. #include "error.ch"
  12.  
  13. STATIC aReportData, nPageNumber, nLinesLeft, aReportTotals
  14. STATIC aGroupTotals, lFirstPass, lFormFeeds, nMaxLinesAvail
  15.  
  16. FUNCTION __ReportForm( cFRMName, lPrinter, cAltFile, lNoConsole, bFor, ;
  17.                        bWhile, nNext, nRecord, lRest, lPlain, cHeading, ;
  18.                        lBEject, lSummary )
  19.  
  20.    LOCAL lPrintOn, lConsoleOn // Status of PRINTER and CONSOLE
  21.    LOCAL cExtraFile, lExtraState    // Status of EXTRA
  22.    LOCAL nCol, nGroup
  23.    LOCAL xBreakVal, lBroke := .F.
  24.    LOCAL err
  25.  
  26.    LOCAL lAnyTotals
  27.    LOCAL lAnySubTotals
  28.  
  29.    // Resolve parameters
  30.    IF cFRMName == NIL
  31.       err := ErrorNew()
  32.       err:severity := 2
  33.       err:genCode := EG_ARG
  34.       err:subSystem := "FRMLBL"
  35.       Eval(ErrorBlock(), err)
  36.    ELSE
  37.       IF AT( ".", cFRMName ) == 0
  38.          cFRMName := TRIM( cFRMName ) + ".FRM"
  39.       ENDIF
  40.    ENDIF
  41.  
  42.    IF lPrinter == NIL
  43.       lPrinter     := .F.
  44.    ENDIF
  45.  
  46.    IF cHeading == NIL
  47.       cHeading := ""
  48.    ENDIF
  49.  
  50.    // Set output devices
  51.    lPrintOn   := SET( _SET_PRINTER, lPrinter )
  52.  
  53.    IF lNoConsole
  54.       lConsoleOn := SET( _SET_CONSOLE, .F. )
  55.    ENDIF
  56.  
  57.    IF lPrinter                            // To the printer
  58.       lFormFeeds := .T.
  59.    ELSE
  60.       lFormFeeds := .F.
  61.    ENDIF
  62.  
  63.    IF (!Empty(cAltFile))                // To file
  64.       cExtraFile := SET( _SET_EXTRAFILE, cAltFile )
  65.       lExtraState := SET( _SET_EXTRA, .T. )
  66.    ENDIF
  67.  
  68.  
  69.    BEGIN SEQUENCE
  70.  
  71.       aReportData := __FrmLoad( cFRMName )     // Load the frm into an array
  72.       nMaxLinesAvail := aReportData[RP_LINES]
  73.  
  74.       // Modify aReportData based on the report parameters
  75.       IF lSummary != NIL                   // Set the summary only flag
  76.          aReportData[ RP_SUMMARY ] := lSummary
  77.       ENDIF
  78.       IF lBEject != NIL
  79.          aReportData[ RP_BEJECT ]  := .F.
  80.       ENDIF
  81.       IF lPlain                            // Set plain report flag
  82.          aReportData[ RP_PLAIN ]   := .T.
  83.          cHeading                   := ""
  84.          lFormFeeds                := .F.
  85.       ENDIF
  86.       aReportData[ RP_HEADING ]    := cHeading
  87.  
  88.       // Add to the left margin if a SET MARGIN has been defined
  89.       // NOTE: uncommenting this line will cause REPORT FORM to respect
  90.       // SET MARGIN to screen/to file, but double the margin TO PRINT
  91.       // aReportData[ RP_LMARGIN ] += SET( _SET_MARGIN )
  92.  
  93.       nPageNumber := 1                       // Set the initial page number
  94.       lFirstPass  := .T.                   // Set the first pass flag
  95.  
  96.       nLinesLeft  := aReportData[ RP_LINES ]
  97.  
  98.       // Check to see if a "before report" eject, or TO FILE has been specified
  99.       IF aReportData[ RP_BEJECT ]
  100.          EjectPage()
  101.       ENDIF
  102.  
  103.       // Generate the initial report header manually (in case there are no
  104.       // records that match the report scope)
  105.       ReportHeader()
  106.  
  107.       // Initialize aReportTotals to track both group and report totals, then
  108.       // set the column total elements to 0 if they are to be totaled, otherwise
  109.       // leave them NIL
  110.       aReportTotals := ARRAY( LEN(aReportData[RP_GROUPS]) + 1, ;
  111.                               LEN(aReportData[RP_COLUMNS]) )
  112.  
  113.       // Column total elements
  114.       FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  115.          IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
  116.             FOR nGroup := 1 TO LEN(aReportTotals)
  117.                aReportTotals[nGroup,nCol] := 0
  118.             NEXT
  119.          ENDIF
  120.       NEXT
  121.  
  122.       // Initialize aGroupTotals as an array
  123.       aGroupTotals := ARRAY( LEN(aReportData[RP_GROUPS]) )
  124.  
  125.       // Execute the actual report based on matching records
  126.       DBEval( { || ExecuteReport() }, bFor, bWhile, nNext, nRecord, lRest )
  127.  
  128.       // Generate any totals that may have been identified
  129.       // Make a pass through all the groups
  130.       FOR nGroup := LEN(aReportData[RP_GROUPS]) TO 1 STEP -1
  131.  
  132.  
  133.          // make sure group has subtotals
  134.          lAnySubTotals := .F.
  135.          FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  136.             IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
  137.                 lAnySubTotals := .T.
  138.                 EXIT                    // NOTE
  139.             ENDIF
  140.          NEXT
  141.  
  142.          IF !lAnySubTotals
  143.             LOOP                        // NOTE
  144.          ENDIF
  145.  
  146.  
  147.          // Check to see if we need to eject the page
  148.          IF nLinesLeft < 2
  149.             EjectPage()
  150.             IF aReportData[ RP_PLAIN ]
  151.                nLinesLeft := 1000
  152.             ELSE
  153.                ReportHeader()
  154.             ENDIF
  155.          ENDIF
  156.  
  157.          // Print the first line
  158.          PrintIt( SPACE(aReportData[RP_LMARGIN]) + ;
  159.                IF(nGroup==1,"** Subtotal **","* Subsubtotal *") )
  160.  
  161.          // Print the second line
  162.          QQOUT( SPACE(aReportData[RP_LMARGIN]) )
  163.          FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  164.             IF nCol > 1
  165.                QQOUT( " " )
  166.             ENDIF
  167.             IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
  168.                QQOUT( TRANSFORM(aReportTotals[nGroup+1,nCol], ;
  169.                   aReportData[RP_COLUMNS,nCol,RC_PICT]) )
  170.             ELSE
  171.                QQOUT( SPACE(aReportData[RP_COLUMNS,nCol,RC_WIDTH]) )
  172.             ENDIF
  173.          NEXT
  174.  
  175.          // Send a cr/lf for the last line
  176.          QOUT()
  177.  
  178.       NEXT
  179.  
  180.       // Generate the "Grand totals"
  181.       // Check to see if we need to eject the page
  182.       IF nLinesLeft < 2
  183.          EjectPage()
  184.          IF aReportData[ RP_PLAIN ]
  185.             nLinesLeft := 1000
  186.          ELSE
  187.             ReportHeader()
  188.          ENDIF
  189.       ENDIF
  190.  
  191.  
  192.       // Any report totals?
  193.       lAnyTotals := .F.
  194.       FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  195.          IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
  196.             lAnyTotals := .T.
  197.             EXIT
  198.          ENDIF
  199.       NEXT
  200.  
  201.  
  202.       IF lAnyTotals
  203.  
  204.           // Print the first line
  205.           PrintIt( SPACE(aReportData[RP_LMARGIN]) + "*** Total ***" )
  206.  
  207.           // Print the second line
  208.           QQOUT( SPACE(aReportData[RP_LMARGIN]) )
  209.           FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  210.              IF nCol > 1
  211.                 QQOUT( " " )
  212.              ENDIF
  213.              IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
  214.                 QQOUT( TRANSFORM(aReportTotals[1,nCol], ;
  215.                    aReportData[RP_COLUMNS,nCol,RC_PICT]) )
  216.              ELSE
  217.                 QQOUT( SPACE(aReportData[RP_COLUMNS,nCol,RC_WIDTH]) )
  218.              ENDIF
  219.           NEXT
  220.  
  221.           // Send a cr/lf for the last line
  222.           QOUT()
  223.  
  224.       END
  225.  
  226.  
  227.       // Check to see if an "after report" eject, or TO FILE has been specified
  228.       IF aReportData[ RP_AEJECT ]
  229.          EjectPage()
  230.       ENDIF
  231.  
  232.  
  233.    RECOVER USING xBreakVal
  234.  
  235.       lBroke := .T.
  236.  
  237.    END    // Sequence
  238.  
  239.  
  240.    // Clean up and leave
  241.    aReportData      := NIL               // Recover the space
  242.    aReportTotals  := NIL
  243.    aGroupTotals   := NIL
  244.    nPageNumber      := NIL
  245.    lFirstPass      := NIL
  246.    nLinesLeft      := NIL
  247.    lFormFeeds      := NIL
  248.    nMaxLinesAvail := NIL
  249.  
  250.    // clean up
  251.    SET( _SET_PRINTER, lPrintOn )       // Set the printer back to prior state
  252.    SET( _SET_CONSOLE, lConsoleOn )       // Set the console back to prior state
  253.  
  254.    IF (!Empty(cAltFile))               // Set extrafile back
  255.       SET( _SET_EXTRAFILE, cExtraFile )
  256.       SET( _SET_EXTRA, lExtraState )
  257.    ENDIF
  258.  
  259.    IF lBroke
  260.       // keep the break value going
  261.       BREAK xBreakVal
  262.    END
  263.  
  264.    RETURN NIL
  265.  
  266.  
  267.  
  268. /***
  269. *        ExecuteReport() --> NIL
  270. *        Executed by DBEVAL() for each record that matches record scope
  271. */
  272. STATIC FUNCTION ExecuteReport
  273.    LOCAL aRecordHeader  := {}           // Header for the current record
  274.    LOCAL aRecordToPrint := {}           // Current record to print
  275.    LOCAL nCol                           // Counter for the column work
  276.    LOCAL nGroup                         // Counter for the group work
  277.    LOCAL lGroupChanged  := .F.          // Has any group changed?
  278.    LOCAL nMaxLines                      // Number of lines needed by record
  279.    LOCAL nLine                          // Counter for each record line
  280.    LOCAL cLine                          // Current line of text for parsing
  281.    LOCAL nLastElement                   // Last element pointer if record is
  282.  
  283.    LOCAL lAnySubTotals
  284.                                         // too large for a page
  285.  
  286.    // Add to the main column totals
  287.    FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  288.       IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
  289.          // If this column should be totaled, do it
  290.          aReportTotals[ 1 ,nCol] += ;
  291.                   EVAL( aReportData[RP_COLUMNS,nCol,RC_EXP] )
  292.       ENDIF
  293.    NEXT
  294.  
  295.    // Determine if any of the groups have changed.  If so, add the appropriate
  296.    // line to aRecordHeader for totaling out the previous records
  297.    IF !lFirstPass                       // Don't bother first time through
  298.  
  299.       // Make a pass through all the groups
  300.       FOR nGroup := LEN(aReportData[RP_GROUPS]) TO 1 STEP -1
  301.  
  302.  
  303.          // make sure group has subtotals
  304.          lAnySubTotals := .F.
  305.          FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  306.             IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
  307.                lAnySubTotals := .T.
  308.                EXIT                     // NOTE
  309.             ENDIF
  310.          NEXT
  311.  
  312.          IF !lAnySubTotals
  313.             LOOP                        // NOTE
  314.          ENDIF
  315.  
  316.  
  317.  
  318.          // If this group has changed since the last record
  319.          IF MakeAStr(EVAL(aReportData[RP_GROUPS,nGroup,RG_EXP]),;
  320.                aReportData[RP_GROUPS,nGroup,RG_TYPE]) != aGroupTotals[nGroup]
  321.             AADD( aRecordHeader, IF(nGroup==1,"** Subtotal **","* Subsubtotal *") )
  322.             AADD( aRecordHeader, "" )
  323.  
  324.             // Cycle through the columns, adding either the group
  325.             // amount from aReportTotals or spaces wide enough for
  326.             // the non-totaled columns
  327.             FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  328.                IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
  329.                   aRecordHeader[ LEN(aRecordHeader) ] += ;
  330.                      TRANSFORM(aReportTotals[nGroup+1,nCol], ;
  331.                      aReportData[RP_COLUMNS,nCol,RC_PICT])
  332.                   // Zero out the group totals column from aReportTotals
  333.                   aReportTotals[nGroup+1,nCol] := 0
  334.                ELSE
  335.                   aRecordHeader[ LEN(aRecordHeader) ] += ;
  336.                         SPACE(aReportData[RP_COLUMNS,nCol,RC_WIDTH])
  337.                ENDIF
  338.                aRecordHeader[ LEN(aRecordHeader) ] += " "
  339.             NEXT
  340.             // Get rid of the extra space from the last column
  341.             aRecordHeader[LEN(aRecordHeader)] := ;
  342.                   LEFT( aRecordHeader[LEN(aRecordHeader)], ;
  343.                   LEN(aRecordHeader[LEN(aRecordHeader)]) - 1 )
  344.          ENDIF
  345.       NEXT
  346.  
  347.    ENDIF
  348.  
  349.    lFirstPass = .F.
  350.  
  351.    // Add to aRecordHeader in the event that the group has changed and
  352.    // new group headers need to be generated
  353.  
  354.    // Cycle through the groups
  355.    FOR nGroup := 1 TO LEN(aReportData[RP_GROUPS])
  356.       // If the group has changed
  357.       IF MakeAStr(EVAL(aReportData[RP_GROUPS,nGroup,RG_EXP]),;
  358.             aReportData[RP_GROUPS,nGroup,RG_TYPE]) == aGroupTotals[nGroup]
  359.       ELSE
  360.          AADD( aRecordHeader, "" )   // The blank line
  361.          AADD( aRecordHeader, IF(nGroup==1,"** ","* ") +;
  362.                aReportData[RP_GROUPS,nGroup,RG_HEADER] + " " +;
  363.                MakeAStr(EVAL(aReportData[RP_GROUPS,nGroup,RG_EXP]), ;
  364.                aReportData[RP_GROUPS,nGroup,RG_TYPE]) )
  365.       ENDIF
  366.    NEXT
  367.  
  368.    // Is there anything in the record header?
  369.    IF LEN( aRecordHeader ) > 0
  370.       // Determine if aRecordHeader will fit on the current page.  If not,
  371.       // start a new header
  372.       IF LEN( aRecordHeader ) > nLinesLeft
  373.          EjectPage()
  374.          IF aReportData[ RP_PLAIN ]
  375.             nLinesLeft := 1000
  376.          ELSE
  377.             ReportHeader()
  378.          ENDIF
  379.       ENDIF
  380.  
  381.       // Send aRecordHeader to the output device, resetting nLinesLeft
  382.       AEVAL( aRecordHeader, ;
  383.           { | HeaderLine | ;
  384.               PrintIt( SPACE(aReportData[RP_LMARGIN])+ HeaderLine ) ;
  385.           } ;
  386.       )
  387.       nLinesLeft -= LEN( aRecordHeader )
  388.  
  389.       // Make sure it didn't hit the bottom margin
  390.       IF nLinesLeft == 0
  391.          EjectPage()
  392.          IF aReportData[ RP_PLAIN ]
  393.             nLinesLeft := 1000
  394.          ELSE
  395.             ReportHeader()
  396.          ENDIF
  397.       ENDIF
  398.    ENDIF
  399.  
  400.    // Add to the group totals
  401.    FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  402.       // If this column should be totaled, do it
  403.       IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
  404.          // Cycle through the groups
  405.          FOR nGroup := 1 TO LEN( aReportTotals ) - 1
  406.             aReportTotals[nGroup+1,nCol] += ;
  407.                EVAL( aReportData[RP_COLUMNS,nCol,RC_EXP] )
  408.          NEXT
  409.       ENDIF
  410.    NEXT
  411.  
  412.    // Reset the group expressions in aGroupTotals
  413.    FOR nGroup := 1 TO LEN(aReportData[RP_GROUPS])
  414.       aGroupTotals[nGroup] := MakeAStr(EVAL(aReportData[RP_GROUPS,nGroup,RG_EXP]),;
  415.                                     aReportData[RP_GROUPS,nGroup,RG_TYPE])
  416.    NEXT
  417.  
  418.    // Only run through the record detail if this is NOT a summary report
  419.    IF !aReportData[ RP_SUMMARY ]
  420.       // Determine the max number of lines needed by each expression
  421.       nMaxLines := 1    // $BH 2/14/91
  422.       FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  423.          IF aReportData[RP_COLUMNS,nCol,RC_TYPE] $ "CM"
  424.             nMaxLines := MAX(XMLCOUNT(EVAL(aReportData[RP_COLUMNS,nCol,RC_EXP]),;
  425.                          aReportData[RP_COLUMNS,nCol,RC_WIDTH]), nMaxLines)
  426.          ENDIF
  427.       NEXT
  428.  
  429.       // Size aRecordToPrint to the maximum number of lines it will need, then
  430.       // fill it with nulls
  431.       ASIZE( aRecordToPrint, nMaxLines )
  432.       AFILL( aRecordToPrint, "" )
  433.  
  434.       // Load the current record into aRecordToPrint
  435.       FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
  436.          FOR nLine := 1 TO nMaxLines
  437.             // Check to see if it's a memo or character
  438.             IF aReportData[RP_COLUMNS,nCol,RC_TYPE] $ "CM"
  439.                // Load the current line of the current column into cLine
  440.                cLine := XMEMOLINE(TRIM(EVAL(aReportData[RP_COLUMNS,nCol,RC_EXP])),;
  441.                              aReportData[RP_COLUMNS,nCol,RC_WIDTH], nLine )
  442.                cLine := PADR( cLine, aReportData[RP_COLUMNS,nCol,RC_WIDTH] )
  443.             ELSE
  444.                IF nLine == 1
  445.                   cLine := TRANSFORM(EVAL(aReportData[RP_COLUMNS,nCol,RC_EXP]),;
  446.                            aReportData[RP_COLUMNS,nCol,RC_PICT])
  447.                   cLine := PADR( cLine, aReportData[RP_COLUMNS,nCol,RC_WIDTH] )
  448.                ELSE
  449.                   cLine := SPACE( aReportData[RP_COLUMNS,nCol,RC_WIDTH])
  450.                ENDIF
  451.             ENDIF
  452.             // Add it to the existing report line
  453.             IF nCol > 1
  454.                aRecordToPrint[ nLine ] += " "
  455.             ENDIF
  456.             aRecordToPrint[ nLine ] += cLine
  457.          NEXT
  458.       NEXT
  459.  
  460.       // Determine if aRecordToPrint will fit on the current page
  461.       IF LEN( aRecordToPrint ) > nLinesLeft
  462.          // The record will not fit on the current page - will it fit on
  463.          // a full page?  If not, break it up and print it.
  464.          IF LEN( aRecordToPrint ) > nMaxLinesAvail
  465.             // This record is HUGE!  Break it up...
  466.             nLine := 1
  467.             DO WHILE nLine < LEN( aRecordToPrint )
  468.                PrintIt( SPACE(aReportData[RP_LMARGIN]) + aRecordToPrint[nLine] )
  469.                nLine++
  470.                nLinesLeft--
  471.                IF nLinesLeft == 0
  472.                   EjectPage()
  473.                   IF aReportData[ RP_PLAIN ]
  474.                      nLinesLeft := 1000
  475.                   ELSE
  476.                      ReportHeader()
  477.                   ENDIF
  478.                ENDIF
  479.             ENDDO
  480.          ELSE
  481.             EjectPage()
  482.             IF aReportData[ RP_PLAIN ]
  483.                nLinesLeft := 1000
  484.             ELSE
  485.                ReportHeader()
  486.             ENDIF
  487.             AEVAL( aRecordToPrint, ;
  488.                { | RecordLine | ;
  489.                  PrintIt( SPACE(aReportData[RP_LMARGIN])+ RecordLine ) ;
  490.                } ;
  491.             )
  492.             nLinesLeft -= LEN( aRecordToPrint )
  493.          ENDIF
  494.       ELSE
  495.          // Send aRecordToPrint to the output device, resetting nLinesLeft
  496.          AEVAL( aRecordToPrint, ;
  497.             { | RecordLine | ;
  498.               PrintIt( SPACE(aReportData[RP_LMARGIN])+ RecordLine ) ;
  499.             } ;
  500.          )
  501.          nLinesLeft -= LEN( aRecordToPrint )
  502.       ENDIF
  503.  
  504.       // Make sure it didn't hit the bottom margin
  505.       IF nLinesLeft == 0
  506.          EjectPage()
  507.          IF aReportData[ RP_PLAIN ]
  508.             nLinesLeft := 1000
  509.          ELSE
  510.             ReportHeader()
  511.          ENDIF
  512.       ENDIF
  513.  
  514.       // Tack on the spacing for double/triple/etc.
  515.       IF aReportData[ RP_SPACING ] > 1
  516.          IF nLinesLeft > aReportData[ RP_SPACING ] - 1
  517.             FOR nLine := 2 TO aReportData[ RP_SPACING ]
  518.                PrintIt()
  519.                nLinesLeft--
  520.             NEXT
  521.          ENDIF
  522.       ENDIF
  523.  
  524.    ENDIF    // Was this a summary report?
  525.  
  526.    RETURN NIL
  527.  
  528.  
  529. STATIC FUNCTION ReportHeader
  530.    LOCAL nLinesInHeader := 0
  531.    LOCAL aPageHeader    := {}
  532.    LOCAL nHeadingLength := aReportData[RP_WIDTH] - aReportData[RP_LMARGIN] - 30
  533.    LOCAL nCol, nLine, nMaxColLength, nGroup, cHeader
  534.  
  535.    // Create the header and drop it into aPageHeader
  536.  
  537.    // Start with the heading
  538.    IF !aReportData[ RP_PLAIN ]           // If not a plain paper report, build
  539.       IF aReportData[RP_HEADING] == ""   // the heading
  540.          AADD( aPageHeader, "Page No." + STR(nPageNumber,6) )
  541.       ELSE
  542.          nLinesInHeader := XMLCOUNT( aReportData[RP_HEADING], nHeadingLength )
  543.          FOR nLine := 1 TO nLinesInHeader
  544.             AADD( aPageHeader, SPACE(15) + ;
  545.             PADC(TRIM(XMEMOLINE(aReportData[RP_HEADING],nHeadingLength,nLine)),;
  546.                 nHeadingLength))
  547.          NEXT
  548.          aPageHeader[ 1 ] := STUFF( aPageHeader[ 1 ], 1, 14, ;
  549.                                     "Page No." + STR(nPageNumber,6) )
  550.       ENDIF
  551.       AADD( aPageHeader, DTOC(DATE()) )
  552.    ENDIF
  553.  
  554.    // Tack on the actual header from the FRM
  555.    FOR nLine := 1 TO LEN( aReportData[RP_HEADER] )
  556.         AADD( aPageHeader, ;
  557.                 SPACE( ( aReportData[RP_WIDTH] - aReportData[RP_LMARGIN] - ;
  558.                          aReportData[RP_RMARGIN] - ;
  559.                          Len( aReportData[RP_HEADER,nLine] ) ) / 2 ) + ;
  560.                          aReportData[RP_HEADER,nLine] )
  561.    NEXT
  562.  
  563.    // S87 compat.
  564.    AADD( aPageHeader, "" )
  565.  
  566.    // Now add the column headings
  567.    nLinesInHeader := LEN( aPageHeader )
  568.  
  569.    // Determine the longest column header
  570.    nMaxColLength := 0
  571.    FOR nCol := 1 TO LEN( aReportData[ RP_COLUMNS ] )
  572.        nMaxColLength := MAX( LEN(aReportData[RP_COLUMNS,nCol,RC_HEADER]), ;
  573.                              nMaxColLength )
  574.    NEXT
  575.    FOR nCol := 1 TO LEN( aReportData[ RP_COLUMNS ] )
  576.       ASIZE( aReportData[RP_COLUMNS,nCol,RC_HEADER], nMaxColLength )
  577.    NEXT
  578.  
  579.    FOR nLine := 1 TO nMaxColLength
  580.       AADD( aPageHeader, "" )
  581.    NEXT
  582.  
  583.    FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])    // Cycle through the columns
  584.       FOR nLine := 1 TO nMaxColLength
  585.          IF nCol > 1
  586.             aPageHeader[ nLinesInHeader + nLine ] += " "
  587.          ENDIF
  588.          IF aReportData[RP_COLUMNS,nCol,RC_HEADER,nLine] == NIL
  589.             aPageHeader[ nLinesInHeader + nLine ] += ;
  590.                            SPACE( aReportData[RP_COLUMNS,nCol,RC_WIDTH] )
  591.          ELSE
  592.             IF aReportData[RP_COLUMNS,nCol,RC_TYPE] == "N"
  593.                aPageHeader[ nLinesInHeader + nLine ] += ;
  594.                            PADL(aReportData[RP_COLUMNS,nCol,RC_HEADER,nLine],;
  595.                            aReportData[RP_COLUMNS,nCol,RC_WIDTH])
  596.             ELSE
  597.                aPageHeader[ nLinesInHeader + nLine ] += ;
  598.                            PADR(aReportData[RP_COLUMNS,nCol,RC_HEADER,nLine],;
  599.                            aReportData[RP_COLUMNS,nCol,RC_WIDTH])
  600.             ENDIF
  601.          ENDIF
  602.       NEXT
  603.    NEXT
  604.  
  605.    // Insert the two blank lines between the heading and the actual data
  606.    AADD( aPageHeader, "" )
  607.    AADD( aPageHeader, "" )
  608.  
  609.    // Send it to the output device
  610.    IF lFirstPass
  611.       QOUT()
  612.    ENDIF
  613.  
  614.    AEVAL( aPageHeader, ;
  615.       { | HeaderLine | ;
  616.          PrintIt( SPACE(aReportData[RP_LMARGIN])+ HeaderLine ) ;
  617.       } ;
  618.    )
  619.  
  620.    // Set the page number and number of available lines
  621.    nPageNumber++
  622.    nLinesLeft := aReportData[RP_LINES] - LEN( aPageHeader )
  623.    nMaxLinesAvail := aReportData[RP_LINES] - LEN( aPageHeader )
  624.  
  625.    RETURN NIL
  626.  
  627. /***
  628. *        MakeStr( <exp>, <cType> ) --> value
  629. *       Convert a value of any data type into string to add to the group header 
  630. */
  631. STATIC FUNCTION MakeAStr( uVar, cType )
  632.    LOCAL cString
  633.    DO CASE
  634.    CASE UPPER(cType) == "D"
  635.       cString := DTOC( uVar )
  636.  
  637.    CASE UPPER(cType) == "L"
  638.       cString := IF( uVar, "T", "F" )
  639.  
  640.    CASE UPPER(cType) == "N"
  641.       cString := STR( uVar )
  642.  
  643.    CASE UPPER(cType) == "C"
  644.       cString := uVar
  645.  
  646.    OTHERWISE
  647.       cString := "INVALID EXPRESSION"
  648.    ENDCASE
  649.    RETURN( cString )
  650.  
  651. /***
  652. *        PrintIt( <cString> ) --> NIL
  653. *        Print a string, THEN send a CRLF
  654. */
  655. STATIC FUNCTION PrintIt( cString )
  656.  
  657.    IF cString == NIL
  658.       cString := ""
  659.    ELSE
  660.       // prevents output of trailing space, also prevents wrapping of some
  661.       // lines when sent to screen or 80-column printer. Comment out this
  662.       // line for complete Summer 87 compatibility.
  663.       // cString := Trim( cString )
  664.    ENDIF
  665.  
  666.    QQOUT( cString )
  667.    QOUT()
  668.  
  669.    RETURN NIL
  670.  
  671. /***
  672. *        EjectPage() --> NIL
  673. *         Eject a page if the form feed option is set
  674. */
  675. STATIC FUNCTION EjectPage
  676.    IF lFormFeeds
  677.       EJECT
  678.    ENDIF
  679.    RETURN NIL
  680.  
  681.  
  682. STATIC FUNCTION XMLCOUNT( cString, nLineLength, nTabSize, lWrap )
  683.  
  684.    // Set defaults if none specified
  685.    nLineLength := IF( nLineLength == NIL, 79, nLineLength )
  686.    nTabSize := IF( nTabSize == NIL, 4, nTabSize )
  687.    lWrap := IF( lWrap == NIL, .T., .F. )
  688.  
  689.    IF nTabSize >= nLineLength
  690.       nTabSize := nLineLength - 1
  691.    ENDIF
  692.  
  693.    RETURN( MLCOUNT( TRIM(cString), nLineLength, nTabSize, lWrap ) )
  694.  
  695.  
  696. STATIC FUNCTION XMEMOLINE( cString, nLineLength, nLineNumber, nTabSize, lWrap )
  697.  
  698.    // Set defaults if none specified
  699.    nLineLength := IF( nLineLength == NIL, 79, nLineLength )
  700.    nLineNumber := IF( nLineNumber == NIL, 1, nLineNumber )
  701.    nTabSize := IF( nTabSize == NIL, 4, nTabSize )
  702.    lWrap := IF( lWrap == NIL, .T., lWrap )
  703.  
  704.    IF nTabSize >= nLineLength
  705.       nTabSize := nLineLength - 1
  706.    ENDIF
  707.  
  708.    RETURN( MEMOLINE( cString, nLineLength, nLineNumber, nTabSize, lWrap ) )
  709.  
  710.