Files
create/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp
2025-03-16 17:18:23 -05:00

412 lines
17 KiB
C++

/***************************************************************************
* Copyright (c) 2015 Yorik van Havre <yorik@uncreated.net> *
* Copyright (c) 2016 WandererFan <wandererfan@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <iomanip>
#include <sstream>
#include <boost_regex.hpp>
#endif
#include <App/Property.h>
#include <Base/Console.h>
#include <Base/Tools.h>
#include <Mod/Spreadsheet/App/Cell.h>
#include <Mod/Spreadsheet/App/Sheet.h>
#include "DrawUtil.h"
#include "Preferences.h"
#include "DrawViewSpreadsheet.h"
using namespace TechDraw;
//===========================================================================
// DrawViewSpreadsheet
//===========================================================================
PROPERTY_SOURCE(TechDraw::DrawViewSpreadsheet, TechDraw::DrawViewSymbol)
DrawViewSpreadsheet::DrawViewSpreadsheet()
{
static const char *vgroup = "Spreadsheet";
ADD_PROPERTY_TYPE(Source ,(nullptr), vgroup, App::Prop_None, "Spreadsheet to view");
Source.setScope(App::LinkScope::Global);
ADD_PROPERTY_TYPE(CellStart ,("A1"), vgroup, App::Prop_None, "The top left cell of the range to display");
ADD_PROPERTY_TYPE(CellEnd ,("B2"), vgroup, App::Prop_None, "The bottom right cell of the range to display");
ADD_PROPERTY_TYPE(Font ,(Preferences::labelFont().c_str()),
vgroup, App::Prop_None, "The name of the font to use");
ADD_PROPERTY_TYPE(TextColor, (0.0f, 0.0f, 0.0f), vgroup, App::Prop_None, "The default color of the text and lines");
ADD_PROPERTY_TYPE(TextSize, (12.0), vgroup, App::Prop_None, "The size of the text");
ADD_PROPERTY_TYPE(LineWidth, (0.35), vgroup, App::Prop_None, "The thickness of the cell lines");
ADD_PROPERTY_TYPE(Owner, (nullptr), vgroup, (App::PropertyType)(App::Prop_None),
"Feature to which this sheet is attached");
EditableTexts.setStatus(App::Property::Hidden, true);
}
DrawViewSpreadsheet::~DrawViewSpreadsheet()
{
}
short DrawViewSpreadsheet::mustExecute() const
{
if (!isRestoring()) {
if (
Source.isTouched() ||
CellStart.isTouched() ||
CellEnd.isTouched() ||
Font.isTouched() ||
TextSize.isTouched() ||
TextColor.isTouched() ||
LineWidth.isTouched()
) {
return 1;
}
}
return TechDraw::DrawView::mustExecute();
}
void DrawViewSpreadsheet::onChanged(const App::Property* prop)
{
TechDraw::DrawView::onChanged(prop);
}
App::DocumentObjectExecReturn *DrawViewSpreadsheet::execute()
{
App::DocumentObject* link = Source.getValue();
std::string scellstart = CellStart.getValue();
std::string scellend = CellEnd.getValue();
if (!link)
return new App::DocumentObjectExecReturn("No spreadsheet linked");
if (!link->isDerivedFrom<Spreadsheet::Sheet>())
return new App::DocumentObjectExecReturn("The linked object is not a spreadsheet");
if (scellstart.empty() || scellend.empty())
return new App::DocumentObjectExecReturn("Empty cell value");
Symbol.setValue(getSheetImage());
overrideKeepUpdated(false);
return TechDraw::DrawView::execute();
}
std::vector <std::string> DrawViewSpreadsheet::getAvailColumns()
{
// builds a list of available columns: A, B, ... Y, Z, AA, AB, ... ZY, ZZ.
const std::string alphabet [] {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
std::vector <std::string> availcolumns { std::begin (alphabet), std::end (alphabet) };
for (const std::string &left : alphabet)
for (const std::string &right : alphabet)
availcolumns.push_back(left + right);
return availcolumns;
}
std::string DrawViewSpreadsheet::getSVGHead()
{
return std::string("<svg\n") +
std::string(" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"\n") +
std::string(" xmlns:freecad=\"https://www.freecad.org/wiki/index.php?title=Svg_Namespace\">\n");
}
std::string DrawViewSpreadsheet::getSVGTail()
{
return "\n</svg>";
}
std::string DrawViewSpreadsheet::getSheetImage()
{
App::DocumentObject* link = Source.getValue();
link->recomputeFeature(); //make sure s/s is up to date
std::string scellstart = CellStart.getValue();
std::string scellend = CellEnd.getValue();
//s/s columns are A, B,C, ... ZX, ZY, ZZ
//lower case characters are not valid
transform(scellstart.begin(), scellstart.end(), scellstart.begin(), ::toupper);
transform(scellend.begin(), scellend.end(), scellend.begin(), ::toupper);
std::string colPart;
std::string rowPart;
boost::regex re{"([A-Z]*)([0-9]*)"};
boost::smatch what;
int iRowStart = 0, iRowEnd = 0;
std::string sColStart, sColEnd;
if (boost::regex_search(scellstart, what, re)) {
if (what.size() < 3) {
Base::Console().Error("%s - start cell (%s) is invalid\n", getNameInDocument(),
CellStart.getValue());
return std::string();
}
colPart = what[1];
sColStart = colPart;
rowPart = what[2];
try {
iRowStart = std::stoi(rowPart);
}
catch (...) {
Base::Console().Error("%s - start cell (%s) invalid row\n",
getNameInDocument(), rowPart.c_str());
return std::string();
}
}
if (boost::regex_search(scellend, what, re)) {
if (what.size() < 3) {
Base::Console().Error("%s - end cell (%s) is invalid\n", getNameInDocument(), CellEnd.getValue());
} else {
colPart = what[1];
sColEnd = colPart;
rowPart = what[2];
try {
iRowEnd = std::stoi(rowPart);
}
catch (...) {
Base::Console().Error("%s - end cell (%s) invalid row\n",
getNameInDocument(), rowPart.c_str());
return std::string();
}
}
}
const std::vector <std::string> availcolumns = getAvailColumns();
//validate range start column in sheet's available columns
int iAvailColStart = colInList(availcolumns, sColStart);
if (iAvailColStart < 0) { //not found range start column in availcolumns list
Base::Console().Error("DVS - %s - start Column (%s) is invalid\n",
getNameInDocument(), sColStart.c_str());
return std::string();
}
//validate range end column in sheet's available columns
int iAvailColEnd = colInList(availcolumns, sColEnd);
if (iAvailColEnd < 0) {
Base::Console().Error("DVS - %s - end Column (%s) is invalid\n",
getNameInDocument(), sColEnd.c_str());
return std::string();
}
//check for logical range
if ( (iAvailColStart > iAvailColEnd) ||
(iRowStart > iRowEnd) ) {
Base::Console().Error("%s - cell range is illogical\n", getNameInDocument());
return std::string();
}
// build row and column ranges
std::vector<std::string> validColNames;
std::vector<int> validRowNumbers;
int iCol = iAvailColStart;
for (; iCol <= iAvailColEnd; iCol++) {
validColNames.push_back(availcolumns.at(iCol));
}
int iRow = iRowStart;
for ( ; iRow <= iRowEnd ; iRow++) {
validRowNumbers.push_back(iRow);
}
// create the SVG code
std::stringstream result;
result << getSVGHead();
std::string ViewName = Label.getValue();
Base::Color c = TextColor.getValue();
result << "<g id=\"" << ViewName << "\">" << std::endl;
// fill the cells
float rowoffset = 0.0;
float coloffset = 0.0;
float cellheight = 100;
float cellwidth = 100;
std::string celltext;
Spreadsheet::Sheet* sheet = static_cast<Spreadsheet::Sheet*>(link);
std::vector<std::string> skiplist;
for (std::vector<std::string>::const_iterator col = validColNames.begin();
col != validColNames.end(); ++col) {
// create a group for each column
result << " <g id=\"" << ViewName << "_col" << (*col) << "\">" << std::endl;
for (std::vector<int>::const_iterator row = validRowNumbers.begin();
row != validRowNumbers.end(); ++row) {
// get cell size
std::stringstream srow;
srow << (*row);
App::CellAddress address((*col) + srow.str());
cellwidth = sheet->getColumnWidth(address.col());
cellheight = sheet->getRowHeight(address.row());
celltext = "";
Spreadsheet::Cell* cell = sheet->getCell(address);
// get the text
App::Property* prop = sheet->getPropertyByName(address.toString().c_str());
std::stringstream field;
if (prop && cell) {
if (prop->isDerivedFrom<App::PropertyQuantity>()) {
auto contentAsQuantity = static_cast<App::PropertyQuantity*>(prop)->getQuantityValue();
field << contentAsQuantity.getUserString();
} else if (prop->isDerivedFrom<App::PropertyFloat>() ||
prop->isDerivedFrom<App::PropertyInteger>()) {
std::string temp = cell->getFormattedQuantity();
DrawUtil::encodeXmlSpecialChars(temp);
field << temp;
} else if (prop->isDerivedFrom<App::PropertyString>()) {
std::string temp = static_cast<App::PropertyString*>(prop)->getValue();
DrawUtil::encodeXmlSpecialChars(temp);
field << temp;
} else {
Base::Console().Error("DVSS: Unknown property type\n");
}
celltext = field.str();
}
// get colors, style, alignment and span
int alignment = 0;
std::string bcolor = "none";
std::string fcolor = c.asHexString();
std::string textstyle;
if (cell) {
Base::Color f, b;
std::set<std::string> st;
int colspan, rowspan;
if (cell->getBackground(b)) {
bcolor = b.asHexString();
}
if (cell->getForeground(f)) {
fcolor = f.asHexString();
}
if (cell->getStyle(st)) {
for (std::set<std::string>::const_iterator i = st.begin(); i != st.end(); ++i) {
if ((*i) == "bold")
textstyle += "font-weight: bold; ";
else if ((*i) == "italic")
textstyle += "font-style: italic; ";
else if ((*i) == "underline")
textstyle += "text-decoration: underline; ";
}
}
if (cell->getSpans(rowspan, colspan)) {
for (int i=0; i<colspan; ++i) {
for (int j=0; j<rowspan; ++j) {
App::CellAddress nextcell(address.row()+j, address.col()+i);
if (i > 0)
cellwidth += sheet->getColumnWidth(nextcell.col());
if (j > 0)
cellheight += sheet->getRowHeight(nextcell.row());
if ( (i > 0) || (j > 0) )
skiplist.push_back(nextcell.toString());
}
}
}
cell->getAlignment(alignment);
}
// skip cell if found in skiplist
if (std::ranges::find(skiplist, address.toString()) == skiplist.end()) {
result << " <rect x=\"" << coloffset << "\" y=\"" << rowoffset << "\" width=\""
<< cellwidth << "\" height=\"" << cellheight << "\" style=\"fill:" << bcolor
<< ";stroke-width:" << LineWidth.getValue() / getScale()
<< ";stroke:" << c.asHexString() << ";\" />" << std::endl;
if (alignment & Spreadsheet::Cell::ALIGNMENT_LEFT)
result << " <text style=\"" << textstyle << "\" x=\""
<< coloffset + TextSize.getValue() / 2 << "\" y=\""
<< rowoffset + 0.75 * cellheight << "\" font-family=\"";
if (alignment & Spreadsheet::Cell::ALIGNMENT_HCENTER)
result << " <text text-anchor=\"middle\" style=\"" << textstyle << "\" x=\""
<< coloffset + cellwidth / 2 << "\" y=\""
<< rowoffset + 0.75 * cellheight << "\" font-family=\"";
if (alignment & Spreadsheet::Cell::ALIGNMENT_RIGHT)
result << " <text text-anchor=\"end\" style=\"" << textstyle << "\" x=\""
<< coloffset + (cellwidth - TextSize.getValue() / 2) << "\" y=\""
<< rowoffset + 0.75 * cellheight << "\" font-family=\"";
if ((alignment & Spreadsheet::Cell::ALIGNMENT_LEFT)
|| (alignment & Spreadsheet::Cell::ALIGNMENT_HCENTER)
|| (alignment & Spreadsheet::Cell::ALIGNMENT_RIGHT)) {
result << Font.getValue() << "\""
<< " font-size=\"" << TextSize.getValue() << "\""
<< " fill=\"" << fcolor << "\">" << celltext << "</text>" << std::endl;
}
if (!(alignment & Spreadsheet::Cell::ALIGNMENT_LEFT) &&
!(alignment & Spreadsheet::Cell::ALIGNMENT_RIGHT) &&
!(alignment & Spreadsheet::Cell::ALIGNMENT_HCENTER) ) {
// no horizontal alignment specified, so we will default to
// Spreadsheet::Cell::ALIGNMENT_LEFT
result << " <text style=\"" << textstyle << "\" x=\""
<< coloffset + TextSize.getValue() / 2 << "\" y=\""
<< rowoffset + 0.75 * cellheight << "\" font-family=\"";
result << Font.getValue() << "\""
<< " font-size=\"" << TextSize.getValue() << "\""
<< " fill=\"" << fcolor << "\">" << celltext << "</text>" << std::endl;
}
}
rowoffset += sheet->getRowHeight(address.row());
}
result << " </g>" << std::endl;
rowoffset = 0.0;
coloffset += cellwidth;
}
// close the containing group
result << "</g>" << std::endl;
result << getSVGTail();
return result.str();
}
//find index of column name "toFind" in "list" of column names
int DrawViewSpreadsheet::colInList(const std::vector<std::string>& list,
const std::string& toFind)
{
const auto match = std::ranges::find(list, toFind);
if (match == std::end(list)) {
return -1; // Error value
}
return match - std::begin(list);
}
// Python Drawing feature ---------------------------------------------------------
namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawViewSpreadsheetPython, TechDraw::DrawViewSpreadsheet)
template<> const char* TechDraw::DrawViewSpreadsheetPython::getViewProviderName() const {
return "TechDrawGui::ViewProviderSpreadsheet";
}
/// @endcond
// explicit template instantiation
template class TechDrawExport FeaturePythonT<TechDraw::DrawViewSpreadsheet>;
}