| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | #include <boost/tokenizer.hpp>
|
| | #include <boost/regex.hpp>
|
| | #include <deque>
|
| | #include <memory>
|
| | #include <sstream>
|
| | #include <tuple>
|
| | #include <list>
|
| | #include <map>
|
| | #include <string>
|
| | #include <set>
|
| | #include <vector>
|
| |
|
| | #include <App/Application.h>
|
| | #include <App/Document.h>
|
| | #include <App/DynamicProperty.h>
|
| | #include <App/ExpressionParser.h>
|
| | #include <App/FeaturePythonPyImp.h>
|
| | #include <Base/Exception.h>
|
| | #include <Base/FileInfo.h>
|
| | #include <Base/Reader.h>
|
| | #include <Base/Stream.h>
|
| |
|
| | #include "Sheet.h"
|
| | #include "SheetObserver.h"
|
| | #include "SheetPy.h"
|
| |
|
| |
|
| | FC_LOG_LEVEL_INIT("Spreadsheet", true, true)
|
| |
|
| | using namespace Base;
|
| | using namespace App;
|
| | using namespace Spreadsheet;
|
| |
|
| | PROPERTY_SOURCE(Spreadsheet::Sheet, App::DocumentObject)
|
| |
|
| | using DependencyList = boost::adjacency_list<
|
| | boost::vecS,
|
| | boost::vecS,
|
| | boost::directedS,
|
| | boost::no_property,
|
| | boost::no_property,
|
| | boost::no_property,
|
| | boost::listS
|
| | >;
|
| | using Traits = boost::graph_traits<DependencyList>;
|
| | using Vertex = Traits::vertex_descriptor;
|
| | using Edge = Traits::edge_descriptor;
|
| |
|
| | |
| | |
| |
|
| |
|
| | Sheet::Sheet()
|
| | : DocumentObject()
|
| | , props(PropertyContainer::dynamicProps)
|
| | , cells(this)
|
| | {
|
| | ADD_PROPERTY_TYPE(cells, (), "Spreadsheet", (PropertyType)(Prop_Hidden), "Cell contents");
|
| | ADD_PROPERTY_TYPE(
|
| | columnWidths,
|
| | (),
|
| | "Spreadsheet",
|
| | (PropertyType)(Prop_ReadOnly | Prop_Hidden | Prop_Output),
|
| | "Column widths"
|
| | );
|
| | ADD_PROPERTY_TYPE(
|
| | rowHeights,
|
| | (),
|
| | "Spreadsheet",
|
| | (PropertyType)(Prop_ReadOnly | Prop_Hidden | Prop_Output),
|
| | "Row heights"
|
| | );
|
| | ADD_PROPERTY_TYPE(
|
| | rowHeights,
|
| | (),
|
| | "Spreadsheet",
|
| | (PropertyType)(Prop_ReadOnly | Prop_Hidden),
|
| | "Row heights"
|
| | );
|
| | ExpressionEngine.expressionChanged.connect([this](const App::ObjectIdentifier&) {
|
| | this->updateBindings();
|
| | });
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | Sheet::~Sheet()
|
| | {
|
| | try {
|
| | clearAll();
|
| | }
|
| | catch (...) {
|
| |
|
| | Base::Console().error(
|
| | "clearAll() resulted in an exception when deleting the spreadsheet : %s\n",
|
| | getNameInDocument()
|
| | );
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::clearAll()
|
| | {
|
| | cells.clear();
|
| |
|
| | std::vector<std::string> propNames = props.getDynamicPropertyNames();
|
| |
|
| | for (const auto& propName : propNames) {
|
| | if (cells.isValidCellAddressName(propName.c_str())) {
|
| | this->removeDynamicProperty(propName.c_str());
|
| | }
|
| | }
|
| |
|
| | propAddress.clear();
|
| | cellErrors.clear();
|
| | columnWidths.clear();
|
| | rowHeights.clear();
|
| | }
|
| |
|
| |
|
| | bool Sheet::getCharsFromPrefs(char& delim, char& quote, char& escape, std::string& errMsg)
|
| | {
|
| | bool isValid = true;
|
| | ParameterGrp::handle group = App::GetApplication().GetParameterGroupByPath(
|
| | "User parameter:BaseApp/Preferences/Mod/Spreadsheet"
|
| | );
|
| | QString delimiter = QString::fromStdString(group->GetASCII("ImportExportDelimiter", "tab"));
|
| | QString quoteChar = QString::fromStdString(group->GetASCII("ImportExportQuoteCharacter", "\""));
|
| | QString escapeChar = QString::fromStdString(group->GetASCII("ImportExportEscapeCharacter", "\\"));
|
| |
|
| | delim = delimiter.size() == 1 ? delimiter[0].toLatin1() : '\0';
|
| | if (delimiter.compare(QLatin1String("tab"), Qt::CaseInsensitive) == 0
|
| | || delimiter.compare(QLatin1String("\\t"), Qt::CaseInsensitive) == 0) {
|
| | delim = '\t';
|
| | }
|
| | else if (delimiter.compare(QLatin1String("comma"), Qt::CaseInsensitive) == 0) {
|
| | delim = ',';
|
| | }
|
| | else if (delimiter.compare(QLatin1String("semicolon"), Qt::CaseInsensitive) == 0) {
|
| | delim = ';';
|
| | }
|
| | if (delim != '\0' && quoteChar.size() == 1 && escapeChar.size() == 1) {
|
| | quote = quoteChar[0].toLatin1();
|
| | escape = escapeChar[0].toLatin1();
|
| | }
|
| | else {
|
| | isValid = false;
|
| | std::string importExport = errMsg;
|
| | std::stringstream errStream;
|
| | errStream << "Invalid spreadsheet Import/Export parameter.\n";
|
| | if (delim == '\0') {
|
| | errStream
|
| | << "Unrecognized delimiter: " << delimiter.toStdString()
|
| | << " (recognized tokens: \\t, tab, semicolon, comma, or any single character)\n";
|
| | }
|
| | if (quoteChar.size() != 1) {
|
| | errStream << "Invalid quote character: " << quoteChar.toStdString()
|
| | << " (quote character must be one single character)\n";
|
| | }
|
| | if (escapeChar.size() != 1) {
|
| | errStream << "Invalid escape character: " << escapeChar.toStdString()
|
| | << " (escape character must be one single character)\n";
|
| | }
|
| | errStream << importExport << " not done.\n";
|
| | errMsg = errStream.str();
|
| | }
|
| | return isValid;
|
| | }
|
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | bool Sheet::importFromFile(const std::string& filename, char delimiter, char quoteChar, char escapeChar)
|
| | {
|
| | Base::FileInfo fi(filename);
|
| | Base::ifstream file(fi, std::ios::in);
|
| | int row = 0;
|
| |
|
| | PropertySheet::AtomicPropertyChange signaller(cells);
|
| |
|
| | clearAll();
|
| |
|
| | if (file.is_open()) {
|
| | std::string line;
|
| |
|
| | while (std::getline(file, line)) {
|
| | using namespace boost;
|
| |
|
| | try {
|
| | escaped_list_separator<char> e;
|
| | int col = 0;
|
| |
|
| | if (quoteChar) {
|
| | e = escaped_list_separator<char>(escapeChar, delimiter, quoteChar);
|
| | }
|
| | else {
|
| | e = escaped_list_separator<char>('\0', delimiter, '\0');
|
| | }
|
| |
|
| | tokenizer<escaped_list_separator<char>> tok(line, e);
|
| |
|
| | for (tokenizer<escaped_list_separator<char>>::iterator i = tok.begin();
|
| | i != tok.end();
|
| | ++i) {
|
| | if (!i->empty()) {
|
| | setCell(CellAddress(row, col), (*i).c_str());
|
| | }
|
| | col++;
|
| | }
|
| | }
|
| | catch (...) {
|
| | signaller.tryInvoke();
|
| | return false;
|
| | }
|
| |
|
| | ++row;
|
| | }
|
| | file.close();
|
| | signaller.tryInvoke();
|
| | return true;
|
| | }
|
| | else {
|
| | return false;
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | static void writeEscaped(std::string const& s, char quoteChar, char escapeChar, std::ostream& out)
|
| | {
|
| | out << quoteChar;
|
| | for (unsigned char c : s) {
|
| | if (c != quoteChar) {
|
| | out << c;
|
| | }
|
| | else {
|
| | out << escapeChar;
|
| | out << c;
|
| | }
|
| | }
|
| | out << quoteChar;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | bool Sheet::exportToFile(const std::string& filename, char delimiter, char quoteChar, char escapeChar) const
|
| | {
|
| | Base::ofstream file;
|
| | int prevRow = -1, prevCol = -1;
|
| |
|
| | Base::FileInfo fi(filename);
|
| | file.open(fi, std::ios::out | std::ios::ate | std::ios::binary);
|
| |
|
| | if (!file.is_open()) {
|
| | return false;
|
| | }
|
| |
|
| | auto usedCells = cells.getNonEmptyCells();
|
| | auto i = usedCells.begin();
|
| |
|
| | while (i != usedCells.end()) {
|
| | Property* prop = getProperty(*i);
|
| |
|
| | if (prevRow != -1 && prevRow != i->row()) {
|
| | for (int j = prevRow; j < i->row(); ++j) {
|
| | file << std::endl;
|
| | }
|
| | prevCol = usedCells.begin()->col();
|
| | }
|
| | if (prevCol != -1 && i->col() != prevCol) {
|
| | for (int j = prevCol; j < i->col(); ++j) {
|
| | file << delimiter;
|
| | }
|
| | }
|
| |
|
| | std::stringstream field;
|
| |
|
| | if (auto p = freecad_cast<PropertyQuantity*>(prop)) {
|
| | field << p->getValue();
|
| | }
|
| | else if (auto p = freecad_cast<PropertyFloat*>(prop)) {
|
| | field << p->getValue();
|
| | }
|
| | else if (auto p = freecad_cast<PropertyInteger*>(prop)) {
|
| | field << p->getValue();
|
| | }
|
| | else if (auto p = freecad_cast<PropertyString*>(prop)) {
|
| | field << p->getValue();
|
| | }
|
| | else {
|
| | assert(0);
|
| | }
|
| |
|
| | std::string str = field.str();
|
| |
|
| | if (quoteChar && str.find_first_of(std::string(quoteChar, delimiter)) != std::string::npos) {
|
| | writeEscaped(str, quoteChar, escapeChar, file);
|
| | }
|
| | else {
|
| | file << str;
|
| | }
|
| |
|
| | prevRow = i->row();
|
| | prevCol = i->col();
|
| | ++i;
|
| | }
|
| | file << std::endl;
|
| | file.close();
|
| |
|
| | return true;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | bool Sheet::mergeCells(const Range& range)
|
| | {
|
| | return cells.mergeCells(range.from(), range.to());
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::splitCell(CellAddress address)
|
| | {
|
| | cells.splitCell(address);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | Cell* Sheet::getCell(CellAddress address)
|
| | {
|
| | return cells.getValue(address);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | const Cell* Sheet::getCell(CellAddress address) const
|
| | {
|
| | return cells.getValue(address);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | Cell* Sheet::getNewCell(CellAddress address)
|
| | {
|
| | Cell* cell = getCell(address);
|
| |
|
| | if (!cell) {
|
| | cell = cells.createCell(address);
|
| | }
|
| |
|
| | return cell;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setCell(const char* address, const char* contents)
|
| | {
|
| | assert(address && contents);
|
| |
|
| | setCell(CellAddress(address), contents);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setCell(CellAddress address, const char* value)
|
| | {
|
| | assert(value);
|
| |
|
| |
|
| | if (*value == '\0') {
|
| | clear(address, false);
|
| | return;
|
| | }
|
| |
|
| | setContent(address, value);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | PyObject* Sheet::getPyObject()
|
| | {
|
| | if (PythonObject.is(Py::_None())) {
|
| |
|
| | PythonObject = Py::Object(new SheetPy(this), true);
|
| | }
|
| | return Py::new_reference_to(PythonObject);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | Property* Sheet::getProperty(CellAddress key) const
|
| | {
|
| | return props.getDynamicPropertyByName(key.toString(CellAddress::Cell::ShowRowColumn).c_str());
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | Property* Sheet::getProperty(const char* addr) const
|
| | {
|
| | return props.getDynamicPropertyByName(addr);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | bool Sheet::getCellAddress(const Property* prop, CellAddress& address)
|
| | {
|
| | std::map<const Property*, CellAddress>::const_iterator i = propAddress.find(prop);
|
| |
|
| | if (i != propAddress.end()) {
|
| | address = i->second;
|
| | return true;
|
| | }
|
| | return false;
|
| | }
|
| |
|
| | App::CellAddress Sheet::getCellAddress(const char* name, bool silent) const
|
| | {
|
| | return cells.getCellAddress(name, silent);
|
| | }
|
| |
|
| | App::Range Sheet::getRange(const char* name, bool silent) const
|
| | {
|
| | return cells.getRange(name, silent);
|
| | }
|
| |
|
| | std::tuple<App::CellAddress, App::CellAddress> Sheet::getUsedRange() const
|
| | {
|
| | return cells.getUsedRange();
|
| | }
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | std::map<int, int> Sheet::getColumnWidths() const
|
| | {
|
| | return columnWidths.getValues();
|
| | }
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | std::map<int, int> Sheet::getRowHeights() const
|
| | {
|
| | return rowHeights.getValues();
|
| | }
|
| |
|
| |
|
| | |
| | |
| |
|
| |
|
| | void Sheet::onSettingDocument()
|
| | {
|
| | cells.documentSet();
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | Property* Sheet::setFloatProperty(CellAddress key, double value)
|
| | {
|
| | std::string name = key.toString(CellAddress::Cell::ShowRowColumn);
|
| | Property* prop = props.getDynamicPropertyByName(name.c_str());
|
| | PropertyFloat* floatProp;
|
| |
|
| | if (!prop || !prop->is<PropertyFloat>()) {
|
| | if (prop) {
|
| | this->removeDynamicProperty(name.c_str());
|
| | propAddress.erase(prop);
|
| | }
|
| | floatProp = freecad_cast<PropertyFloat*>(addDynamicProperty(
|
| | "App::PropertyFloat",
|
| | name.c_str(),
|
| | nullptr,
|
| | nullptr,
|
| | Prop_ReadOnly | Prop_Hidden | Prop_NoPersist
|
| | ));
|
| | }
|
| | else {
|
| | floatProp = static_cast<PropertyFloat*>(prop);
|
| | }
|
| |
|
| | propAddress[floatProp] = key;
|
| | floatProp->setValue(value);
|
| |
|
| | return floatProp;
|
| | }
|
| |
|
| | Property* Sheet::setIntegerProperty(CellAddress key, long value)
|
| | {
|
| | std::string name = key.toString(CellAddress::Cell::ShowRowColumn);
|
| | Property* prop = props.getDynamicPropertyByName(name.c_str());
|
| | PropertyInteger* intProp;
|
| |
|
| | if (!prop || !prop->is<PropertyInteger>()) {
|
| | if (prop) {
|
| | this->removeDynamicProperty(name.c_str());
|
| | propAddress.erase(prop);
|
| | }
|
| | intProp = freecad_cast<PropertyInteger*>(addDynamicProperty(
|
| | "App::PropertyInteger",
|
| | name.c_str(),
|
| | nullptr,
|
| | nullptr,
|
| | Prop_ReadOnly | Prop_Hidden | Prop_NoPersist
|
| | ));
|
| | }
|
| | else {
|
| | intProp = static_cast<PropertyInteger*>(prop);
|
| | }
|
| |
|
| | propAddress[intProp] = key;
|
| | intProp->setValue(value);
|
| |
|
| | return intProp;
|
| | }
|
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | Property* Sheet::setQuantityProperty(CellAddress key, double value, const Base::Unit& unit)
|
| | {
|
| | std::string name = key.toString(CellAddress::Cell::ShowRowColumn);
|
| | Property* prop = props.getDynamicPropertyByName(name.c_str());
|
| | PropertySpreadsheetQuantity* quantityProp;
|
| |
|
| | if (!prop || !prop->is<PropertySpreadsheetQuantity>()) {
|
| | if (prop) {
|
| | this->removeDynamicProperty(name.c_str());
|
| | propAddress.erase(prop);
|
| | }
|
| | Property* p = addDynamicProperty(
|
| | "Spreadsheet::PropertySpreadsheetQuantity",
|
| | name.c_str(),
|
| | nullptr,
|
| | nullptr,
|
| | Prop_ReadOnly | Prop_Hidden | Prop_NoPersist
|
| | );
|
| | quantityProp = freecad_cast<PropertySpreadsheetQuantity*>(p);
|
| | }
|
| | else {
|
| | quantityProp = static_cast<PropertySpreadsheetQuantity*>(prop);
|
| | }
|
| |
|
| | propAddress[quantityProp] = key;
|
| | quantityProp->setValue(value);
|
| | quantityProp->setUnit(unit);
|
| |
|
| | cells.setComputedUnit(key, unit);
|
| |
|
| | return quantityProp;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | Property* Sheet::setStringProperty(CellAddress key, const std::string& value)
|
| | {
|
| | std::string name = key.toString(CellAddress::Cell::ShowRowColumn);
|
| | Property* prop = props.getDynamicPropertyByName(name.c_str());
|
| | PropertyString* stringProp = freecad_cast<PropertyString*>(prop);
|
| |
|
| | if (!stringProp) {
|
| | if (prop) {
|
| | this->removeDynamicProperty(name.c_str());
|
| | propAddress.erase(prop);
|
| | }
|
| | stringProp = freecad_cast<PropertyString*>(addDynamicProperty(
|
| | "App::PropertyString",
|
| | name.c_str(),
|
| | nullptr,
|
| | nullptr,
|
| | Prop_ReadOnly | Prop_Hidden | Prop_NoPersist
|
| | ));
|
| | }
|
| |
|
| | propAddress[stringProp] = key;
|
| | stringProp->setValue(value.c_str());
|
| |
|
| | return stringProp;
|
| | }
|
| |
|
| | Property* Sheet::setObjectProperty(CellAddress key, Py::Object object)
|
| | {
|
| | std::string name = key.toString(CellAddress::Cell::ShowRowColumn);
|
| | Property* prop = props.getDynamicPropertyByName(name.c_str());
|
| | PropertyPythonObject* pyProp = freecad_cast<PropertyPythonObject*>(prop);
|
| |
|
| | if (!pyProp) {
|
| | if (prop) {
|
| | this->removeDynamicProperty(name.c_str());
|
| | propAddress.erase(prop);
|
| | }
|
| | pyProp = freecad_cast<PropertyPythonObject*>(addDynamicProperty(
|
| | "App::PropertyPythonObject",
|
| | name.c_str(),
|
| | nullptr,
|
| | nullptr,
|
| | Prop_ReadOnly | Prop_Hidden | Prop_NoPersist
|
| | ));
|
| | }
|
| |
|
| | propAddress[pyProp] = key;
|
| | pyProp->setValue(object);
|
| |
|
| | return pyProp;
|
| | }
|
| |
|
| | struct CurrentAddressLock
|
| | {
|
| | CurrentAddressLock(int& r, int& c, const CellAddress& addr)
|
| | : row(r)
|
| | , col(c)
|
| | {
|
| | row = addr.row();
|
| | col = addr.col();
|
| | }
|
| | ~CurrentAddressLock()
|
| | {
|
| | row = -1;
|
| | col = -1;
|
| | }
|
| |
|
| | int& row;
|
| | int& col;
|
| | };
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::updateProperty(CellAddress key)
|
| | {
|
| | Cell* cell = getCell(key);
|
| |
|
| | if (cell) {
|
| | std::unique_ptr<Expression> output;
|
| | const Expression* input = cell->getExpression();
|
| |
|
| | if (input) {
|
| | CurrentAddressLock lock(currentRow, currentCol, key);
|
| | output.reset(input->eval());
|
| | }
|
| | else {
|
| | std::string s;
|
| |
|
| | if (cell->getStringContent(s) && !s.empty()) {
|
| | output = std::make_unique<StringExpression>(this, s);
|
| | }
|
| | else {
|
| | this->removeDynamicProperty(key.toString().c_str());
|
| | return;
|
| | }
|
| | }
|
| |
|
| | |
| |
|
| | auto number = freecad_cast<NumberExpression*>(output.get());
|
| | if (number) {
|
| | long l;
|
| | auto constant = freecad_cast<ConstantExpression*>(output.get());
|
| | if (constant && !constant->isNumber()) {
|
| | Base::PyGILStateLocker lock;
|
| | setObjectProperty(key, constant->getPyValue());
|
| | }
|
| | else if (number->getUnit() != Unit::One) {
|
| | setQuantityProperty(key, number->getValue(), number->getUnit());
|
| | }
|
| | else if (number->isInteger(&l)) {
|
| | setIntegerProperty(key, l);
|
| | }
|
| | else {
|
| | setFloatProperty(key, number->getValue());
|
| | }
|
| | }
|
| | else {
|
| | auto str_expr = freecad_cast<StringExpression*>(output.get());
|
| | if (str_expr) {
|
| | setStringProperty(key, str_expr->getText().c_str());
|
| | }
|
| | else {
|
| | Base::PyGILStateLocker lock;
|
| | auto py_expr = freecad_cast<PyObjectExpression*>(output.get());
|
| | if (py_expr) {
|
| | setObjectProperty(key, py_expr->getPyValue());
|
| | }
|
| | else {
|
| | setObjectProperty(key, Py::Object());
|
| | }
|
| | }
|
| | }
|
| | }
|
| | else {
|
| | clear(key);
|
| | }
|
| |
|
| | cellUpdated(key);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | Property* Sheet::getPropertyByName(const char* name) const
|
| | {
|
| | CellAddress addr = getCellAddress(name, true);
|
| | Property* prop = nullptr;
|
| | if (addr.isValid()) {
|
| | prop = getProperty(addr);
|
| | }
|
| | if (prop) {
|
| | return prop;
|
| | }
|
| | else {
|
| | return DocumentObject::getPropertyByName(name);
|
| | }
|
| | }
|
| |
|
| | Property* Sheet::getDynamicPropertyByName(const char* name) const
|
| | {
|
| | CellAddress addr = getCellAddress(name, true);
|
| | Property* prop = nullptr;
|
| | if (addr.isValid()) {
|
| | prop = getProperty(addr);
|
| | }
|
| | if (prop) {
|
| | return prop;
|
| | }
|
| | else {
|
| | return DocumentObject::getDynamicPropertyByName(name);
|
| | }
|
| | }
|
| |
|
| | void Sheet::getPropertyNamedList(std::vector<std::pair<const char*, Property*>>& List) const
|
| | {
|
| | DocumentObject::getPropertyNamedList(List);
|
| | List.reserve(List.size() + cells.aliasProp.size());
|
| | for (auto& v : cells.aliasProp) {
|
| | auto prop = getProperty(v.first);
|
| | if (prop) {
|
| | List.emplace_back(v.second.c_str(), prop);
|
| | }
|
| | }
|
| | }
|
| |
|
| | void Sheet::visitProperties(const std::function<void(App::Property*)>& visitor) const
|
| | {
|
| | DocumentObject::visitProperties(visitor);
|
| | for (const auto& v : cells.aliasProp) {
|
| | auto prop = getProperty(v.first);
|
| | if (prop != nullptr) {
|
| | visitor(prop);
|
| | }
|
| | };
|
| | }
|
| |
|
| | void Sheet::touchCells(Range range)
|
| | {
|
| | do {
|
| | cells.setDirty(*range);
|
| | } while (range.next());
|
| | }
|
| |
|
| | void Sheet::recomputeCells(Range range)
|
| | {
|
| | do {
|
| | recomputeCell(*range);
|
| | } while (range.next());
|
| | }
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::recomputeCell(CellAddress p)
|
| | {
|
| | Cell* cell = cells.getValue(p);
|
| |
|
| | try {
|
| | if (cell && cell->hasException()) {
|
| | std::string content;
|
| | cell->getStringContent(content);
|
| | cell->setContent(content.c_str());
|
| | }
|
| |
|
| | updateProperty(p);
|
| |
|
| | if (!cell || !cell->hasException()) {
|
| | cells.clearDirty(p);
|
| | cellErrors.erase(p);
|
| | }
|
| | }
|
| | catch (const Base::Exception& e) {
|
| | QString msg = QStringLiteral("ERR: %1").arg(QString::fromUtf8(e.what()));
|
| |
|
| | setStringProperty(p, msg.toStdString());
|
| | if (cell) {
|
| | cell->setException(e.what());
|
| | }
|
| | else {
|
| | e.reportException();
|
| | }
|
| |
|
| |
|
| | cellErrors.insert(p);
|
| | cellUpdated(p);
|
| |
|
| | if (e.isDerivedFrom<Base::AbortException>()) {
|
| | throw;
|
| | }
|
| | }
|
| | }
|
| |
|
| | PropertySheet::BindingType Sheet::getCellBinding(
|
| | Range& range,
|
| | ExpressionPtr* pStart,
|
| | ExpressionPtr* pEnd,
|
| | App::ObjectIdentifier* pTarget
|
| | ) const
|
| | {
|
| | range.normalize();
|
| | do {
|
| | CellAddress addr = *range;
|
| | for (const auto& r : boundRanges) {
|
| | if (addr.row() >= r.from().row() && addr.row() <= r.to().row()
|
| | && addr.col() >= r.from().col() && addr.col() <= r.to().col()) {
|
| | auto res = cells.getBinding(r, pStart, pEnd, pTarget);
|
| | if (res != PropertySheet::BindingNone) {
|
| | range = r;
|
| | return res;
|
| | }
|
| | }
|
| | }
|
| | } while (range.next());
|
| | return PropertySheet::BindingNone;
|
| | }
|
| |
|
| | static inline unsigned _getBorder(
|
| | const Sheet* sheet,
|
| | const std::vector<App::Range>& ranges,
|
| | const App::CellAddress& address
|
| | )
|
| | {
|
| | unsigned flags = 0;
|
| | int rows, cols;
|
| | sheet->getSpans(address, rows, cols);
|
| | --rows;
|
| | --cols;
|
| | for (auto& range : ranges) {
|
| | auto from = range.from();
|
| | auto to = range.to();
|
| | if (address.row() < from.row() || address.row() + rows > to.row()
|
| | || address.col() < from.col() || address.col() + cols > to.col()) {
|
| | continue;
|
| | }
|
| | if (address.row() == from.row()) {
|
| | flags |= Sheet::BorderTop;
|
| | }
|
| | if (address.row() == to.row() || address.row() + rows == to.row()) {
|
| | flags |= Sheet::BorderBottom;
|
| | }
|
| | if (address.col() == from.col()) {
|
| | flags |= Sheet::BorderLeft;
|
| | }
|
| | if (address.col() == to.col() || address.col() + cols == to.col()) {
|
| | flags |= Sheet::BorderRight;
|
| | }
|
| | if (flags == Sheet::BorderAll) {
|
| | break;
|
| | }
|
| | }
|
| | return flags;
|
| | }
|
| |
|
| | unsigned Sheet::getCellBindingBorder(App::CellAddress address) const
|
| | {
|
| | return _getBorder(this, boundRanges, address);
|
| | }
|
| |
|
| | void Sheet::updateBindings()
|
| | {
|
| | std::set<Range> oldRangeSet(boundRanges.begin(), boundRanges.end());
|
| | std::set<Range> newRangeSet;
|
| | std::set<Range> rangeSet;
|
| | boundRanges.clear();
|
| | for (const auto& v : ExpressionEngine.getExpressions()) {
|
| | CellAddress from, to;
|
| | if (!cells.isBindingPath(v.first, &from, &to)) {
|
| | continue;
|
| | }
|
| | App::Range range(from, to, true);
|
| | if (!oldRangeSet.erase(range)) {
|
| | newRangeSet.insert(range);
|
| | }
|
| | rangeSet.insert(range);
|
| | }
|
| | boundRanges.reserve(rangeSet.size());
|
| | boundRanges.insert(boundRanges.end(), rangeSet.begin(), rangeSet.end());
|
| | for (const auto& range : oldRangeSet) {
|
| | rangeUpdated(range);
|
| | }
|
| | for (const auto& range : newRangeSet) {
|
| | rangeUpdated(range);
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | DocumentObjectExecReturn* Sheet::execute()
|
| | {
|
| | updateBindings();
|
| |
|
| |
|
| | std::set<CellAddress> dirtyCells = cells.getDirty();
|
| |
|
| |
|
| | for (auto cellError : cellErrors) {
|
| | cells.recomputeDependencies(cellError);
|
| | dirtyCells.insert(cellError);
|
| | }
|
| |
|
| | DependencyList graph;
|
| | std::map<CellAddress, Vertex> VertexList;
|
| | std::map<Vertex, CellAddress> VertexIndexList;
|
| | std::deque<CellAddress> workQueue(dirtyCells.begin(), dirtyCells.end());
|
| | while (!workQueue.empty()) {
|
| | CellAddress currPos = workQueue.front();
|
| | workQueue.pop_front();
|
| |
|
| |
|
| | auto res = VertexList.emplace(currPos, Vertex());
|
| | if (res.second) {
|
| | res.first->second = add_vertex(graph);
|
| | VertexIndexList[res.first->second] = currPos;
|
| | }
|
| |
|
| |
|
| | for (auto& dep : providesTo(currPos)) {
|
| | auto resDep = VertexList.emplace(dep, Vertex());
|
| | if (resDep.second) {
|
| | resDep.first->second = add_vertex(graph);
|
| | VertexIndexList[resDep.first->second] = dep;
|
| | if (dirtyCells.insert(dep).second) {
|
| | workQueue.push_back(dep);
|
| | }
|
| | }
|
| |
|
| | add_edge(res.first->second, resDep.first->second, graph);
|
| | }
|
| | }
|
| |
|
| | std::list<Vertex> make_order;
|
| |
|
| | try {
|
| | boost::topological_sort(graph, std::front_inserter(make_order));
|
| |
|
| | FC_LOG("recomputing " << getFullName());
|
| | for (auto& pos : make_order) {
|
| | const auto& addr = VertexIndexList[pos];
|
| | FC_TRACE(addr.toString());
|
| | recomputeCell(addr);
|
| | }
|
| | }
|
| | catch (std::exception&) {
|
| | for (auto& v : VertexList) {
|
| | Cell* cell = cells.getValue(v.first);
|
| |
|
| | if (cell) {
|
| | cellErrors.insert(v.first);
|
| | cell->setException("Pending computation due to cyclic dependency", true);
|
| | cellUpdated(v.first);
|
| | }
|
| | }
|
| |
|
| |
|
| | while (!dirtyCells.empty()) {
|
| |
|
| | std::deque<CellAddress> workQueue;
|
| | DependencyList graph;
|
| | std::map<CellAddress, Vertex> VertexList;
|
| | std::map<Vertex, CellAddress> VertexIndexList;
|
| |
|
| | CellAddress currentAddr = *dirtyCells.begin();
|
| | workQueue.push_back(currentAddr);
|
| | dirtyCells.erase(dirtyCells.begin());
|
| |
|
| | while (!workQueue.empty()) {
|
| | CellAddress currPos = workQueue.front();
|
| | workQueue.pop_front();
|
| |
|
| |
|
| | auto res = VertexList.emplace(currPos, Vertex());
|
| | if (res.second) {
|
| | res.first->second = add_vertex(graph);
|
| | VertexIndexList[res.first->second] = currPos;
|
| | }
|
| |
|
| |
|
| | for (auto& dep : providesTo(currPos)) {
|
| | auto resDep = VertexList.emplace(dep, Vertex());
|
| | if (resDep.second) {
|
| | resDep.first->second = add_vertex(graph);
|
| | VertexIndexList[resDep.first->second] = dep;
|
| | workQueue.push_back(dep);
|
| | dirtyCells.erase(dep);
|
| | }
|
| |
|
| | add_edge(res.first->second, resDep.first->second, graph);
|
| | }
|
| | }
|
| |
|
| | std::list<Vertex> make_order;
|
| | try {
|
| | boost::topological_sort(graph, std::front_inserter(make_order));
|
| | }
|
| | catch (std::exception&) {
|
| |
|
| | Base::Console().error(
|
| | "Cyclic dependency detected in spreadsheet : %s\n",
|
| | getNameInDocument()
|
| | );
|
| | std::ostringstream ss;
|
| | ss << "Cyclic dependency";
|
| | int count = 0;
|
| | for (auto& v : VertexList) {
|
| | if (count++ % 20 == 0) {
|
| | ss << std::endl;
|
| | }
|
| | else {
|
| | ss << ", ";
|
| | }
|
| | ss << v.first.toString();
|
| | }
|
| | std::string msg = ss.str();
|
| | for (auto& v : VertexList) {
|
| | Cell* cell = cells.getValue(v.first);
|
| | if (cell) {
|
| | cell->setException(msg.c_str(), true);
|
| | cellUpdated(v.first);
|
| | }
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | const std::set<int>& dirtyColumns = columnWidths.getDirty();
|
| |
|
| | for (int dirtyColumn : dirtyColumns) {
|
| | columnWidthChanged(dirtyColumn, columnWidths.getValue(dirtyColumn));
|
| | }
|
| |
|
| |
|
| | const std::set<int>& dirtyRows = rowHeights.getDirty();
|
| |
|
| | for (int dirtyRow : dirtyRows) {
|
| | rowHeightChanged(dirtyRow, rowHeights.getValue(dirtyRow));
|
| | }
|
| |
|
| |
|
| | rowHeights.clearDirty();
|
| | columnWidths.clearDirty();
|
| |
|
| | if (cellErrors.empty()) {
|
| | return DocumentObject::StdReturn;
|
| | }
|
| | else {
|
| | return new DocumentObjectExecReturn("One or more cells failed contains errors.", this);
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | short Sheet::mustExecute() const
|
| | {
|
| | if (!cellErrors.empty() || cells.isDirty()) {
|
| | return 1;
|
| | }
|
| | return DocumentObject::mustExecute();
|
| | }
|
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::clear(CellAddress address, bool )
|
| | {
|
| | if (auto cell = getCell(address)) {
|
| |
|
| | std::string aliasStr;
|
| | if (cell->getAlias(aliasStr)) {
|
| | this->removeDynamicProperty(aliasStr.c_str());
|
| | }
|
| | cells.clear(address);
|
| | }
|
| |
|
| | std::string addr = address.toString();
|
| | if (auto prop = props.getDynamicPropertyByName(addr.c_str())) {
|
| | propAddress.erase(prop);
|
| | this->removeDynamicProperty(addr.c_str());
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::getSpans(CellAddress address, int& rows, int& cols) const
|
| | {
|
| | return cells.getSpans(address, rows, cols);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | bool Sheet::isMergedCell(CellAddress address) const
|
| | {
|
| | return cells.isMergedCell(address);
|
| | }
|
| |
|
| | App::CellAddress Spreadsheet::Sheet::getAnchor(App::CellAddress address) const
|
| | {
|
| | return cells.getAnchor(address);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setColumnWidth(int col, int width)
|
| | {
|
| | columnWidths.setValue(col, width);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | int Sheet::getColumnWidth(int col) const
|
| | {
|
| | return columnWidths.getValue(col);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setRowHeight(int row, int height)
|
| | {
|
| | rowHeights.setValue(row, height);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | int Sheet::getRowHeight(int row) const
|
| | {
|
| | return rowHeights.getValue(row);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | std::vector<std::string> Sheet::getUsedCells() const
|
| | {
|
| | std::vector<std::string> usedCells;
|
| | for (const auto& addr : cells.getUsedCells()) {
|
| | usedCells.push_back(addr.toString());
|
| | }
|
| |
|
| | return usedCells;
|
| | }
|
| |
|
| | void Sheet::updateColumnsOrRows(bool horizontal, int section, int count)
|
| | {
|
| | const auto& sizes = horizontal ? columnWidths.getValues() : rowHeights.getValues();
|
| | auto iter = sizes.lower_bound(section);
|
| | if (iter != sizes.end()) {
|
| | std::map<int, int> newsizes(sizes.begin(), iter);
|
| | if (count > 0) {
|
| | for (; iter != sizes.end(); ++iter) {
|
| | newsizes.emplace(iter->first + count, iter->second);
|
| | }
|
| | }
|
| | else {
|
| | iter = sizes.lower_bound(section - count);
|
| | if (iter != sizes.end()) {
|
| | for (; iter != sizes.end(); ++iter) {
|
| | newsizes.emplace(iter->first + count, iter->second);
|
| | }
|
| | }
|
| | }
|
| | if (horizontal) {
|
| | columnWidths.setValues(newsizes);
|
| | }
|
| | else {
|
| | rowHeights.setValues(newsizes);
|
| | }
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::insertColumns(int col, int count)
|
| | {
|
| | cells.insertColumns(col, count);
|
| | updateColumnsOrRows(true, col, count);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::removeColumns(int col, int count)
|
| | {
|
| |
|
| | for (const auto& address : cells.getColumns(col, count)) {
|
| | auto cell = getCell(address);
|
| | std::string aliasStr;
|
| | if (cell && cell->getAlias(aliasStr)) {
|
| | removeDynamicProperty(aliasStr.c_str());
|
| | }
|
| | }
|
| |
|
| | cells.removeColumns(col, count);
|
| | updateColumnsOrRows(true, col, -count);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::insertRows(int row, int count)
|
| | {
|
| | cells.insertRows(row, count);
|
| | updateColumnsOrRows(false, row, count);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::removeRows(int row, int count)
|
| | {
|
| |
|
| | for (const auto& address : cells.getRows(row, count)) {
|
| | auto cell = getCell(address);
|
| | std::string aliasStr;
|
| | if (cell && cell->getAlias(aliasStr)) {
|
| | removeDynamicProperty(aliasStr.c_str());
|
| | }
|
| | }
|
| |
|
| | cells.removeRows(row, count);
|
| | updateColumnsOrRows(false, row, -count);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setContent(CellAddress address, const char* value)
|
| | {
|
| | cells.setContent(address, value);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setAlignment(CellAddress address, int alignment)
|
| | {
|
| | cells.setAlignment(address, alignment);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setStyle(CellAddress address, const std::set<std::string>& style)
|
| | {
|
| | cells.setStyle(address, style);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setForeground(CellAddress address, const Color& color)
|
| | {
|
| | cells.setForeground(address, color);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setBackground(CellAddress address, const Color& color)
|
| | {
|
| | cells.setBackground(address, color);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setDisplayUnit(CellAddress address, const std::string& unit)
|
| | {
|
| | cells.setDisplayUnit(address, unit);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setComputedUnit(CellAddress address, const Base::Unit& unit)
|
| | {
|
| | cells.setComputedUnit(address, unit);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setAlias(CellAddress address, const std::string& alias)
|
| | {
|
| | std::string existingAlias = getAddressFromAlias(alias);
|
| |
|
| | if (!existingAlias.empty()) {
|
| | if (existingAlias == address.toString()) {
|
| | return;
|
| | }
|
| | else {
|
| | throw Base::ValueError("Alias already defined");
|
| | }
|
| | }
|
| | else if (alias.empty()) {
|
| | cells.setAlias(address, "");
|
| | }
|
| | else if (isValidAlias(alias)) {
|
| | cells.setAlias(address, alias);
|
| | }
|
| | else {
|
| | throw Base::ValueError("Invalid alias");
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | std::string Sheet::getAddressFromAlias(const std::string& alias) const
|
| | {
|
| | const Cell* cell = cells.getValueFromAlias(alias);
|
| |
|
| | if (cell) {
|
| | return cell->getAddress().toString();
|
| | }
|
| | return {};
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | bool Sheet::isValidAlias(const std::string& candidate)
|
| | {
|
| |
|
| | if (!cells.isValidAlias(candidate)) {
|
| | return false;
|
| | }
|
| |
|
| |
|
| | if (!getAddressFromAlias(candidate).empty()) {
|
| | return true;
|
| | }
|
| |
|
| |
|
| | if (getPropertyByName(candidate.c_str())) {
|
| | return false;
|
| | }
|
| | else {
|
| | return true;
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::setSpans(CellAddress address, int rows, int columns)
|
| | {
|
| | cells.setSpans(address, rows, columns);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | std::set<std::string> Sheet::dependsOn(CellAddress address) const
|
| | {
|
| | return cells.getDeps(address);
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Sheet::providesTo(CellAddress address, std::set<std::string>& result) const
|
| | {
|
| | std::string fullName = getFullName() + ".";
|
| | std::set<CellAddress> tmpResult = cells.getDeps(fullName + address.toString());
|
| |
|
| | for (const auto& i : tmpResult) {
|
| | result.insert(fullName + i.toString());
|
| | }
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | std::set<CellAddress> Sheet::providesTo(CellAddress address) const
|
| | {
|
| | return cells.getDeps(getFullName() + "." + address.toString());
|
| | }
|
| |
|
| | void Sheet::onDocumentRestored()
|
| | {
|
| | auto ret = execute();
|
| | if (ret != DocumentObject::StdReturn) {
|
| | FC_ERR("Failed to restore " << getFullName() << ": " << ret->Why);
|
| | delete ret;
|
| | }
|
| | }
|
| |
|
| | void Sheet::renameObjectIdentifiers(const std::map<ObjectIdentifier, ObjectIdentifier>& paths)
|
| | {
|
| | DocumentObject::renameObjectIdentifiers(paths);
|
| |
|
| | cells.renameObjectIdentifiers(paths);
|
| | }
|
| | bool Sheet::hasCell(const std::vector<App::Range>& ranges) const
|
| | {
|
| | for (auto range : ranges) {
|
| | do {
|
| | if (cells.getValue(*range)) {
|
| | return true;
|
| | }
|
| | } while (range.next());
|
| | }
|
| | return false;
|
| | }
|
| |
|
| | std::string Sheet::getRow(int offset) const
|
| | {
|
| | if (currentRow < 0) {
|
| | throw Base::RuntimeError("No current row");
|
| | }
|
| | int row = currentRow + offset;
|
| | if (row < 0 || row > CellAddress::MAX_ROWS) {
|
| | throw Base::ValueError("Out of range");
|
| | }
|
| | return std::to_string(row + 1);
|
| | }
|
| |
|
| | std::string Sheet::getColumn(int offset) const
|
| | {
|
| | if (currentCol < 0) {
|
| | throw Base::RuntimeError("No current column");
|
| | }
|
| | int col = currentCol + offset;
|
| | if (col < 0 || col > CellAddress::MAX_COLUMNS) {
|
| | throw Base::ValueError("Out of range");
|
| | }
|
| | if (col < 26) {
|
| | char txt[2];
|
| | txt[0] = (char)('A' + col);
|
| | txt[1] = 0;
|
| | return txt;
|
| | }
|
| |
|
| | col -= 26;
|
| | char txt[3];
|
| | txt[0] = (char)('A' + (col / 26));
|
| | txt[1] = (char)('A' + (col % 26));
|
| | txt[2] = 0;
|
| | return txt;
|
| | }
|
| |
|
| | void Sheet::onChanged(const App::Property* prop)
|
| | {
|
| | if (prop == &cells) {
|
| | decltype(copyCutRanges) tmp;
|
| | tmp.swap(copyCutRanges);
|
| | for (auto& range : tmp) {
|
| | rangeUpdated(range);
|
| | }
|
| | }
|
| | else {
|
| | cells.slotChangedObject(*this, *prop);
|
| | }
|
| | App::DocumentObject::onChanged(prop);
|
| | }
|
| |
|
| | void Sheet::setCopyOrCutRanges(const std::vector<App::Range>& ranges, bool copy)
|
| | {
|
| | std::set<Range> rangeSet(copyCutRanges.begin(), copyCutRanges.end());
|
| | copyCutRanges = ranges;
|
| | rangeSet.insert(copyCutRanges.begin(), copyCutRanges.end());
|
| | for (const auto& range : rangeSet) {
|
| | rangeUpdated(range);
|
| | }
|
| | hasCopyRange = copy;
|
| | }
|
| |
|
| | const std::vector<Range>& Sheet::getCopyOrCutRange(bool copy) const
|
| | {
|
| | static const std::vector<Range> nullRange;
|
| | if (hasCopyRange != copy) {
|
| | return nullRange;
|
| | }
|
| | return copyCutRanges;
|
| | }
|
| |
|
| | unsigned Sheet::getCopyOrCutBorder(CellAddress address, bool copy) const
|
| | {
|
| | if (hasCopyRange != copy) {
|
| | return 0;
|
| | }
|
| | return _getBorder(this, copyCutRanges, address);
|
| | }
|
| |
|
| |
|
| |
|
| | TYPESYSTEM_SOURCE(Spreadsheet::PropertySpreadsheetQuantity, App::PropertyQuantity)
|
| |
|
| | Property* PropertySpreadsheetQuantity::Copy() const
|
| | {
|
| | PropertySpreadsheetQuantity* obj = new PropertySpreadsheetQuantity();
|
| |
|
| | obj->_dValue = _dValue;
|
| | obj->_Unit = _Unit;
|
| |
|
| | return obj;
|
| | }
|
| |
|
| | void PropertySpreadsheetQuantity::Paste(const Property& from)
|
| | {
|
| | const auto& src = dynamic_cast<const PropertySpreadsheetQuantity&>(from);
|
| | aboutToSetValue();
|
| | _dValue = src._dValue;
|
| | _Unit = src._Unit;
|
| | hasSetValue();
|
| | }
|
| |
|
| |
|
| |
|
| | namespace App
|
| | {
|
| |
|
| | PROPERTY_SOURCE_TEMPLATE(Spreadsheet::SheetPython, Spreadsheet::Sheet)
|
| | template<>
|
| | const char* Spreadsheet::SheetPython::getViewProviderName() const
|
| | {
|
| | return "SpreadsheetGui::ViewProviderSheetPython";
|
| | }
|
| | template<>
|
| | PyObject* Spreadsheet::SheetPython::getPyObject()
|
| | {
|
| | if (PythonObject.is(Py::_None())) {
|
| |
|
| | PythonObject = Py::Object(new FeaturePythonPyT<Spreadsheet::SheetPy>(this), true);
|
| | }
|
| | return Py::new_reference_to(PythonObject);
|
| | }
|
| |
|
| |
|
| |
|
| | template class SpreadsheetExport FeaturePythonT<Spreadsheet::Sheet>;
|
| | }
|
| |
|