Files
create/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp
Kacper Donat a72a63232a Base: Move App::Color to Base
Every basic data type is stored in Base module, color is standing out as
one that does not. Moving it to Base opens possibilities to integrate it
better with the rest of FreeCAD.
2025-02-17 21:10:26 +01: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::find(skiplist.begin(), skiplist.end(), 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)
{
auto match = std::find(std::begin(list), std::end(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>;
}