573 lines
24 KiB
C#
573 lines
24 KiB
C#
///
|
|
/// Copyright (c) 2018-2023 Sensus Slovensko a.s.
|
|
///
|
|
using Common;
|
|
using Results.Resources;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Printing;
|
|
|
|
namespace Results.Output
|
|
{
|
|
public enum TableLines
|
|
{
|
|
None,
|
|
FirstAndLast,
|
|
FirstSecondAndLast,
|
|
All,
|
|
}
|
|
|
|
public class TableStyle
|
|
{
|
|
public float MarginX;
|
|
public float MarginY;
|
|
public Font Font;
|
|
public Font HeaderFont;
|
|
public int TopHeadersCount;
|
|
public int LeftHeadersCount;
|
|
public float LineWidth;
|
|
public TableLines HorizLines;
|
|
public TableLines VertLines;
|
|
public float[] ColumnWidths; /// null = automatic
|
|
}
|
|
|
|
public class Table
|
|
{
|
|
public readonly int ColumnsCount; /// Number of columns including the headers
|
|
public readonly string AutoTestParam; /// Reserved for future use
|
|
public readonly IList<Cell[]> Cells; /// Table content: List of rows, each row is an array of cells
|
|
public readonly TableStyle Style;
|
|
|
|
/// <summary>
|
|
/// Calculated by Measure(e)
|
|
/// </summary>
|
|
float width;
|
|
float height;
|
|
float[] columnWidth; /// Widths of columns of cells
|
|
float[] columnPos; /// X-coordinate of positions of texts in columns (MarginX is left empty)
|
|
float[] vertLinePos; /// X-coordinate of positions of vertical lines
|
|
float[] rowHeight; /// Heights of rows of cells
|
|
float[] rowPos; /// Y-coordinate of positions of texts in columns (MarginY is left empty)
|
|
float[] horizLinePos; /// Y-coordinate of positions of horizontal lines
|
|
|
|
public int RowsCount /// Number of rows including the headers
|
|
{
|
|
get { return Cells.Count; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="columnsCount">Number of columns</param>
|
|
/// <param name="autoTestParam">Reserved for future use</param>
|
|
public Table(int columnsCount, TableStyle style, string autoTestParam = null)
|
|
{
|
|
this.ColumnsCount = columnsCount;
|
|
this.Style = style;
|
|
this.AutoTestParam = autoTestParam;
|
|
|
|
Cells = new List<Cell[]>();
|
|
columnWidth = null;
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
Cells.Clear();
|
|
columnWidth = null;
|
|
}
|
|
|
|
public void AddRow(Cell[] rowOfCells)
|
|
{
|
|
if (rowOfCells != null && rowOfCells.Length == ColumnsCount)
|
|
{
|
|
Cells.Add(rowOfCells);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true when table has either rows or columns count == 0
|
|
/// </summary>
|
|
/// <returns>true when empty</returns>
|
|
public bool IsEmpty()
|
|
{
|
|
return RowsCount == 0 || ColumnsCount == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates table dimensions. Inputs: ColumnsCount, Cells, Style
|
|
/// </summary>
|
|
/// <param name="e">PrintPageEventArgs passed to Measure(...)</param>
|
|
/// <returns>Table width and height (SizeF)</returns>
|
|
public SizeF Measure(PrintPageEventArgs e)
|
|
{
|
|
if (IsEmpty()) return new SizeF(0, 0); /// Empty table => zero Size
|
|
|
|
columnWidth = new float[ColumnsCount];
|
|
columnPos = new float[ColumnsCount];
|
|
vertLinePos = new float[ColumnsCount + 1];
|
|
|
|
rowHeight = new float[RowsCount];
|
|
rowPos = new float[RowsCount];
|
|
horizLinePos = new float[RowsCount + 1];
|
|
|
|
width = 0;
|
|
height = 0;
|
|
vertLinePos[0] = 0;
|
|
horizLinePos[0] = 0;
|
|
|
|
///
|
|
/// Measure table column widths, vertical line and text positions, etc.
|
|
///
|
|
for (int colIx = 0; colIx < ColumnsCount; colIx++)
|
|
{
|
|
bool hasLineToLeft = false;
|
|
|
|
for (int rowIx = 0; rowIx < RowsCount; rowIx++)
|
|
{
|
|
Cells[rowIx][colIx].Measure(e);
|
|
|
|
float cellWidth = (Style.ColumnWidths != null && Style.ColumnWidths.Length > colIx)
|
|
? Style.ColumnWidths[colIx]
|
|
: Cells[rowIx][colIx].Size.Width;
|
|
if (cellWidth > columnWidth[colIx]) columnWidth[colIx] = cellWidth;
|
|
|
|
if (Cells[rowIx][colIx].LeftBorder) hasLineToLeft = true;
|
|
}
|
|
|
|
width += columnWidth[colIx] + (hasLineToLeft ? (Style.LineWidth + Style.MarginX) : 0) + Style.MarginX;
|
|
columnPos[colIx] = vertLinePos[colIx] + (hasLineToLeft ? (Style.LineWidth + Style.MarginX) : 0);
|
|
vertLinePos[colIx + 1] = columnPos[colIx] + columnWidth[colIx] + Style.MarginX;
|
|
}
|
|
|
|
///
|
|
/// Measure table row heights, horixontal line and text positions, etc.
|
|
///
|
|
for (int rowIx = 0; rowIx < RowsCount; rowIx++)
|
|
{
|
|
bool hasLineAbove = false;
|
|
|
|
for (int colIx = 0; colIx < ColumnsCount; colIx++)
|
|
{
|
|
if (Cells[rowIx][colIx].Size.Height > rowHeight[rowIx]) rowHeight[rowIx] = Cells[rowIx][colIx].Size.Height;
|
|
if (Cells[rowIx][colIx].TopBorder) hasLineAbove = true;
|
|
}
|
|
|
|
height += rowHeight[rowIx] + (hasLineAbove ? (Style.LineWidth + Style.MarginY) : 0) + Style.MarginY;
|
|
rowPos[rowIx] = horizLinePos[rowIx] + (hasLineAbove ? Style.MarginY : 0);
|
|
horizLinePos[rowIx + 1] = rowPos[rowIx] + rowHeight[rowIx] + Style.LineWidth + Style.MarginY;
|
|
}
|
|
|
|
if (Style.VertLines == TableLines.None) width -= Style.MarginX;
|
|
if (Style.HorizLines == TableLines.None) height -= Style.MarginY;
|
|
|
|
return new SizeF(width, height);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prints a table defined by ColumnsCount, Rows, HorizAlignments, VertAlignments and Style
|
|
/// </summary>
|
|
/// <param name="e">PrintPageEventArgs passed to Measure(.), DrawLine(.) and PrintAt(.)</param>
|
|
/// <param name="left">X-coordinate of the top-left corner of the table</param>
|
|
/// <param name="top">Y-coordinate of the top-left corner of the table</param>
|
|
/// <returns>Y-coordinate for a next item following this table on a page</returns>
|
|
public float Draw(PrintPageEventArgs e, float left, float top)
|
|
{
|
|
if (IsEmpty()) return top; /// Empty table or Mesure(e) have not been called yet
|
|
|
|
if (columnWidth == null) Measure(e);
|
|
|
|
for (int rowIx = 0; rowIx <= RowsCount; rowIx++)
|
|
{
|
|
for (int colIx = 0; colIx <= ColumnsCount; colIx++)
|
|
{
|
|
///
|
|
/// Draw horizontal line
|
|
///
|
|
if (colIx < ColumnsCount && Style.HorizLines != TableLines.None &&
|
|
(rowIx == 0 || rowIx == RowsCount || Cells[rowIx][colIx].TopBorder))
|
|
{
|
|
e.Graphics.DrawLine(new Pen(Color.Black, Style.LineWidth),
|
|
new PointF(left + vertLinePos[colIx], top + horizLinePos[rowIx] + Style.LineWidth / 2),
|
|
new PointF(left + vertLinePos[colIx + 1] + Style.LineWidth, top + horizLinePos[rowIx] + Style.LineWidth / 2));
|
|
}
|
|
|
|
///
|
|
/// Draw vertical line
|
|
///
|
|
if (rowIx < RowsCount && Style.VertLines != TableLines.None &&
|
|
(colIx == 0 || colIx == ColumnsCount || Cells[rowIx][colIx].LeftBorder))
|
|
{
|
|
e.Graphics.DrawLine(new Pen(Color.Black, Style.LineWidth),
|
|
new PointF(left + vertLinePos[colIx] + Style.LineWidth / 2, top + horizLinePos[rowIx]),
|
|
new PointF(left + vertLinePos[colIx] + Style.LineWidth / 2, top + horizLinePos[rowIx + 1] + Style.LineWidth));
|
|
}
|
|
|
|
///
|
|
/// Draw table item (a text)
|
|
///
|
|
if (rowIx < RowsCount && colIx < ColumnsCount) /// Make sure Cells[][] indices are within range
|
|
{
|
|
if (!Cells[rowIx][colIx].BottomMerge && !Cells[rowIx][colIx].RightMerge)
|
|
{
|
|
/// This is not a cell that is merged with a subsequent cell => print content
|
|
|
|
/// Calculate X-coordinate taking into account cell merging
|
|
float mergedColPos = columnPos[colIx];
|
|
int mergedCellsCount = 1;
|
|
for (int colIx2 = colIx - 1; colIx2 >= 0 && Cells[rowIx][colIx2].RightMerge; colIx2--)
|
|
{
|
|
mergedColPos += columnPos[colIx2];
|
|
mergedCellsCount++;
|
|
}
|
|
mergedColPos /= mergedCellsCount;
|
|
|
|
/// Calculate Y-coordinate taking into account cell merging
|
|
float mergedRowPos = rowPos[rowIx];
|
|
mergedCellsCount = 1;
|
|
List<string> texts = new List<string>();
|
|
texts.Add(Cells[rowIx][colIx].Text);
|
|
for (int rowIx2 = rowIx - 1; rowIx2 >= 0 && Cells[rowIx2][colIx].BottomMerge; rowIx2--)
|
|
{
|
|
mergedRowPos += rowPos[rowIx2];
|
|
mergedCellsCount++;
|
|
texts.Add(Cells[rowIx2][colIx].Text);
|
|
}
|
|
mergedRowPos /= mergedCellsCount;
|
|
|
|
/// Print the text at the calculated position
|
|
Common.Printers.PrintersCommon.PrintAt(e, mergedCellsCount == 1 ? Cells[rowIx][colIx].Text : GetMergedCellStringAsNegativeOrPositive(texts),
|
|
Cells[rowIx][colIx].Font,
|
|
left + mergedColPos,
|
|
top + mergedRowPos,
|
|
columnWidth[colIx],
|
|
rowHeight[rowIx],
|
|
Cells[rowIx][colIx].HorizAlignment,
|
|
Cells[rowIx][colIx].VertAlignment);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return top + horizLinePos[RowsCount] + Style.LineWidth;
|
|
}
|
|
|
|
|
|
public static Table Create_TestsAreRows(Entities.WaterMeter wm, IList<WMeterRsltItemSpec> items, TableStyle style, string autoTestParam = null)
|
|
{
|
|
/// Select results to be printed
|
|
var mtrTestRslts = string.IsNullOrEmpty(autoTestParam) ? wm.RegularMeterTestRslts() : wm.AutoMeterTestRslts(autoTestParam);
|
|
|
|
///
|
|
/// Calculate rows count
|
|
///
|
|
int rowsCount = 1; /// Top row contains item captions
|
|
foreach (var mtr in mtrTestRslts)
|
|
{
|
|
if (mtr != null && mtr.IsPilotRslt() && mtr.Publish() == Publish.Always)
|
|
{
|
|
rowsCount++; /// One additional row for each test to be published
|
|
}
|
|
}
|
|
|
|
Table table = new Table(items.Count, style);
|
|
|
|
///
|
|
/// Top row contains column captions
|
|
///
|
|
var header = new Cell[items.Count];
|
|
for (int i = 0; i < items.Count; i++)
|
|
{
|
|
header[i] = new Cell(items[i].Caption, style.HeaderFont, Alignment.Center, VertAlignment.Middle, true, true);
|
|
}
|
|
table.AddRow(header);
|
|
|
|
///
|
|
/// Create and add a row for each test
|
|
///
|
|
WMeterRsltItemSpec.TableRowNr = 1;
|
|
foreach (var mtr in mtrTestRslts)
|
|
{
|
|
if (mtr != null && mtr.IsPilotRslt() && mtr.Publish() == Publish.Always)
|
|
{
|
|
var aTableRow = new Cell[items.Count];
|
|
for (int i = 0; i < items.Count; i++)
|
|
{
|
|
WMeterRsltItemSpec.TableColumnNr = i + 1;
|
|
string text = items[i].Print(wm, mtr.Name()).Split(new char[] { '|' })[0];
|
|
aTableRow[i] = new Cell(text,
|
|
style.Font,
|
|
items[i].Alignment,
|
|
VertAlignment.Middle,
|
|
(WMeterRsltItemSpec.TableRowNr == 1) || !items[i].Merge,
|
|
true,
|
|
(WMeterRsltItemSpec.TableRowNr != rowsCount - 1) && items[i].Merge,
|
|
false);
|
|
}
|
|
table.AddRow(aTableRow);
|
|
|
|
WMeterRsltItemSpec.TableRowNr++;
|
|
}
|
|
}
|
|
|
|
return table;
|
|
}
|
|
|
|
public string GetMergedCellStringAsNegativeOrPositive(List<string> texts)
|
|
{
|
|
if (texts.Count < 1)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
bool firstIsHead = false;
|
|
bool hasNegativeAll = true;
|
|
bool hasPositiveAll = true;
|
|
bool hasMixed = false;
|
|
string lastRowText = null;
|
|
|
|
//check if is a header
|
|
|
|
foreach (string text in texts)
|
|
{
|
|
if (text != "-")
|
|
{
|
|
hasNegativeAll = false;
|
|
}
|
|
|
|
if (text != "+")
|
|
{
|
|
hasPositiveAll = false;
|
|
}
|
|
|
|
if (!hasMixed)
|
|
{
|
|
if (lastRowText == null)
|
|
{
|
|
lastRowText = text;
|
|
}
|
|
else
|
|
{
|
|
if (lastRowText != text)
|
|
{
|
|
hasMixed = true;
|
|
}
|
|
|
|
lastRowText = text;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasMixed)
|
|
{
|
|
//merge as negative
|
|
return "-";
|
|
}
|
|
else if (hasPositiveAll)
|
|
{
|
|
//merge as positive
|
|
return "+";
|
|
}
|
|
else if (hasNegativeAll)
|
|
{
|
|
//merge as negative
|
|
return "-";
|
|
}
|
|
|
|
return texts[0];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a table where tests are in columns.
|
|
/// Left column contains item (or row) captions.
|
|
/// </summary>
|
|
/// <param name="wm">Water meter</param>
|
|
/// <param name="items">Result items</param>
|
|
/// <param name="style">Table style</param>
|
|
/// <param name="autoTestParam">Auto test parameter</param>
|
|
/// <returns>Table to be printed</returns>
|
|
public static Table Create_TestsAreColumns(Entities.WaterMeter wm, IList<WMeterRsltItemSpec> items, TableStyle style, string autoTestParam = null)
|
|
{
|
|
/// Select results to be printed
|
|
var mtrTestRslts = string.IsNullOrEmpty(autoTestParam) ? wm.RegularMeterTestRslts() : wm.AutoMeterTestRslts(autoTestParam);
|
|
|
|
///
|
|
/// Calculate columns count
|
|
///
|
|
int columnsCount = 1; /// Left column contains item captions
|
|
foreach (var mtr in mtrTestRslts)
|
|
{
|
|
if (mtr != null && mtr.IsPilotRslt() && mtr.Publish() == Publish.Always)
|
|
{
|
|
columnsCount++; /// One additional column for each test to be published
|
|
}
|
|
}
|
|
|
|
Table table = new Table(columnsCount, style);
|
|
|
|
///
|
|
/// Table content
|
|
///
|
|
bool separatorLineSignalled = false;
|
|
WMeterRsltItemSpec.TableRowNr = 1;
|
|
for (int i = 0; i < items.Count; i++)
|
|
{
|
|
if (items[i].Uid == (int)ItemID.Separator)
|
|
{
|
|
/// Insert a separator line (topBorder = true for all row cells)
|
|
separatorLineSignalled = true;
|
|
continue;
|
|
}
|
|
|
|
bool topBorder = (style.HorizLines == TableLines.All)
|
|
|| (style.HorizLines == TableLines.FirstAndLast && i == 0)
|
|
|| (style.HorizLines == TableLines.FirstSecondAndLast && i <= 1)
|
|
|| separatorLineSignalled;
|
|
|
|
separatorLineSignalled = false;
|
|
|
|
Cell[] aTableRow = new Cell[columnsCount];
|
|
|
|
aTableRow[0] = new Cell(items[i].Caption,
|
|
style.HeaderFont,
|
|
Alignment.Center,
|
|
VertAlignment.Middle,
|
|
topBorder,
|
|
style.VertLines != TableLines.None);
|
|
|
|
WMeterRsltItemSpec.TableColumnNr = 1;
|
|
for (int j = 0; j < mtrTestRslts.Count; j++)
|
|
{
|
|
var mtr = mtrTestRslts[j];
|
|
|
|
bool leftBorder = (style.VertLines == TableLines.All && (!items[i].Merge || j == 0))
|
|
|| (style.VertLines == TableLines.FirstSecondAndLast && j == 0);
|
|
|
|
if (mtr != null && mtr.IsPilotRslt() && mtr.Publish() == Publish.Always)
|
|
{
|
|
/// Get a text, strip color information
|
|
string text = items[i].Print(wm, mtr.Name()).Split(new char[] { '|' })[0];
|
|
|
|
aTableRow[WMeterRsltItemSpec.TableColumnNr] = new Cell(text,
|
|
style.Font,
|
|
items[i].Alignment,
|
|
VertAlignment.Middle,
|
|
topBorder,
|
|
leftBorder,
|
|
false,
|
|
(WMeterRsltItemSpec.TableColumnNr + 1 != columnsCount) && items[i].Merge);
|
|
|
|
WMeterRsltItemSpec.TableColumnNr++;
|
|
}
|
|
}
|
|
|
|
table.AddRow(aTableRow);
|
|
|
|
WMeterRsltItemSpec.TableRowNr++;
|
|
}
|
|
|
|
return table;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Create a table where tests are in columns.
|
|
/// Two leftmost columns contains item (or row) captions and units.
|
|
/// Separator lines can be inserted between rows.
|
|
/// </summary>
|
|
/// <param name="wm">Water meter</param>
|
|
/// <param name="items">Result items</param>
|
|
/// <param name="style">Table style</param>
|
|
/// <param name="autoTestParam">Auto test parameter</param>
|
|
/// <returns>Table to be printed</returns>
|
|
public static Table Create_TestsAreColumnsPL(Entities.WaterMeter wm, IList<WMeterRsltItemSpec> items, TableStyle style, string autoTestParam = null)
|
|
{
|
|
/// Select results to be printed
|
|
var mtrTestRslts = string.IsNullOrEmpty(autoTestParam) ? wm.RegularMeterTestRslts() : wm.AutoMeterTestRslts(autoTestParam);
|
|
|
|
///
|
|
/// Calculate columns count
|
|
///
|
|
int columnsCount = 2; /// Two left columns contain item caption and unit
|
|
foreach (var mtr in mtrTestRslts)
|
|
{
|
|
if (mtr != null && mtr.IsPilotRslt() && mtr.Publish() == Publish.Always)
|
|
{
|
|
columnsCount++; /// One additional column for each test to be published
|
|
}
|
|
}
|
|
|
|
Table table = new Table(columnsCount, style);
|
|
|
|
///
|
|
/// Table content
|
|
///
|
|
bool separatorLineSignalled = false;
|
|
WMeterRsltItemSpec.TableRowNr = 1;
|
|
for (int i = 0; i < items.Count; i++)
|
|
{
|
|
if (items[i].Uid == (int)ItemID.Separator)
|
|
{
|
|
/// Insert a separator line (topBorder = true for all row cells)
|
|
separatorLineSignalled = true;
|
|
continue;
|
|
}
|
|
|
|
bool topBorder = (style.HorizLines == TableLines.All)
|
|
|| (style.HorizLines == TableLines.FirstAndLast && i == 0)
|
|
|| (style.HorizLines == TableLines.FirstSecondAndLast && i <= 1)
|
|
|| separatorLineSignalled;
|
|
|
|
separatorLineSignalled = false;
|
|
|
|
Cell[] aTableRow = new Cell[columnsCount];
|
|
|
|
aTableRow[0] = new Cell(items[i].Caption,
|
|
style.HeaderFont,
|
|
Alignment.Center,
|
|
VertAlignment.Middle,
|
|
topBorder,
|
|
style.VertLines != TableLines.None);
|
|
|
|
aTableRow[1] = new Cell((i == 0) ? Strings.Unit : items[i].Units.ToDescription(),
|
|
style.HeaderFont,
|
|
Alignment.Center,
|
|
VertAlignment.Middle,
|
|
topBorder,
|
|
style.VertLines == TableLines.All || style.VertLines == TableLines.FirstSecondAndLast);
|
|
|
|
WMeterRsltItemSpec.TableColumnNr = 1;
|
|
for (int j = 0; j < mtrTestRslts.Count; j++)
|
|
{
|
|
var mtr = mtrTestRslts[j];
|
|
|
|
bool leftBorder = (style.VertLines == TableLines.All && (!items[i].Merge || j == 0))
|
|
|| (style.VertLines == TableLines.FirstSecondAndLast && WMeterRsltItemSpec.TableColumnNr == 1);
|
|
|
|
if (mtr != null && mtr.IsPilotRslt() && mtr.Publish() == Publish.Always)
|
|
{
|
|
/// Get a text, strip color information
|
|
string text = items[i].Print(wm, mtr.Name()).Split(new char[] { '|' })[0];
|
|
|
|
aTableRow[WMeterRsltItemSpec.TableColumnNr + 1] = new Cell(text,
|
|
style.Font,
|
|
items[i].Alignment,
|
|
VertAlignment.Middle,
|
|
topBorder,
|
|
leftBorder,
|
|
false,
|
|
(WMeterRsltItemSpec.TableColumnNr + 2 != columnsCount) && items[i].Merge);
|
|
|
|
WMeterRsltItemSpec.TableColumnNr++;
|
|
}
|
|
}
|
|
|
|
table.AddRow(aTableRow);
|
|
|
|
WMeterRsltItemSpec.TableRowNr++;
|
|
}
|
|
|
|
return table;
|
|
}
|
|
}
|
|
}
|