Exemple #1
0
void CStartupView::DrawTitleText(Gdiplus::Graphics &graphics)
{
   Gdiplus::Rect gdipRcLeftButtons;
   CalculateLeftButtonsRegion(gdipRcLeftButtons);

   Gdiplus::SolidBrush blackBrush(Gdiplus::Color(255, 0, 0, 0));

   Gdiplus::Font titleFont(m_csFontFamily, m_iTitleFontSize, 
      Gdiplus::FontStyleBold, Gdiplus::UnitPixel);
   Gdiplus::Font subTitleFont(m_csFontFamily, m_iNormalFontSize, 
      Gdiplus::FontStyleBold, Gdiplus::UnitPixel);
   CString csRecord1;
   csRecord1.LoadString(IDS_RECORD);
   INT x = gdipRcLeftButtons.X;
   INT y = gdipRcLeftButtons.Y + 2*BUTTON_TITLE_HEIGHT;
   Gdiplus::PointF textOrigin(x/*gdipRcLeftButtons.X*/, y/*(gdipRcLeftButtons.Y + gdipRcLeftButtons.Height)*/ );
   graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit);
   graphics.DrawString(csRecord1, csRecord1.GetLength(), &titleFont, textOrigin, &blackBrush);
   
   x = gdipRcLeftButtons.X;
   y = gdipRcLeftButtons.Y + BUTTON_TITLE_HEIGHT + 2*BUTTON_SUBTITLE_HEIGHT + 2*(BUTTON_HEIGHT1 + 10) + SEPARATOR_HEIGHT + 5;
   //Gdiplus::PointF textOrigin(x/*gdipRcLeftButtons.X*/, y/*(gdipRcLeftButtons.Y + gdipRcLeftButtons.Height)*/ );
   textOrigin.X = x;
   textOrigin.Y = y;

   CString csWelcome;
   //csWelcome.LoadStringW(IDS_WELCOME);
   csWelcome.LoadStringW(ID_MENU_NEW);
   graphics.DrawString(csWelcome, csWelcome.GetLength(), &titleFont, textOrigin, &blackBrush); 

   textOrigin.Y += BUTTON_TITLE_HEIGHT;
   

   Gdiplus::Rect gdipRcRightButtons;
   CalculateRightButtonsRegion(gdipRcRightButtons);

   textOrigin.X = gdipRcRightButtons.X ;
   textOrigin.Y = gdipRcRightButtons.Y + BUTTON_TITLE_HEIGHT + BUTTON_HEIGHT;;
   CString csRecord;
   //csRecord.LoadStringW(IDS_RECORD);
   csRecord.LoadStringW(ID_MENU_OPEN);
   graphics.DrawString(csRecord, csRecord.GetLength(), &titleFont, textOrigin, &blackBrush); 

   /*textOrigin.X = gdipRcRightButtons.X ;
   textOrigin.Y += 3*BUTTON_HEIGHT;

   CString csRecent;
   csRecent.LoadStringW(IDS_RECENTLY_OPENED_PROJECTS);
   graphics.DrawString(csRecent, csRecent.GetLength(), &titleFont, textOrigin, &blackBrush); */

}
void BpDocument::composeGraphBasics( Graph *g,
    bool isLineGraph, EqVar *xVar, EqVar *yVar, EqVar *zVar,
    int curves, GraphAxleParms *xParms, GraphAxleParms *yParms )
{
    //--------------------------------------------------------------------------
    // Set the logical fonts and colors here
    //--------------------------------------------------------------------------

    // Axis text fonts.
    QFont  textFont( property()->string( "graphTextFontFamily" ),
                    property()->integer( "graphTextFontSize" ) );
    QPen   textPen( property()->color( "graphTextFontColor" ) );

    QFont  axleFont( textFont );
    QColor axleColor( property()->color( "graphAxleColor" ) );
    QPen   axlePen( axleColor,
                property()->integer( "graphAxleWidth" ), Qt::SolidLine );
    QPen   gridPen( property()->color( "graphGridColor" ),
                property()->integer( "graphGridWidth" ), Qt::SolidLine );

    // Title fonts and colors.
    QFont  subTitleFont( property()->string( "graphSubtitleFontFamily" ),
                    property()->integer( "graphSubtitleFontSize" ) );
    QColor subTitleColor( property()->color( "graphSubtitleFontColor" ) );

    QFont  titleFont( property()->string( "graphTitleFontFamily" ),
                    property()->integer( "graphTitleFontSize" ) );
    QColor titleColor( property()->color( "graphTitleFontColor" ) );

    // Background and canvas colors.
    QBrush worldBg( property()->color( "graphBackgroundColor" ),
                Qt::SolidPattern );
    QBrush canvasBg( "white", SolidPattern );
    QPen   canvasBox( "black", 1, NoPen );

    //--------------------------------------------------------------------------
    // Create the graph canvas, world, and decoration.
    //--------------------------------------------------------------------------

    // Get the x- and y-axle parameters
    double xValMin    = xParms->m_axleMin;
    double xValMax    = xParms->m_axleMax;
    double xMajorStep = xParms->m_majorStep;
    int    xDec       = xParms->m_decimals;

    double yValMin    = yParms->m_axleMin;
    double yValMax    = yParms->m_axleMax;
    double yMajorStep = yParms->m_majorStep;
    int    yDec       = yParms->m_decimals;

    // Graph title and subtitle.
    QString qStr = m_eqTree->m_eqCalc->docDescriptionStore().stripWhiteSpace();
    //if ( qStr.isNull() || qStr.isEmpty() )
    //{
    //  translate( qStr, "BpDocument:NoRunDescription" );
    //}
    g->setTitle( qStr, titleFont, titleColor );

    if ( property()->boolean( "graphTitleActive" ) )
    {
        QString text("");
        translate( text, "BpDocument:Graphs:By" );
        qStr = *(yVar->m_label) + "\n" + text + " " + *(xVar->m_label);
        if ( curves > 1 && isLineGraph && zVar )
        {
            translate( text, "BpDocument:Graphs:And" );
            qStr += "\n" + text + " " + *(zVar->m_label);
        }
        g->setSubTitle( qStr, subTitleFont, subTitleColor );
    }

    // Use portrait orientation (assuming screen output, not printer).
    g->setCanvasRotation( 0.0 );
    g->setCanvasScale( 1.0, 1.0 );
    // NOTE: to change the graph location or size,
    // we must manipulate the m_composer->graph() args!
    g->setCanvas(
        m_screenSize->m_marginLeft,     // upper left X pixel
        m_screenSize->m_marginTop,      // upper left Y pixel
        m_screenSize->m_bodyWd,         // graph canvas width in pixels
        m_screenSize->m_bodyHt,         // graph canvas height in pixels
        0                               // graph border in pixels
    );

    // Canvas background and box.
    g->setCanvasBg( canvasBg );
    g->setCanvasBox( canvasBox );

    // Make the world coordinate system 20% larger than the data range.
    g->setWorld(
        xValMin - 0.2 * (xValMax-xValMin),  // canvas WORLD left coordinate
        yValMin - 0.2 * (yValMax-yValMin),  // canvas WORLD bottom coordinate
        xValMax + 0.1 * (xValMax-xValMin),  // canvas WORLD right coordinate
        yValMax + 0.1 * (yValMax-yValMin)   // canvas WORLD top coordinate
    );

    // Provide a colored background for the graph area.
    g->setWorldBg(
        xValMin,                        // colored bg WORLD left coordinate
        yValMin,                        // colored bg WORLD bottom coordinate
        xValMax,                        // colored bg WORLD right coordinate
        yValMax,                        // colored bg WORLD top coordinate
        worldBg                         // brush color and pattern
    );

    //--------------------------------------------------------------------------
    // Create a bottom axle with endpoints in WORLD coordinates (not AXLE).
    // This has all elements of an axle EXCEPT a minor grid and subtitle.
    //--------------------------------------------------------------------------

    GraphAxle *b = g->addGraphAxle(
        xValMin,                        // axle's min scale (NOT WORLD) value
        xValMax,                        // axle's max scale (NOT WORLD) value
        axlePen                         // axle line pen (color, width, style)
    );

    b->setWorld(
        xValMin,                        // axle's start WORLD x coordinate
        yValMin,                        // axle's start WORLD y coordinate
        xValMax,                        // axle's end WORLD x coordinate
        yValMin                         // axle's end WORLD y coordinate
    );

    // Line graph bottom axles need labels and tic marks.
    if ( isLineGraph )
    {
        b->setMajorLabels(
            axleFont,                   // label font
            axleColor,                  // font color
            xDec                        // decimal places (-1 mean no label)
        );

        b->setMajorTics(
            GraphAxleBottom,                 // side of axle to draw tic marks
            xValMin,                    // first tic mark location in AXLE units
            xValMax,                    // last tic mark location in AXLE units
            xMajorStep,                 // tic spacing in AXLE units
            0.05 * (yValMax-yValMin),   // tic mark length in Y WORLD units
            axlePen                     // tic pen (color, width, style)
        );

        b->setMinorTics(
            GraphAxleBottom,                 // side of axle to draw tic marks
            xValMin,                    // first tic mark location in AXLE units
            xValMax,                    // last tic mark location in AXLE units
            0.50 * xMajorStep,          // tic spacing in AXLE units
            0.03 * (yValMax-yValMin),   // tic mark length in Y WORLD units
            axlePen                     // tic pen (color, width, style)
        );

        b->setMajorGrid(
            gridPen,                    // grid pen color, width, style
            (yValMax-yValMin)           // length in Y WORLD units !!
        );

        // Don't show the units for ratio variables.
        qStr = *(xVar->m_label) + " " + xVar->displayUnits(true);
        b->setTitle(
            qStr,                       // axle title string
            GraphAxleBottom,                 // axle side to write the string
            axleFont,                   // axle title font
            axleColor                   // axle title color
        );
    }
    // Bar graphs just need the title.
    else
    {
        b->setTitle(
            *(xVar->m_label),           // axle title string
            GraphAxleBottom,                 // axle side to write the string
            axleFont,                   // axle title font
            axleColor                   // axle title color
        );
    }

    //--------------------------------------------------------------------------
    // Create a top axle with endpoints in WORLD coordinates (not AXLE)
    // Same as bottom axle EXCEPT no tic labels or axle label
    //--------------------------------------------------------------------------

    // Line graph top axles need tic marks.
    if ( isLineGraph )
    {
        GraphAxle *t = g->addGraphAxle(
            xValMin,                    // axle's min scale (NOT WORLD) value
            xValMax,                    // axle's max scale (NOT WORLD) value
            axlePen                     // axle line pen (color, width, style)
        );

        t->setWorld(
            xValMin,                    // axle's start WORLD x coordinate
            yValMax,                    // axle's start WORLD y coordinate
            xValMax,                    // axle's end WORLD x coordinate
            yValMax                     // axle's end WORLD y coordinate
        );

        if ( false )
        t->setMajorTics(
            GraphAxleTop,                    // side of axle to draw tic marks
            xValMin,                    // first tic mark location in AXLE units
            xValMax,                    // last tic mark location in AXLE units
            xMajorStep,                 // tic spacing in AXLE units
            0.05 * (yValMax-yValMin),   // tic mark length in WORLD units
            axlePen                     // tic pen (color, width, style)
        );

        if ( false )
        t->setMinorTics(
            GraphAxleTop,                    // side of axle to draw tic marks
            xValMin,                    // first tic mark location in AXLE units
            xValMax,                    // last tic mark location in AXLE units
            0.50 * xMajorStep,          // tic spacing in AXLE units
            0.03 * (yValMax-yValMin),   // tic mark length in WORLD units
            axlePen                     // tic pen (color, width, style)
        );
    }

    //--------------------------------------------------------------------------
    // Create a left axle with endpoints in WORLD coordinates (not AXLE)
    // This has all elements of an axle EXCEPT a minor grid and subtitle
    //--------------------------------------------------------------------------

    GraphAxle *l = g->addGraphAxle(
        yValMin,                        // axle's min scale (NOT WORLD) value
        yValMax,                        // axle's max scale (NOT WORLD) value
        axlePen                         // axle line pen (color, width, style)
    );

    l->setWorld(
        xValMin,                        // axle's start WORLD x coordinate
        yValMin,                        // axle's start WORLD y coordinate
        xValMin,                        // axle's end WORLD x coordinate
        yValMax                         // axle's end WORLD y coordinate
    );

    l->setMajorLabels(
        axleFont,                       // label font
        axleColor,                      // font color
        yDec                            // decimal places (-1 mean no label)
    );

    l->setMajorTics(
        GraphAxleLeft,                       // side of axle to draw tic marks
        yValMin,                        // first tic mark location in AXLE units
        yValMax,                        // last tic mark location in AXLE units
        yMajorStep,                     // tic spacing in AXLE units
        0.04 * (xValMax-xValMin),       // tic mark length in X WORLD units
        axlePen                         // tic pen (color, width, style)
    );

    l->setMinorTics(
        GraphAxleLeft,                       // side of axle to draw tic marks
        yValMin,                        // first tic mark location in AXLE units
        yValMax,                        // last tic mark location in AXLE units
        0.50 * yMajorStep,              // tic spacing in AXLE units
        0.02 * (xValMax-xValMin),       // tic mark length in X WORLD units
        axlePen                         // tic pen (color, width, style)
    );

    l->setMajorGrid(
        gridPen,                        // grid pen color, width, style
        (xValMax-xValMin)               // length in X axis WORLD units !!
    );

    // Don't show the units for fraction or ratio variables.
    qStr = *(yVar->m_label) + " " + yVar->displayUnits(true);
    l->setTitle(
        qStr,                           // axle title string
        GraphAxleLeft,                  // axle side to write the string
        axleFont,                       // axle title font
        axleColor                       // axle title font color
    );

    //--------------------------------------------------------------------------
    // Create a right axle with endpoints in WORLD coordinates (not AXLE)
    // Same as the left axle EXCEPT no tic labels or axle label.
    //--------------------------------------------------------------------------

    GraphAxle *r = g->addGraphAxle(
        yValMin,                        // axle's min scale (NOT WORLD) value
        yValMax,                        // axle's max scale (NOT WORLD) value
        axlePen                         // axle line pen (color, width, style)
    );

    r->setWorld(
        xValMax,                        // axle's start WORLD x coordinate
        yValMin,                        // axle's start WORLD y coordinate
        xValMax,                        // axle's end WORLD x coordinate
        yValMax                         // axle's end WORLD y coordinate
    );

    if ( false )
    r->setMajorTics(
        GraphAxleRight,                      // side of axle to draw tic marks
        yValMin,                        // first tic mark location in AXLE units
        yValMax,                        // last tic mark location in AXLE units
        yMajorStep,                     // tic spacing in AXLE units
        0.04 * (xValMax-xValMin),       // tic mark length in X WORLD units
        axlePen                         // tic pen (color, width, style)
    );

    if ( false )
    r->setMinorTics(
        GraphAxleLeft,                       // side of axle to draw tic marks
        yValMin,                        // first tic mark location in AXLE units
        yValMax,                        // last tic mark location in AXLE units
        0.50 * yMajorStep,              // tic spacing in AXLE units
        0.02 * (xValMax-xValMin),       // tic mark length in X WORLD units
        axlePen                         // tic pen (color, width, style)
    );

    return;
}
void BpDocument::composeTable3( int vid, EqVar *rowVar, EqVar *colVar )
{
    // START THE STANDARD PREAMBLE USED BY ALL TABLE COMPOSITION FUNCTIONS.
    // WIN98 requires that we actually create a font here and use it for
    // font metrics rather than using the widget's font.
    QFont subTitleFont( property()->string( "tableSubtitleFontFamily" ),
                        property()->integer( "tableSubtitleFontSize" ) );
    QPen subTitlePen( property()->color( "tableSubtitleFontColor" ) );
    QFontMetrics subTitleMetrics( subTitleFont );

    QFont textFont( property()->string( "tableTextFontFamily" ),
                    property()->integer( "tableTextFontSize" ) );
    QPen textPen( property()->color( "tableTextFontColor" ) );
    QFontMetrics textMetrics( textFont );

    QFont titleFont( property()->string( "tableTitleFontFamily" ),
                     property()->integer( "tableTitleFontSize" ) );
    QPen titlePen( property()->color( "tableTitleFontColor" ) );
    QFontMetrics titleMetrics( titleFont );

    QFont valueFont( property()->string( "tableValueFontFamily" ),
                     property()->integer( "tableValueFontSize" ) );
    QPen valuePen( property()->color( "tableValueFontColor" ) );
    QFontMetrics valueMetrics( valueFont );

    bool doRowBg = property()->boolean( "tableRowBackgroundColorActive" );
    QBrush rowBrush( property()->color( "tableRowBackgroundColor" ),
                     Qt::SolidPattern );

    QString text("");

    // Store pixel resolution into local variables.
    double yppi  = m_screenSize->m_yppi;
    double xppi  = m_screenSize->m_xppi;
    double m_padWd = m_pageSize->m_padWd;

    // Determine the height of the various display fonts.
    double textHt, titleHt, valueHt, rowHt, x0, x1;
    textHt  = ( textMetrics.lineSpacing()  + m_screenSize->m_padHt ) / yppi;
    titleHt = ( titleMetrics.lineSpacing() + m_screenSize->m_padHt ) / yppi;
    valueHt = ( valueMetrics.lineSpacing() + m_screenSize->m_padHt ) / yppi;
    rowHt   = ( textHt > valueHt )
              ? textHt
              : valueHt;
    // END THE STANDARD PREAMBLE USED BY ALL TABLE COMPOSITION FUNCTIONS.

    // Determine the number of rows we can display on a page.
    int rowsPerPage = (int)
                      ( ( m_pageSize->m_bodyHt - 5. * titleHt - 4. * textHt ) / rowHt );

    // Number of pages the table requires to accomodate all the rows.
    int pagesLong = 1 + (int) ( tableRows() / rowsPerPage );

    // Arrays to hold the output values' column information.
    // Page on which the output value column appears.
    int *colPage = new int[ tableCols() ];
    checkmem( __FILE__, __LINE__, colPage, "int colPage", tableCols() );
    // Horizontal position of each output value column.
    double *colXPos = new double[ tableCols() ];
    checkmem( __FILE__, __LINE__, colXPos, "double colXPos", tableCols() );
    // Column header text.
    QString *colText = new QString[ tableCols() ];
    checkmem( __FILE__, __LINE__, colText, "double colText", tableCols() );

    // Prescription shading?
    bool doRx = property()->boolean( "tableShading" );
    bool doBlank = property()->boolean( "tableShadingBlank" );

    // Determine the row variable's (left-most) column width.
    int row, iid, cell;
    double len;
    QString qStr;
    // Start wide enough to hold the variable name and units.
    double rowWd = m_padWd
                   + ( (double) headerWidth( rowVar, textMetrics ) / xppi );
    // Enlarge it to hold the fattest row value.
    m_rowDecimals = 0;
    for ( row = 0;
            row < tableRows();
            row++ )
    {
        if ( rowVar->isDiscrete() )
        {
            iid = (int) tableRow( row );
            qStr = rowVar->m_itemList->itemName( iid ) + "MMM";
        }
        else if ( rowVar->isContinuous() )
        {
            // CDB DECIMALS MOD
            if ( false )
            {
                qStr.sprintf( "%1.*fMMM", rowVar->m_displayDecimals, tableRow( row ) );
            }
            else
            {
                // Start with 6 decimals for this row value
                int decimals = 6;
                qStr.sprintf( "%1.*f", decimals, tableRow( row ) );
                // Remove all trailing zeros
                while ( qStr.endsWith( "0" ) )
                {
                    qStr = qStr.left( qStr.length()-1 );
                    decimals--;
                }
                // Update m_rowDecimals digits
                m_rowDecimals = ( decimals > m_rowDecimals ) ? decimals : m_rowDecimals;
                qStr.append( "MWM" );
            }
        }
        len = (double) textMetrics.width( qStr ) / xppi;
        if ( len > rowWd )
        {
            rowWd = len;
        }
    }
    // Find the fattest output value for this table variable.
    int col;
    int out = vid;
    EqVar *outVar = tableVar(vid);
    double colWd = 0;
    for ( row = 0;
            row < tableRows();
            row++ )
    {
        for ( col = 0;
                col < tableCols();
                col++ )
        {
            if ( outVar->isDiscrete() )
            {
                iid = (int) tableVal( out );
                qStr = outVar->m_itemList->itemName( iid ) + "WM";
            }
            else if ( outVar->isContinuous() )
            {
                qStr.sprintf( "%1.*fWM",
                              outVar->m_displayDecimals, tableVal( out ) );
            }
            len = (double) textMetrics.width( qStr ) / xppi;
            if ( len > colWd )
            {
                colWd = len;
            }
            out += tableVars();
        }   // Next table column.
    } // Next table row.

    // Set the column header value text.
    m_colDecimals = 0;
    for ( col = 0;
            col < tableCols();
            col++ )
    {
        if ( colVar->isDiscrete() )
        {
            iid = (int) tableCol( col );
            colText[col] = colVar->m_itemList->itemName( iid );
        }
        else if ( colVar->isContinuous() )
        {
            // CDB DECIMALS MOD
            if ( false )
            {
                colText[col].sprintf( " %1.*f",	colVar->m_displayDecimals, tableCol( col ) );
            }
            else
            {
                // Start with 6 decimals for this row value
                int decimals = 6;
                colText[col].sprintf( " %1.*f", decimals, tableCol( col ) );
                // Remove all trailing zeros
                while ( colText[col].endsWith( "0" ) )
                {
                    colText[col] = colText[col].left( colText[col].length()-1 );
                    decimals--;
                }
                // Update Decimals digits
                m_colDecimals = ( decimals > m_colDecimals ) ? decimals : m_colDecimals;
            }
        }
        // Expand the column width to accomodate the header value text?
        len = (double) textMetrics.width( colText[col] ) / xppi;
        if ( len > colWd )
        {
            colWd = len;
        }
    }   // Next table column.
    // CDB DECIMALS MOD
    for ( col = 0;  col < tableCols(); col++ )
    {
        if ( colVar->isContinuous() )
        {
            colText[col].sprintf( " %1.*f", m_colDecimals, tableCol( col ) );
        }
    }
    // Add padding between each column.
    colWd += m_padWd;

    // Determine each column's position on each page.
    int pagesWide = 1;
    // Horizontal position of first column on the first page.
    double xpos = m_pageSize->m_bodyLeft + rowWd + 2. * m_padWd;
    for ( col = 0;
            col < tableCols();
            col++ )
    {
        // If past the right page edge, start a new page.
        if ( xpos + colWd > m_pageSize->m_bodyRight )
        {
            // The table page width has just increased.
            pagesWide++;
            // This will be the first column on the new page.
            xpos = m_pageSize->m_bodyLeft + rowWd + 2. * m_padWd;
        }
        // Store the page and position of this output variable's column.
        colXPos[col] = xpos;
        colPage[col] = pagesWide;
        // Update the position pointer.
        xpos += ( 2. * m_padWd + colWd );
    }

    // Determine the column title width (inches).
    double colTitleWd = textMetrics.width( *(colVar->m_label) ) / xppi;
    if ( ( textMetrics.width( colVar->m_displayUnits ) / xppi ) > colTitleWd )
    {
        colTitleWd = textMetrics.width( colVar->m_displayUnits ) / xppi;
    }
    colTitleWd += ( 2. * m_padWd );

    // Determine an offset shift to center each pageAcross.
    double *shift = new double[ pagesWide + 1 ];
    checkmem( __FILE__, __LINE__, shift, "double shift", pagesWide + 1 );
    for ( col = 0;
            col < tableCols();
            col++ )
    {
        // Table must be at least this wide.
        double minLeft = m_pageSize->m_bodyLeft + rowWd + 2. * m_padWd + colTitleWd;
        // Does it need to be wider to accomodate this column?
        if ( colXPos[col] + colWd > minLeft )
        {
            minLeft = colXPos[col] + colWd;
        }
        // Just the last column of each page finally gets stored in shift[].
        shift[colPage[col]] = 0.5 * ( m_pageSize->m_bodyRight - minLeft );
    }

    // Start drawing the table.
    double yPos, s, bgLeft, bgRight, rightEdge, leftEdge;
    int i;
    // Loop for each page down.
    int thisPage = 1;
    for ( int pageDown = 1;
            pageDown <= pagesLong;
            pageDown++ )
    {
        // Loop for each page across.
        for ( int pageAcross = 1;
                pageAcross <= pagesWide;
                pageAcross++, thisPage++ )
        {
            // Table title indicates the table portion
            translate( text, "BpDocument:Table:PageOf",
                       *(tableVar(vid)->m_label),
                       QString( "%1" ).arg( thisPage ),
                       QString( "%1" ).arg( pagesLong * pagesWide ) );

            // Start a new page
            startNewPage( text, TocTable );
            yPos = m_pageSize->m_marginTop + titleHt;

            // Draw a map of where we are.
            composePageMap( ( 2. * titleHt - 0.1 ), pagesLong, pagesWide,
                            pagesLong, pagesWide, 1, 1, pageDown-1, pageAcross-1 );

            // Display the table title::description
            m_composer->font( titleFont );          // use tableTitleFont
            m_composer->pen( titlePen );            // use tableTitleFontColor
            qStr = m_eqTree->m_eqCalc->docDescriptionStore().stripWhiteSpace();
            m_composer->text(
                m_pageSize->m_marginLeft,   yPos,   // start at UL corner
                m_pageSize->m_bodyWd,       titleHt,// width and height
                Qt::AlignVCenter|Qt::AlignHCenter,  // center alignement
                qStr );                             // display description
            yPos += titleHt;

            // Display the table title::variable
            m_composer->font( subTitleFont );       // use tableSubtitleFont
            m_composer->pen( subTitlePen );         // use tableSubtitleFontColor
            qStr = *(outVar->m_label);
            if ( outVar->isContinuous() )
            {
                qStr = *(outVar->m_label) + " " + outVar->displayUnits(true);
            }
            m_composer->text(
                m_pageSize->m_marginLeft,   yPos,   // start at UL corner
                m_pageSize->m_bodyWd,       titleHt,// width and height
                Qt::AlignVCenter|Qt::AlignHCenter,  // center alignment
                qStr );                             // table variable name
            yPos += titleHt;

            // Display the table title::portion
            //m_composer->text(
            //    m_pageSize->m_marginLeft,   yPos,
            //    m_pageSize->m_bodyWd,       titleHt,
            //    Qt::AlignVCenter|Qt::AlignHCenter,
            //    portion );
            //yPos += titleHt;
            yPos += titleHt;

            // Everything else on this page is shifted
            s = shift[pageAcross];

            // Determine left and right edges of the table.
            leftEdge = -1;
            for ( col = 0;
                    col < tableCols();
                    col++ )
            {
                if ( pageAcross == colPage[col] )
                {
                    rightEdge = colXPos[col] + colWd + s;
                    if ( leftEdge < 0. )
                    {
                        leftEdge = colXPos[col] + s;
                    }
                }
            }
            // Must be at least wide enough to accomodate column header text.
            if ( rightEdge < leftEdge + colTitleWd )
            {
                rightEdge = leftEdge + colTitleWd;
            }
            bgLeft = m_pageSize->m_marginLeft + s - m_padWd ;
            bgRight = rightEdge - leftEdge + rowWd + 4 * m_padWd ;

            // Display a colored row column header background?
            if ( doRowBg )
            {
                m_composer->fill(
                    bgLeft,     yPos,
                    bgRight,    3 * textHt,
                    rowBrush );
            }
            // Display the row column header0.
            m_composer->font( textFont );           // use tableTextFont
            m_composer->pen( textPen );             // use tableTextFontColor
            m_composer->text(
                m_pageSize->m_marginLeft + s,   yPos,
                rowWd,                          textHt,
                Qt::AlignVCenter|Qt::AlignLeft,
                *(rowVar->m_hdr0) );

            // Display the row column header1.
            m_composer->text(
                m_pageSize->m_marginLeft + s,   yPos + textHt,
                rowWd,                          textHt,
                Qt::AlignVCenter|Qt::AlignLeft,
                *(rowVar->m_hdr1) );

            // Display the row column units.
            m_composer->text(
                m_pageSize->m_marginLeft + s,   yPos + 2. * textHt,
                rowWd,                          textHt,
                Qt::AlignVCenter|Qt::AlignLeft,
                rowVar->displayUnits() );
            // Display the row column header underline
            // only if we are not coloring row backgrounds.
            int skipLines = 3;
            if ( ! doRowBg )
            {
                m_composer->line(
                    m_pageSize->m_marginLeft + s,           yPos + 3.5 * textHt,
                    m_pageSize->m_marginLeft + rowWd + s,   yPos + 3.5 * textHt );
                skipLines = 4;
            }
            // Display the output column headers.
            for ( col = 0;
                    col < tableCols();
                    col++ )
            {
                if ( pageAcross == colPage[col] )
                {
                    // Display the output column units.
                    m_composer->text(
                        colXPos[col] + s,   yPos + 2. * textHt,
                        colWd,              textHt,
                        Qt::AlignVCenter|Qt::AlignRight,
                        colText[col] );
                    // Display the output column underline.
                    if ( ! doRowBg )
                    {
                        m_composer->line(
                            colXPos[col] + s,           yPos + 3.5 * textHt,
                            colXPos[col] + colWd + s,   yPos + 3.5 * textHt );
                    }
                }
            }
            // Display a centered column variable name and units header.
            m_composer->text(
                leftEdge,                   yPos,
                ( rightEdge - leftEdge ),   textHt,
                Qt::AlignVCenter|Qt::AlignHCenter,
                *(colVar->m_label) );

            if ( colVar->isContinuous() )
            {
                m_composer->text(
                    leftEdge,                   yPos + textHt,
                    ( rightEdge - leftEdge ),   textHt,
                    Qt::AlignVCenter|Qt::AlignHCenter,
                    colVar->displayUnits() );
            }

            // If there are previous columns, display a visual cue.
            if ( pageAcross > 1 )
            {
                for ( i = 0;
                        i < 3;
                        i++ )
                {
                    m_composer->text(
                        0, ( yPos + i * textHt ),
                        ( m_pageSize->m_marginLeft + s - m_padWd ),   textHt,
                        Qt::AlignVCenter|Qt::AlignRight,
                        "<" );
                }
            }
            // If there are subsequent column pages, display a visual clue.
            if ( pageAcross < pagesWide )
            {
                for ( i = 0;
                        i < 3;
                        i++ )
                {
                    m_composer->text(
                        ( rightEdge + m_padWd ), ( yPos + i * textHt ),
                        ( m_pageSize->m_pageWd - rightEdge ), textHt,
                        Qt::AlignVCenter|Qt::AlignLeft,
                        ">" );
                }
            }
            // Set vertical start of rows.
            yPos += skipLines * textHt;

            // Determine the rows range to display on this page.
            int rowFrom = ( pageDown - 1 ) * rowsPerPage;
            int rowThru = pageDown * rowsPerPage - 1;
            if ( rowThru >= tableRows() )
            {
                rowThru = tableRows() - 1;
            }
            // Determine the columns range to display on this page.
            int colFrom = -1;
            int colThru = 0;
            for ( col = 0;
                    col < tableCols();
                    col++ )
            {
                if ( colPage[col] == pageAcross )
                {
                    if ( colFrom == -1 )
                    {
                        colFrom = col;
                    }
                    colThru = col;
                }
            }

            // Loop for each row on this page.
            bool doThisRowBg = false;
            for ( row = rowFrom;
                    row <= rowThru;
                    row++ )
            {
                // Display a colored row background?
                if ( doRowBg && doThisRowBg )
                {
                    m_composer->fill(
                        bgLeft,     yPos,
                        bgRight,    textHt,
                        rowBrush );
                }
                doThisRowBg = ! doThisRowBg;

                // Left-most (row variable) column value.
                if ( rowVar->isDiscrete() )
                {
                    iid = (int) tableRow( row );
                    qStr = rowVar->m_itemList->itemName( iid );
                }
                else if ( rowVar->isContinuous() )
                {
                    // CDB DECIMALS MOD
                    if ( false )
                    {
                        qStr.sprintf( "%1.*f", rowVar->m_displayDecimals, tableRow( row ) );
                    }
                    else
                    {
                        qStr.sprintf( "%1.*f", m_rowDecimals, tableRow( row ) );
                    }
                }
                m_composer->font( textFont );       // use tableTextFont
                m_composer->pen( textPen );         // use tableTextFontColor
                m_composer->text(
                    m_pageSize->m_marginLeft + s,   yPos,
                    rowWd,                          textHt,
                    Qt::AlignVCenter|Qt::AlignLeft,
                    qStr );

                // Loop for each column value on this page.
                m_composer->font( valueFont );      // use tableValueFont
                m_composer->pen( valuePen );        // use tableValueFontColor
                out = vid
                      + colFrom * tableVars()
                      + row * tableCols() * tableVars();
                for ( col = colFrom;
                        col <= colThru;
                        col++ )
                {
                    // Determine whether to hatch this cell
                    cell = col + row * tableCols();
                    bool hatch = doRx && ! tableInRx( cell );

                    // Discrete variables use their item name.
                    if ( outVar->isDiscrete() )
                    {
                        iid = (int) tableVal( out );
                        qStr = outVar->m_itemList->itemName( iid );
                    }
                    // Continuous variables use the current display units format.
                    else if ( outVar->isContinuous() )
                    {
                        qStr.sprintf( " %1.*f",
                                      outVar->m_displayDecimals, tableVal( out ) );
                    }
                    // Display the output value.
                    if ( hatch && doBlank )
                    {
                        // draw nothing
                    }
                    else
                    {
                        m_composer->text(
                            colXPos[col] + s,   yPos,
                            colWd,              textHt,
                            Qt::AlignVCenter|Qt::AlignRight,
                            qStr );
                    }
                    out += tableVars();
                    // RX hatching
                    if ( hatch && ! doBlank && ! outVar->isDiagram() )
                    {
                        x0 = colXPos[col] + s - m_padWd;
                        x1 = colXPos[col] + s + m_padWd + colWd;
                        m_composer->line( x0, yPos, x1, ( yPos + textHt ) );
                        m_composer->line( x0, ( yPos + textHt ), x1, yPos );
                    }
                } // Next table output variable.
                yPos += rowHt;
            } // Next table row.
        } // Next pageAcross.
    } // Next pageDown.

    // Be polite and stop the composer.
    m_composer->end();

    // Clean up and return.
    delete[] colPage;
    delete[] colXPos;
    delete[] colText;
    delete[] shift;
    return;
}
void BpDocument::composeFireCharacteristicsDiagram( void )
{
    // Surface Module must be active and using fuel model inputs
    PropertyDict *prop = m_eqTree->m_propDict;
    if ( ! prop->boolean( "surfaceModuleActive" )
      || ! prop->boolean( "surfaceCalcFireCharacteristicsDiagram" ) )
    {
        return;
    }

    // Graph fonts.
    QFont  textFont( property()->string( "graphTextFontFamily" ),
                     property()->integer( "graphTextFontSize" ) );
    QColor textColor( property()->color( "graphTextFontColor" ) );
    QPen   textPen( textColor );
    QFont  subTitleFont( property()->string( "graphSubtitleFontFamily" ),
                    property()->integer( "graphSubtitleFontSize" ) );
    QColor subTitleColor( property()->color( "graphSubtitleFontColor" ) );

    // Open the result file
    QString resultFile = m_eqTree->m_resultFile;
    FILE *fptr = 0;
    if ( ! ( fptr = fopen( resultFile.latin1(), "r" ) ) )
    // This code block should never be executed!
    {
        QString text("");
        translate( text, "BpDocument:FireCharacteristicsDiagram:NoLogOpen",
            resultFile );
        error( text );
        return;
    }
    // Allocate ros and hpua data arrays
    int rows  = tableRows();
    int cols  = tableCols();
    int cells = rows * cols;
    double *hpua = new double[ cells ];
    checkmem( __FILE__, __LINE__, hpua, "double hpua", cells );
    double *ros = new double[ cells ];
    checkmem( __FILE__, __LINE__, ros, "double ros", cells );
    // Set the variable names we're looking for
    const char* hpuaName = "vSurfaceFireHeatPerUnitArea";
    const char* rosName = "vSurfaceFireSpreadAtHead";
    if ( prop->boolean( "surfaceConfSpreadDirInput" ) )
    {
        rosName = "vSurfaceFireSpreadAtVector";
    }
    // Read and store the ros and hpua values
    char   buffer[1024], varName[128], varUnits[128];
    int    row, col, cell;
    double value;
    double rosMax = 0.0;
    double hpuaMax = 0.0;
    while ( fgets( buffer, sizeof(buffer), fptr ) )
    {
        if ( strncmp( buffer, "CELL", 4 ) == 0 )
        {
            if ( strstr( buffer, hpuaName ) )
            {
                sscanf( buffer, "CELL %d %d %s cont %lf %s",
                    &row, &col, varName, &value, varUnits );
                cell = ( col - 1 ) + ( cols * ( row - 1) );
                if ( ( hpua[ cell ] = value ) > hpuaMax )
                {
                    hpuaMax = value;
                }
            }
            else if ( strstr( buffer, rosName ) )
            {
                sscanf( buffer, "CELL %d %d %s cont %lf %s",
                    &row, &col, varName, &value, varUnits );
                cell = ( col - 1 ) + ( cols * ( row - 1) );
                if ( ( ros[ cell ] = value ) > rosMax )
                {
                    rosMax = value;
                }
            }
        }
    }
    fclose( fptr );

    // Get variable pointers
    EqVar *hpuaVar = m_eqTree->m_varDict->find( "vSurfaceFireHeatPerUnitArea" );
    EqVar *rosVar = m_eqTree->m_varDict->find( "vSurfaceFireSpreadAtHead" );
    EqVar *fliVar = m_eqTree->m_varDict->find( "vSurfaceFireLineIntAtHead" );
    EqVar *flVar = m_eqTree->m_varDict->find( "vSurfaceFireFlameLengAtHead" );

    // Conversion factor
    double flFactor, fliFactor, rosFactor, hpuaFactor, offset;
    appSiUnits()->conversionFactorOffset(
        flVar->m_nativeUnits, flVar->m_displayUnits, &flFactor, &offset );
    appSiUnits()->conversionFactorOffset(
        fliVar->m_nativeUnits, fliVar->m_displayUnits, &fliFactor, &offset );
    appSiUnits()->conversionFactorOffset(
        hpuaVar->m_nativeUnits, hpuaVar->m_displayUnits, &hpuaFactor, &offset );
    appSiUnits()->conversionFactorOffset(
        rosVar->m_nativeUnits, rosVar->m_displayUnits, &rosFactor, &offset );

    // Determine which of four different chart scales to use
    static const int Scales = 4;
    static double RosScale[Scales] = { 100., 200., 400., 800. };        // ft/min
    static double HpuaScale[Scales] = { 2000., 4000., 8000., 16000. };  // Btu/ft2
    double rosScale = 0.;       // Max y-axis ros
    double hpuaScale = 0.;      // Max x-axis hpua
    int scale;
    for ( scale=0; scale<Scales; scale++ )
    {
        if ( rosMax < ( rosScale = RosScale[ scale ] ) )
        {
            break;
        }
    }
    for ( scale=0; scale<Scales; scale++ )
    {
        if ( hpuaMax < ( hpuaScale = HpuaScale[scale] ) )
        {
            break;
        }
    }
    // Set axis maximums to appropriate predefined scale in display units
    rosMax  = rosFactor * rosScale;
    hpuaMax = hpuaFactor * hpuaScale;
    double ratio = rosMax / hpuaMax;

    // Create the graph
    Graph  graph;
    GraphLine *graphLine;
    GraphMarker *graphMarker;
    static const int Points = 100;
    double l_x[Points];
    double l_y[Points];

    // Draw the four standard hauling chart fli-fl levels
    static const int Lines = 4;
    static const double Fli[Lines] = { 100., 500., 1000., 2000. };  // Btu/ft/s
    static const double Fl[Lines] = { 4., 8., 11., 15. };           // ft

    // Create the hauling chart lines
    // Put Fireline Int label 65% of the way along the HPUA axis (display units)
    double  xPosFli = 0.65 * hpuaMax;
    // Put Flame Length label 85% of the way along the HPUA axis (display units)
    double  xPosFl  = 0.85 * hpuaMax;
    // Fireline Int and Flame Length label Y positions (display units)
    double  yPosFl[Lines], yPosFli[Lines];
    // Icon locations (in display units)
    double xIcon[Lines+1], yIcon[Lines+1];
    double diff, minDiff;
    QString label;
    QPen    redPen( "red", 1 );
    QColor  blackColor( "black" );
    int     alignCenter = Qt::AlignHCenter | Qt::AlignVCenter;
    // Fireline intensity - flame length curves
    int     line, point;
    for ( line = 0; line < Lines; line++ )
    {
        minDiff = 999999999.;
        for ( point = 0; point < Points; point++ )
        {
            // Hpua value in native units (Btu/ft2)
            l_x[point] = ( (point+1) * hpuaScale ) / (double) Points;
            // Ros value in native units (ft/min)
            l_y[point] = 60. * Fli[line] / l_x[point];
            // Convert to display units
            l_x[point] *= hpuaFactor;
            l_y[point] *= rosFactor;
            // Check for curve inflection point (for icon placement)
            if ( ( diff = fabs( l_y[point]/l_x[point] - ratio ) ) < minDiff )
            {
                minDiff = diff;
                xIcon[line+1] = l_x[point];
                yIcon[line+1] = l_y[point];
            }
        }
        // Create a graph line (with its own copy of the data).
        graphLine = graph.addGraphLine( Points, l_x, l_y, redPen );

        // Fireline intensity label
        label = QString( "%1" ).arg( ( Fli[line] * fliFactor ), 0, 'f', 0 );
        yPosFli[line] = rosFactor * ( 60. * Fli[line] / ( xPosFli / hpuaFactor ) );
        graph.addGraphMarker( xPosFli, yPosFli[line], label, textFont,
            blackColor, alignCenter );

        // Flame length label
        label = QString( "%1" ).arg( ( Fl[line] * flFactor ), 0, 'f', 0 );
        yPosFl[line] = rosFactor * ( 60. * Fli[line] / ( xPosFl / hpuaFactor ) );
        graph.addGraphMarker( xPosFl, yPosFl[line], label, textFont,
            blackColor, alignCenter );
    } // Next line

    // Fireline intensity label and units
    translate( label, "BpDocument:FireCharacteristicsDiagram:FLI" );
    graph.addGraphMarker( xPosFli, ( yPosFli[Lines-1] + 0.10 * rosMax ),
        label, textFont, blackColor, alignCenter );
    graph.addGraphMarker( xPosFli, ( yPosFli[Lines-1] + 0.05 * rosMax ),
        fliVar->m_displayUnits, textFont, blackColor, alignCenter );

    // Flame length label and units
    translate( label, "BpDocument:FireCharacteristicsDiagram:FL" );
    graph.addGraphMarker( xPosFl, ( yPosFl[Lines-1] + 0.10 * rosMax ),
        label, textFont, blackColor, alignCenter );
    graph.addGraphMarker( xPosFl, ( yPosFl[Lines-1] + 0.05 * rosMax ),
        flVar->m_displayUnits, textFont, blackColor, alignCenter );

    // Add icons
    QPixmap pixmap[Lines];
    pixmap[0] = QPixmap( fireman_xpm );
    pixmap[1] = QPixmap( dozer_xpm );
    pixmap[2] = QPixmap( torchtree_xpm );
    pixmap[3] = QPixmap( mtnfire_xpm );
    xIcon[0] = yIcon[0] = 0.0;
    for ( line=0; line<Lines; line++ )
    {
        graphMarker = graph.addGraphMarker(
            xIcon[line] + ( 0.5 * ( xIcon[line+1] - xIcon[line] ) ),
            yIcon[line] + ( 0.5 * ( yIcon[line+1] - yIcon[line] ) ),
            "", textFont, blackColor, alignCenter );
        graphMarker->setGraphMarkerPixmap( pixmap[line] );
    }

    // Finally, add a marker for each output result
    QColor  bluePen( "blue" );
    for ( cell=0; cell<cells; cell++ )
    {
        //fprintf( stderr, "%02d: %3.2f  %3.2f\n", i, hpua[i], ros[i] );
        graph.addGraphMarker( hpua[cell], ros[cell],
            QString( "%1" ).arg( cell + 1 ), textFont, bluePen, alignCenter );
    }
    // Compose the graph
    EqVar *zVar = 0;
    GraphAxleParms xParms( 0.0, hpuaMax, 11 );
    GraphAxleParms yParms( 0.0, rosMax, 11 );
    composeGraphBasics( &graph, true, hpuaVar, rosVar, zVar, Lines,
        &xParms, &yParms );

    // Create a separate page for this graph.
    translate( label, "BpDocument:FireCharacteristicsDiagram:Caption" );
    graph.setSubTitle( label, subTitleFont, subTitleColor );
    startNewPage( label, TocHaulChart );

    // This is how we save the graph and its composer.
    m_composer->graph( graph,
        m_pageSize->m_marginLeft
            + m_pageSize->m_bodyWd * property()->real( "graphXOffset" ),
        m_pageSize->m_marginTop
            + m_pageSize->m_bodyHt * property()->real( "graphYOffset" ),
        m_pageSize->m_bodyWd * property()->real( "graphScaleWidth" ),
        m_pageSize->m_bodyHt * property()->real( "graphScaleHeight" )
    );
    // Be polite and stop the composer.
    m_composer->end();
    delete[] ros;   ros  = 0;
    delete[] hpua;  hpua = 0;
    return;
}