/// /// 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 Cells; /// Table content: List of rows, each row is an array of cells public readonly TableStyle Style; /// /// Calculated by Measure(e) /// 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; } } /// /// Constructor /// /// Number of columns /// Reserved for future use public Table(int columnsCount, TableStyle style, string autoTestParam = null) { this.ColumnsCount = columnsCount; this.Style = style; this.AutoTestParam = autoTestParam; Cells = new List(); columnWidth = null; } public void Clear() { Cells.Clear(); columnWidth = null; } public void AddRow(Cell[] rowOfCells) { if (rowOfCells != null && rowOfCells.Length == ColumnsCount) { Cells.Add(rowOfCells); } } /// /// Returns true when table has either rows or columns count == 0 /// /// true when empty public bool IsEmpty() { return RowsCount == 0 || ColumnsCount == 0; } /// /// Calculates table dimensions. Inputs: ColumnsCount, Cells, Style /// /// PrintPageEventArgs passed to Measure(...) /// Table width and height (SizeF) 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); } /// /// Prints a table defined by ColumnsCount, Rows, HorizAlignments, VertAlignments and Style /// /// PrintPageEventArgs passed to Measure(.), DrawLine(.) and PrintAt(.) /// X-coordinate of the top-left corner of the table /// Y-coordinate of the top-left corner of the table /// Y-coordinate for a next item following this table on a page 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 texts = new List(); 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 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 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]; } /// /// Create a table where tests are in columns. /// Left column contains item (or row) captions. /// /// Water meter /// Result items /// Table style /// Auto test parameter /// Table to be printed public static Table Create_TestsAreColumns(Entities.WaterMeter wm, IList 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; } /// /// 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. /// /// Water meter /// Result items /// Table style /// Auto test parameter /// Table to be printed public static Table Create_TestsAreColumnsPL(Entities.WaterMeter wm, IList 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; } } }