diff options
Diffstat (limited to 'app/source/Cplt/Model/Template/TableTemplate.cpp')
-rw-r--r-- | app/source/Cplt/Model/Template/TableTemplate.cpp | 591 |
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); +} |