aboutsummaryrefslogtreecommitdiff
path: root/app/source/Cplt/Model/Template/TableTemplate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'app/source/Cplt/Model/Template/TableTemplate.cpp')
-rw-r--r--app/source/Cplt/Model/Template/TableTemplate.cpp591
1 files changed, 591 insertions, 0 deletions
diff --git a/app/source/Cplt/Model/Template/TableTemplate.cpp b/app/source/Cplt/Model/Template/TableTemplate.cpp
new file mode 100644
index 0000000..5cd9ed8
--- /dev/null
+++ b/app/source/Cplt/Model/Template/TableTemplate.cpp
@@ -0,0 +1,591 @@
+#include "TableTemplate.hpp"
+
+#include <Cplt/Utils/IO/StringIntegration.hpp>
+#include <Cplt/Utils/IO/TslArrayIntegration.hpp>
+#include <Cplt/Utils/IO/VectorIntegration.hpp>
+
+#include <xlsxwriter.h>
+#include <algorithm>
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <map>
+
+bool TableCell::IsDataHoldingCell() const
+{
+ return IsPrimaryCell() || !IsMergedCell();
+}
+
+bool TableCell::IsPrimaryCell() const
+{
+ return PrimaryCellLocation == Location;
+}
+
+bool TableCell::IsMergedCell() const
+{
+ return PrimaryCellLocation.x == -1 || PrimaryCellLocation.y == -1;
+}
+
+template <class TTableCell, class TStream>
+void OperateStreamForTableCell(TTableCell& cell, TStream& proxy)
+{
+ proxy.template ObjectAdapted<DataStreamAdapters::String>(cell.Content);
+ proxy.Object(cell.Location);
+ proxy.Object(cell.PrimaryCellLocation);
+ proxy.Value(cell.SpanX);
+ proxy.Value(cell.SpanY);
+ proxy.Enum(cell.HorizontalAlignment);
+ proxy.Enum(cell.VerticalAlignment);
+ proxy.Enum(cell.Type);
+ proxy.Value(cell.DataId);
+}
+
+void TableCell::ReadFromDataStream(InputDataStream& stream)
+{
+ ::OperateStreamForTableCell(*this, stream);
+}
+
+void TableCell::WriteToDataStream(OutputDataStream& stream) const
+{
+ ::OperateStreamForTableCell(*this, stream);
+}
+
+Vec2i TableArrayGroup::GetLeftCell() const
+{
+ return { Row, LeftCell };
+}
+
+Vec2i TableArrayGroup::GetRightCell() const
+{
+ return { Row, RightCell };
+}
+
+int TableArrayGroup::GetCount() const
+{
+ return RightCell - LeftCell + 1;
+}
+
+Vec2i TableArrayGroup::FindCell(std::string_view name)
+{
+ // TODO
+ return Vec2i{};
+}
+
+template <class TMap>
+static bool UpdateElementName(TMap& map, std::string_view oldName, std::string_view newName)
+{
+ auto iter = map.find(oldName);
+ if (iter == map.end()) {
+ return false;
+ }
+
+ auto elm = iter.value();
+ auto [DISCARD, inserted] = map.insert(newName, elm);
+ if (!inserted) {
+ return false;
+ }
+
+ map.erase(iter);
+ return true;
+}
+
+bool TableArrayGroup::UpdateCellName(std::string_view oldName, std::string_view newName)
+{
+ return ::UpdateElementName(mName2Cell, oldName, newName);
+}
+
+template <class TTableArrayGroup, class TStream>
+void OperateStreamForTableArrayGroup(TTableArrayGroup& group, TStream& stream)
+{
+ stream.Value(group.Row);
+ stream.Value(group.LeftCell);
+ stream.Value(group.RightCell);
+}
+
+void TableArrayGroup::ReadFromDataStream(InputDataStream& stream)
+{
+ ::OperateStreamForTableArrayGroup(*this, stream);
+}
+
+void TableArrayGroup::WriteToDataStream(OutputDataStream& stream) const
+{
+ ::OperateStreamForTableArrayGroup(*this, stream);
+}
+
+TableInstantiationParameters::TableInstantiationParameters(const TableTemplate& table)
+ : mTable{ &table }
+{
+}
+
+TableInstantiationParameters& TableInstantiationParameters::ResetTable(const TableTemplate& newTable)
+{
+ mTable = &newTable;
+ return *this;
+}
+
+TableInstantiationParameters TableInstantiationParameters::RebindTable(const TableTemplate& newTable) const
+{
+ TableInstantiationParameters result(newTable);
+ result.SingularCells = this->SingularCells;
+ result.ArrayGroups = this->ArrayGroups;
+ return result;
+}
+
+const TableTemplate& TableInstantiationParameters::GetTable() const
+{
+ return *mTable;
+}
+
+bool TableTemplate::IsInstance(const Template* tmpl)
+{
+ return tmpl->GetKind() == KD_Table;
+}
+
+TableTemplate::TableTemplate()
+ : Template(KD_Table)
+{
+}
+
+int TableTemplate::GetTableWidth() const
+{
+ return mColumnWidths.size();
+}
+
+int TableTemplate::GetTableHeight() const
+{
+ return mRowHeights.size();
+}
+
+void TableTemplate::Resize(int newWidth, int newHeight)
+{
+ // TODO this doesn't gracefully handle resizing to a smaller size which trims some merged cells
+
+ std::vector<TableCell> cells;
+ cells.reserve(newWidth * newHeight);
+
+ int tableWidth = GetTableWidth();
+ int tableHeight = GetTableHeight();
+
+ for (int y = 0; y < newHeight; ++y) {
+ if (y >= tableHeight) {
+ for (int x = 0; x < newWidth; ++x) {
+ cells.push_back(TableCell{});
+ }
+ continue;
+ }
+
+ for (int x = 0; x < newWidth; ++x) {
+ if (x >= tableWidth) {
+ cells.push_back(TableCell{});
+ } else {
+ auto& cell = GetCell({ x, y });
+ cells.push_back(std::move(cell));
+ }
+ }
+ }
+
+ mCells = std::move(cells);
+ mColumnWidths.resize(newWidth, 80);
+ mRowHeights.resize(newHeight, 20);
+}
+
+int TableTemplate::GetRowHeight(int row) const
+{
+ return mRowHeights[row];
+}
+
+void TableTemplate::SetRowHeight(int row, int height)
+{
+ mRowHeights[row] = height;
+}
+
+int TableTemplate::GetColumnWidth(int column) const
+{
+ return mColumnWidths[column];
+}
+
+void TableTemplate::SetColumnWidth(int column, int width)
+{
+ mColumnWidths[column] = width;
+}
+
+const TableCell& TableTemplate::GetCell(Vec2i pos) const
+{
+ int tableWidth = GetTableWidth();
+ return mCells[pos.y * tableWidth + pos.x];
+}
+
+TableCell& TableTemplate::GetCell(Vec2i pos)
+{
+ return const_cast<TableCell&>(const_cast<const TableTemplate*>(this)->GetCell(pos));
+}
+
+void TableTemplate::SetCellType(Vec2i pos, TableCell::CellType type)
+{
+ auto& cell = GetCell(pos);
+ if (cell.Type == type) {
+ return;
+ }
+
+ switch (cell.Type) {
+ // Nothing to change
+ case TableCell::ConstantCell: break;
+
+ case TableCell::SingularParametricCell:
+ mName2Parameters.erase(cell.Content);
+ break;
+
+ case TableCell::ArrayParametricCell: {
+ auto& ag = mArrayGroups[cell.DataId];
+ if (pos.x == ag.LeftCell) {
+ ag.LeftCell++;
+ } else if (pos.x == ag.RightCell) {
+ ag.RightCell--;
+ } else {
+ }
+ } break;
+ }
+
+ switch (type) {
+ // Nothing to do
+ case TableCell::ConstantCell: break;
+
+ case TableCell::SingularParametricCell: {
+ int idx = pos.y * GetTableWidth() + pos.x;
+ auto [DISCARD, inserted] = mName2Parameters.insert(cell.Content, idx);
+
+ // Duplicate name
+ if (!inserted) {
+ return;
+ }
+ } break;
+
+ case TableCell::ArrayParametricCell: {
+ auto ptr = AddArrayGroup(pos.y, pos.x, pos.x);
+
+ // Duplicate name
+ if (ptr == nullptr) {
+ return;
+ }
+ } break;
+ }
+
+ cell.Type = type;
+}
+
+bool TableTemplate::UpdateParameterName(std::string_view oldName, std::string_view newName)
+{
+ return ::UpdateElementName(mName2Parameters, oldName, newName);
+}
+
+int TableTemplate::GetArrayGroupCount() const
+{
+ return mArrayGroups.size();
+}
+
+const TableArrayGroup& TableTemplate::GetArrayGroup(int id) const
+{
+ return mArrayGroups[id];
+}
+
+TableArrayGroup& TableTemplate::GetArrayGroup(int id)
+{
+ return mArrayGroups[id];
+}
+
+TableArrayGroup* TableTemplate::AddArrayGroup(int row, int left, int right)
+{
+ // size_t max value: 18446744073709551615
+ // ^~~~~~~~~~~~~~~~~~~~ 20 chars
+ char name[20];
+ auto res = std::to_chars(std::begin(name), std::end(name), mArrayGroups.size());
+ std::string_view nameStr(name, res.ptr - name);
+
+ return AddArrayGroup(nameStr, row, left, right);
+}
+
+TableArrayGroup* TableTemplate::AddArrayGroup(std::string_view name, int row, int left, int right)
+{
+ assert(row >= 0 && row < GetTableHeight());
+ assert(left >= 0 && left < GetTableWidth());
+ assert(right >= 0 && right < GetTableWidth());
+
+ // TODO check for overlap
+
+ if (left > right) {
+ std::swap(left, right);
+ }
+
+ auto [DISCARD, inserted] = mName2ArrayGroups.insert(name, (int)mArrayGroups.size());
+ if (!inserted) {
+ return nullptr;
+ }
+
+ mArrayGroups.push_back(TableArrayGroup{
+ .Row = row,
+ .LeftCell = left,
+ .RightCell = right,
+ });
+ auto& ag = mArrayGroups.back();
+
+ for (int x = left; x <= right; x++) {
+ auto& cell = GetCell({ x, row });
+
+ // Update type
+ cell.Type = TableCell::ArrayParametricCell;
+
+ // Insert parameter name lookup
+ while (true) {
+ auto [DISCARD, inserted] = ag.mName2Cell.insert(cell.Content, x);
+ if (inserted) {
+ break;
+ }
+
+ cell.Content += "-";
+ }
+ }
+
+ return &ag;
+}
+
+bool TableTemplate::UpdateArrayGroupName(std::string_view oldName, std::string_view newName)
+{
+ return ::UpdateElementName(mName2ArrayGroups, oldName, newName);
+}
+
+bool TableTemplate::ExtendArrayGroupLeft(int id, int n)
+{
+ assert(n > 0);
+
+ auto& ag = mArrayGroups[id];
+ ag.LeftCell -= n;
+
+ return false;
+}
+
+bool TableTemplate::ExtendArrayGroupRight(int id, int n)
+{
+ assert(n > 0);
+
+ auto& ag = mArrayGroups[id];
+ ag.RightCell += n;
+
+ return false;
+}
+
+TableCell* TableTemplate::FindCell(std::string_view name)
+{
+ auto iter = mName2Parameters.find(name);
+ if (iter != mName2Parameters.end()) {
+ return &mCells[iter.value()];
+ } else {
+ return nullptr;
+ }
+}
+
+TableArrayGroup* TableTemplate::FindArrayGroup(std::string_view name)
+{
+ auto iter = mName2ArrayGroups.find(name);
+ if (iter != mName2ArrayGroups.end()) {
+ return &mArrayGroups[iter.value()];
+ } else {
+ return nullptr;
+ }
+}
+
+TableTemplate::MergeCellsResult TableTemplate::MergeCells(Vec2i topLeft, Vec2i bottomRight)
+{
+ auto SortTwo = [](int& a, int& b) {
+ if (a > b) {
+ std::swap(a, b);
+ }
+ };
+ SortTwo(topLeft.x, bottomRight.x);
+ SortTwo(topLeft.y, bottomRight.y);
+
+ auto ResetProgress = [&]() {
+ for (int y = topLeft.y; y < bottomRight.y; ++y) {
+ for (int x = topLeft.x; x < bottomRight.x; ++x) {
+ auto& cell = GetCell({ x, y });
+ cell.PrimaryCellLocation = { -1, -1 };
+ }
+ }
+ };
+
+ for (int y = topLeft.y; y < bottomRight.y; ++y) {
+ for (int x = topLeft.x; x < bottomRight.x; ++x) {
+ auto& cell = GetCell({ x, y });
+ if (cell.IsMergedCell()) {
+ ResetProgress();
+ return MCR_CellAlreadyMerged;
+ }
+
+ cell.PrimaryCellLocation = topLeft;
+ }
+ }
+
+ auto& primaryCell = GetCell(topLeft);
+ primaryCell.SpanX = bottomRight.x - topLeft.x;
+ primaryCell.SpanY = bottomRight.y - topLeft.y;
+
+ return MCR_Success;
+}
+
+TableTemplate::BreakCellsResult TableTemplate::BreakCells(Vec2i topLeft)
+{
+ auto& primaryCell = GetCell(topLeft);
+ if (!primaryCell.IsMergedCell()) {
+ return BCR_CellNotMerged;
+ }
+
+ for (int dy = 0; dy < primaryCell.SpanY; ++dy) {
+ for (int dx = 0; dx < primaryCell.SpanX; ++dx) {
+ auto& cell = GetCell({ topLeft.x + dx, topLeft.y + dy });
+ cell.PrimaryCellLocation = { -1, -1 };
+ }
+ }
+
+ primaryCell.SpanX = 1;
+ primaryCell.SpanY = 1;
+
+ return BCR_Success;
+}
+
+lxw_workbook* TableTemplate::InstantiateToExcelWorkbook(const TableInstantiationParameters& params) const
+{
+ auto workbook = workbook_new("Table.xlsx");
+ InstantiateToExcelWorksheet(workbook, params);
+ return workbook;
+}
+
+lxw_worksheet* TableTemplate::InstantiateToExcelWorksheet(lxw_workbook* workbook, const TableInstantiationParameters& params) const
+{
+ auto worksheet = workbook_add_worksheet(workbook, "CpltExport.xlsx");
+
+ // Map: row number -> length of generated ranges
+ std::map<int, int> generatedRanges;
+
+ for (size_t i = 0; i < mArrayGroups.size(); ++i) {
+ auto& info = mArrayGroups[i];
+ auto& param = params.ArrayGroups[i];
+
+ auto iter = generatedRanges.find(i);
+ if (iter != generatedRanges.end()) {
+ int available = iter->second;
+ if (available >= param.size()) {
+ // Current space is enough to fit in this array group, skip
+ continue;
+ }
+ }
+
+ // Not enough space to fit in this array group, update (or insert) the appropriate amount of generated rows
+ int row = i;
+ int count = param.size();
+ generatedRanges.try_emplace(row, count);
+ }
+
+ auto GetOffset = [&](int y) -> int {
+ // std::find_if <values less than y>
+ int verticalOffset = 0;
+ for (auto it = generatedRanges.begin(); it != generatedRanges.end() && it->first < y; ++it) {
+ verticalOffset += it->second;
+ }
+ return verticalOffset;
+ };
+
+ auto WriteCell = [&](int row, int col, const TableCell& cell, const char* text) -> void {
+ if (cell.IsPrimaryCell()) {
+ int lastRow = row + cell.SpanY - 1;
+ int lastCol = col + cell.SpanX - 1;
+ // When both `string` and `format` are null, the top-left cell contents are untouched (what we just wrote in the above switch)
+ worksheet_merge_range(worksheet, row, col, lastRow, lastCol, text, nullptr);
+ } else {
+ worksheet_write_string(worksheet, row, col, text, nullptr);
+ }
+ };
+
+ // Write/instantiate all array groups
+ for (size_t i = 0; i < mArrayGroups.size(); ++i) {
+ auto& groupInfo = mArrayGroups[i];
+ auto& groupParams = params.ArrayGroups[i];
+
+ int rowCellCount = groupInfo.GetCount();
+ int rowCount = groupParams.size();
+ int baseRowIdx = groupInfo.Row + GetOffset(groupInfo.Row);
+
+ // For each row that would be generated
+ for (int rowIdx = 0; rowIdx < rowCount; ++rowIdx) {
+ auto& row = groupParams[rowIdx];
+
+ // For each cell in the row
+ for (int rowCellIdx = 0; rowCellIdx < rowCellCount; ++rowCellIdx) {
+ // TODO support merged cells in array groups
+ worksheet_write_string(worksheet, baseRowIdx + rowIdx, rowCellIdx, row[rowCellIdx].c_str(), nullptr);
+ }
+ }
+ }
+
+ int tableWidth = GetTableWidth();
+ int tableHeight = GetTableHeight();
+
+ // Write all regular and singular parameter cells
+ for (int y = 0; y < tableHeight; ++y) {
+ for (int x = 0; x < tableWidth; ++x) {
+ auto& cell = GetCell({ x, y });
+
+ if (!cell.IsDataHoldingCell()) {
+ continue;
+ }
+
+ switch (cell.Type) {
+ case TableCell::ConstantCell: {
+ int row = y + GetOffset(y);
+ int col = x;
+
+ WriteCell(row, col, cell, cell.Content.c_str());
+ } break;
+
+ case TableCell::SingularParametricCell: {
+ int row = y + GetOffset(y);
+ int col = x;
+
+ auto iter = params.SingularCells.find({ x, y });
+ if (iter != params.SingularCells.end()) {
+ WriteCell(row, col, cell, iter.value().c_str());
+ }
+ } break;
+
+ // See loop above that processes whole array groups at the same time
+ case TableCell::ArrayParametricCell: break;
+ }
+ }
+ }
+
+ return worksheet;
+}
+
+class TableTemplate::Private
+{
+public:
+ template <class TTableTemplate, class TProxy>
+ static void OperateStream(TTableTemplate& table, TProxy& proxy)
+ {
+ proxy.template ObjectAdapted<DataStreamAdapters::Vector<>>(table.mColumnWidths);
+ proxy.template ObjectAdapted<DataStreamAdapters::Vector<>>(table.mRowHeights);
+ proxy.template ObjectAdapted<DataStreamAdapters::Vector<>>(table.mCells);
+ proxy.template ObjectAdapted<DataStreamAdapters::Vector<>>(table.mArrayGroups);
+ proxy.template ObjectAdapted<DataStreamAdapters::TslArrayMap<>>(table.mName2Parameters);
+ proxy.template ObjectAdapted<DataStreamAdapters::TslArrayMap<>>(table.mName2ArrayGroups);
+ }
+};
+
+void TableTemplate::ReadFromDataStream(InputDataStream& stream)
+{
+ Private::OperateStream(*this, stream);
+}
+
+void TableTemplate::WriteToDataStream(OutputDataStream& stream) const
+{
+ Private::OperateStream(*this, stream);
+}