Package jimm.datavision.layout

Source Code of jimm.datavision.layout.LayoutEngine

package jimm.datavision.layout;
import jimm.datavision.*;
import jimm.datavision.field.Field;
import jimm.datavision.field.ImageField;
import java.io.PrintWriter;
import java.util.*;

/**
* A layout engine is responsible for formatting and outputting report data.
* <code>LayoutEngine</code> is an abstract class. The Template design
* pattern is heavily used to provide a framework for concrete layout
* engine subclasses.
*
* @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
*/
public abstract class LayoutEngine {

/** The number of points per inch. */
public static final int POINTS_PER_INCH = 72;

protected static final int SECT_REPORT_HEADER = 0;
protected static final int SECT_REPORT_FOOTER = 1;
protected static final int SECT_PAGE_HEADER = 2;
protected static final int SECT_PAGE_FOOTER = 3;
protected static final int SECT_GROUP_HEADER = 4;
protected static final int SECT_GROUP_FOOTER = 5;
protected static final int SECT_DETAIL = 6;

/** Set by report in <code>Report.setLayoutEngine</code>. */
protected Report report;
protected int pageNumber;
protected PrintWriter out;
protected boolean newPage;
protected double pageHeight;
protected double pageWidth;
protected double pageHeightUsed;
protected Section currentSection;
protected boolean wantsMoreData;
protected int previousSectionArea;

/**
* Constructor.
*/
public LayoutEngine() {
    this(null);
}

/**
* Constructor.
*
* @param out output print writer
*/
public LayoutEngine(PrintWriter out) {
    report = null;
    this.out = out;
    wantsMoreData = true;
    previousSectionArea = -1;
}

public void setReport(Report r) { report = r; }

/**
* Returns the page width in points. This value is only valid after
* {@link #start} has been called.
*
* @return the page width in points
*/
public double pageWidth() { return pageWidth; }

/**
* Returns the page height in points. This value is only valid after
* {@link #start} has been called.
*
* @return the page height in points
*/
public double pageHeight() { return pageHeight; }

/**
* Called by a report before retrieving each row of data, this method returns
* <code>true</code> if this layout engine wants more data. Concrete
* subclasses can set the <code>wantsMoreData</code> instance variable
* to <code>false</code> if the user cancells a report run, for example.
*
* @return <code>true</code> if this layout engine would like more data
*/
public boolean wantsMoreData() { return wantsMoreData; }

/**
* Called by someone else running the report to cancel all the hard work
* this layout engine has performed.
*/
public void cancel() {
    closeOutput();
}

/**
* Called by the report at the beginning of a report run.
* <p>
* By this time, <code>report</code> will be non-<code>null</code>
* because we have been added to a report (which in turn sets our
* <code>report</code> instance variable).
*/
public void start() {
    newPage = true;
    pageHeightUsed = 0;
    pageNumber = 0;
    pageHeight = report.getPaperFormat().getHeight();
    pageWidth = report.getPaperFormat().getWidth();
    if (wantsMoreData) {
  doStart();
  startPage();
    }
}

/**
* Called by <code>start</code> as a chance to insert behavior when the
* report starts.
*/
protected void doStart() { }

/**
* Called by the report at the end of a report run.
*/
public void end() {
    if (wantsMoreData) {
  for (Iterator iter = report.footers().iterator(); iter.hasNext(); )
      outputSection((Section)iter.next(), SECT_REPORT_FOOTER);
  endPage(true);
  doEnd();
    }
    closeOutput();
}

/**
* Called by <code>end</code> as a chance to insert behavior when the
* report ends.
*/
protected void doEnd() {}


/**
* Called by <code>end</code> to let this layout engine clean up a bit.
*/
protected void closeOutput() {
    if (out != null) {
  out.flush();
  out.close();
  out = null;
    }
}

/**
* Called by the report when group headers need to be output. Once one
* group header is output, we output all remaining group headers.
* <p>
* We need to explicitly evaluate all formulas in the headers that will be
* output because {@link #checkRemainingPageLength} causes formulas in
* detail and footers to be evaluated. Those formulas may depend upon
* values in these headers.
*
* @param isLastRow if <code>true</code>, this is the last row of the report
*/
public void groupHeaders(boolean isLastRow) {
    if (!wantsMoreData)
  return;

    boolean headerWasOutput = false;
    for (Iterator iter = report.groups(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  if (headerWasOutput || g.isNewValue()) {
      for (Iterator i2 = g.headers().iterator(); i2.hasNext(); )
    ((Section)i2.next()).evaluateFormulas();
      headerWasOutput = true;
  }
    }
//     boolean headerWasOutput;

    checkRemainingPageLength(isLastRow, true);

    headerWasOutput = false;
    for (Iterator iter = report.groups(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  if (headerWasOutput || g.isNewValue()) {
      for (Iterator i2 = g.headers().iterator(); i2.hasNext(); )
    outputSection((Section)i2.next(), SECT_GROUP_HEADER);
      headerWasOutput = true;
  }
    }
}

/**
* Called by the report when a single detail row needs to be output.
*
* @param isLastRow if <code>true</code>, this is the last row of the report
*/
public void detail(boolean isLastRow) {
    if (!wantsMoreData)
  return;

    checkRemainingPageLength(isLastRow, true);
    for (Iterator iter = report.details().iterator(); iter.hasNext(); )
  outputSection((Section)iter.next(), SECT_DETAIL);
}

/**
* Called by the report when group footers need to be output. When one
* group footer is output, we make sure all of the footers before (above)
* it are also output.
*
* @param isLastRow if <code>true</code>, this is the last row of the report
*/
public void groupFooters(boolean isLastRow) {
    if (!wantsMoreData)
  return;

    checkRemainingPageLength(isLastRow, false);

    // We need to output group footers backwards (Group n ... Group 1).
    // When a group footer is output, make sure all of the footers before
    // (above) it are also output. First, we walk the group list forwards
    // so we can see which group changed first and grab all groups
    // after that one.
    boolean footerWasOutput = false;
    ArrayList groupsToOutput = new ArrayList();
    for (Iterator iter = report.groups(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  if (footerWasOutput || g.isNewValue() || isLastRow) {
      if (footerWasOutput) g.forceFooterOutput();
      groupsToOutput.add(g);
      footerWasOutput = true;
  }
    }

    // Now we reverse the list of groups and output them.
    Collections.reverse(groupsToOutput);
    for (Iterator iter = groupsToOutput.iterator(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  for (Iterator i2 = g.footers().iterator(); i2.hasNext(); )
      outputSection((Section)i2.next(), SECT_GROUP_FOOTER);
    }
}

/**
* Checks remaining page length and outputs a new page if we are at the
* bottom of the page.
*
* @param isLastRow if <code>true</code>, this is the last row of the report
* @param includeDetail if <code>true</code>, include height of detail
* sections
*/
protected void checkRemainingPageLength(boolean isLastRow,
          boolean includeDetail)
{
    // NOTE: need to do this dynamically each row because each one
    // of the multiple detail sections may or may not be active for
    // this row.
    double detailHeight = includeDetail ? calcDetailHeight() : 0;
    double footerHeight = calcPageFooterHeight();
    if (isLastRow) footerHeight += calcReportFooterHeight();

    if ((pageHeightUsed + footerHeight + detailHeight) > pageHeight())
  endPage(isLastRow);

    if (newPage) startPage();
}

/**
* Returns the current page number.
*
* @return the current page number
*/
public int pageNumber() { return pageNumber; }

/**
* Starts a new page.
*/
protected void startPage() {
    if (!wantsMoreData)
  return;

    pageNumber += 1;
    pageHeightUsed = 0;
    newPage = false;

    doStartPage();
    if (pageNumber == 1) {
  for (Iterator iter = report.headers().iterator(); iter.hasNext(); )
      outputSection((Section)iter.next(), SECT_REPORT_HEADER);
    }
    for (Iterator iter = report.pageHeaders().iterator(); iter.hasNext(); )
  outputSection((Section)iter.next(), SECT_PAGE_HEADER);
}

/**
* Called by <code>startPage</code> as a chance to insert behavior when a
* new page starts.
*/
protected void doStartPage() { }

/**
* Ends a new page.
*
* @param isLastPage if <code>true</code>, this is the last page of the report
*/
protected void endPage(boolean isLastPage) {
    if (!wantsMoreData)
  return;

    for (Iterator iter = report.pageFooters().iterator(); iter.hasNext(); )
  outputSection((Section)iter.next(), SECT_PAGE_FOOTER);

    newPage = true;
    doEndPage();
}

/**
* Called by <code>endPage</code> as a chance to insert behavior when a
* new page ends.
*/
protected void doEndPage() { }

/**
* Outputs a section.
*
* @param sect the section to output
* @param which the type of section (for example,
* <code>SECT_PAGE_FOOTER</code>)
*/
protected void outputSection(Section sect, int which) {
    if (!wantsMoreData)
  return;

    if (sect.isVisibleForCurrentRow()) {
  // Insert a page break if requested.
  if (sect.hasPageBreak()
      && previousSectionArea != SECT_PAGE_HEADER)
  {
      endPage(false);
      startPage();
  }

  // This must be after page break because calling endPage() and
  // startPage() changes the currentSection.
  currentSection = sect;
  // Insert a page break if section will get overwritten by the page footer.
  if (sect.getArea().getArea() != SECT_PAGE_FOOTER &&
    pageHeight - pageHeightUsed - calcPageFooterHeight() <
    currentSection.getOutputHeight()) {
    endPage(false);
    startPage();
    currentSection = sect;
  }
  report.evaluateFormulasIn(currentSection);
  doOutputSection(currentSection);

  pageHeightUsed += sect.getOutputHeight();
  previousSectionArea = which;
    }
    else {
  // Always eval formulas, even if the section is hidden
  report.evaluateFormulasIn(sect);
    }
}

/**
* Called by <code>outputSection</code> as a chance to insert behavior
* when a section is output.
*
* @param sect a section
*/
protected void doOutputSection(Section sect) {
    // Output the fields in the section
    for (Iterator iter = sect.fields(); iter.hasNext(); ) {
  Field f = (Field)iter.next();
  if (f.isVisible()) {
      if (f instanceof ImageField)
    outputImage((ImageField)f);
      else
    outputField(f);
  }
    }
    // Output the lines
    for (Iterator iter = sect.lines(); iter.hasNext(); ) {
  Line l = (Line)iter.next();
  if (l.isVisible()) outputLine(l);
    }
}

/**
* Outputs a field.
*
* @param field the field to output
*/
protected void outputField(Field field) {
    if (wantsMoreData)    // Do nothing if we have cancelled
  doOutputField(field);
}

/**
* Called by <code>outputField</code> as a chance to insert behavior
* when a field is output.
*
* @param field a field
*/
protected abstract void doOutputField(Field field);

/**
* Outputs a image.
*
* @param image the image field to output
*/
protected void outputImage(ImageField image) {
    if (wantsMoreData)    // Do nothing if we have cancelled
  doOutputImage(image);
}

/**
* Called by <code>outputImage</code> as a chance to insert behavior
* when a image is output.
*
* @param image an image field
*/
protected abstract void doOutputImage(ImageField image);

/**
* Outputs a line.
*
* @param line the line to output
*/
protected void outputLine(Line line) {
    if (wantsMoreData)
  doOutputLine(line);
}

/**
* Called by <code>outputLine</code> as a chance to insert behavior
* when a line is output.
*
* @param line a line
*/
protected abstract void doOutputLine(Line line);

/**
* Returns the current section type (header, footer, detail) as a string.
*
* @return a string representation of the current section type
*/
protected String currentSectionTypeAsString() {
    switch (currentSection.getArea().getArea()) {
    case SectionArea.REPORT_HEADER: return "report header";
    case SectionArea.REPORT_FOOTER: return "report footer";
    case SectionArea.PAGE_HEADER: return "page header";
    case SectionArea.PAGE_FOOTER: return "page footer";
    case SectionArea.GROUP_HEADER: return "group header";
    case SectionArea.GROUP_FOOTER: return "group footer";
    case SectionArea.DETAIL: return "detail";
    default: return "unknown"// Should never happen
    }
}

/**
* Returns the total height of all sections in the specified list.
*
* @param area a section area
* @return the total height of all sections in the list
*/
protected double calcSectionHeights(SectionArea area) {
    double sum = 0;
    for (Iterator iter = area.iterator(); iter.hasNext(); ) {
  Section s = (Section)iter.next();
  if (s.isVisibleForCurrentRow())
      sum += s.getOutputHeight();
    }
    return sum;
}

/**
* Returns the total height of all detail sections.
*
* @return the total height of all detail sections
*/
protected double calcDetailHeight() {
    return calcSectionHeights(report.details());
}

/**
* Returns the total height of all page footer sections.
*
* @return the total height of all page footer sections
*/
protected double calcPageFooterHeight() {
    return calcSectionHeights(report.pageFooters());
}

/**
* Returns the total height of all report footer sections.
*
* @return the total height of all report footer sections
*/
protected double calcReportFooterHeight() {
    return calcSectionHeights(report.footers());
}

}
TOP

Related Classes of jimm.datavision.layout.LayoutEngine

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.