| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <algorithm> |
| |
|
| | #include <boost/range/adaptor/map.hpp> |
| | #include <boost/range/algorithm/copy.hpp> |
| | #include <boost/regex.hpp> |
| |
|
| | #include <App/Document.h> |
| | #include <App/DocumentObject.h> |
| | #include <App/DocumentObserver.h> |
| | #include <App/Expression.h> |
| | #include <App/ExpressionParser.h> |
| | #include <App/ExpressionVisitors.h> |
| | #include <App/Property.h> |
| | #include <Base/Console.h> |
| | #include <Base/Interpreter.h> |
| | #include <Base/Reader.h> |
| | #include <Base/Tools.h> |
| | #include <Base/Writer.h> |
| |
|
| | #include "PropertySheet.h" |
| | #include "PropertySheetPy.h" |
| | #include "Sheet.h" |
| |
|
| |
|
| | FC_LOG_LEVEL_INIT("Spreadsheet", true, true) |
| |
|
| | using namespace App; |
| | using namespace Base; |
| | using namespace Spreadsheet; |
| | namespace sp = std::placeholders; |
| |
|
| | TYPESYSTEM_SOURCE(Spreadsheet::PropertySheet, App::PropertyExpressionContainer) |
| |
|
| | void PropertySheet::clear() |
| | { |
| | |
| | for (auto& it : data) { |
| | delete it.second; |
| | setDirty(it.first); |
| | } |
| |
|
| | |
| | data.clear(); |
| |
|
| | mergedCells.clear(); |
| |
|
| | propertyNameToCellMap.clear(); |
| | cellToPropertyNameMap.clear(); |
| | documentObjectToCellMap.clear(); |
| | cellToDocumentObjectMap.clear(); |
| | aliasProp.clear(); |
| | revAliasProp.clear(); |
| |
|
| | clearDeps(); |
| | } |
| |
|
| | Cell* PropertySheet::getValue(CellAddress key) |
| | { |
| | std::map<CellAddress, Cell*>::const_iterator i = data.find(key); |
| |
|
| | if (i == data.end()) { |
| | return nullptr; |
| | } |
| | else { |
| | return i->second; |
| | } |
| | } |
| |
|
| | const Cell* PropertySheet::getValue(CellAddress key) const |
| | { |
| | std::map<CellAddress, Cell*>::const_iterator i = data.find(key); |
| |
|
| | if (i == data.end()) { |
| | return nullptr; |
| | } |
| | else { |
| | return i->second; |
| | } |
| | } |
| |
|
| | Cell* PropertySheet::getValueFromAlias(const std::string& alias) |
| | { |
| | std::map<std::string, CellAddress>::const_iterator it = revAliasProp.find(alias); |
| |
|
| | if (it != revAliasProp.end()) { |
| | return getValue(it->second); |
| | } |
| | else { |
| | return nullptr; |
| | } |
| | } |
| |
|
| | const Cell* PropertySheet::getValueFromAlias(const std::string& alias) const |
| | { |
| | std::map<std::string, CellAddress>::const_iterator it = revAliasProp.find(alias); |
| |
|
| | if (it != revAliasProp.end()) { |
| | return getValue(it->second); |
| | } |
| | else { |
| | return nullptr; |
| | } |
| | } |
| |
|
| | bool PropertySheet::isValidCellAddressName(const std::string& candidate) |
| | { |
| | |
| | static const boost::regex e("\\${0,1}([A-Z]{1,2})\\${0,1}([0-9]{1,5})"); |
| | boost::cmatch cm; |
| |
|
| | if (boost::regex_match(candidate.c_str(), cm, e)) { |
| | const boost::sub_match<const char*> colstr = cm[1]; |
| | const boost::sub_match<const char*> rowstr = cm[2]; |
| |
|
| | if (App::validRow(rowstr.str()) >= 0 && App::validColumn(colstr.str())) { |
| | return true; |
| | } |
| | } |
| | return false; |
| | } |
| |
|
| | bool PropertySheet::isValidAlias(const std::string& candidate) |
| | { |
| | |
| | static const boost::regex gen("^[A-Za-z][_A-Za-z0-9]*$"); |
| | boost::cmatch cm; |
| | if (!boost::regex_match(candidate.c_str(), cm, gen)) { |
| | return false; |
| | } |
| |
|
| | |
| | if (getValueFromAlias(candidate)) { |
| | return false; |
| | } |
| |
|
| | |
| | if (isValidCellAddressName(candidate)) { |
| | return false; |
| | } |
| |
|
| | |
| | if (ExpressionParser::isTokenAUnit(candidate) || ExpressionParser::isTokenAConstant(candidate)) { |
| | return false; |
| | } |
| |
|
| | return true; |
| | } |
| |
|
| | namespace |
| | { |
| |
|
| | |
| | |
| | |
| | |
| | |
| | std::tuple<CellAddress, CellAddress> extractRange(const std::vector<CellAddress>& cells) |
| | { |
| | CellAddress firstRowAndColumn; |
| | CellAddress lastRowAndColumn; |
| | for (const auto& cell : cells) { |
| | int row = cell.row(); |
| | int column = cell.col(); |
| | if (row < firstRowAndColumn.row() || !firstRowAndColumn.isValid()) { |
| | firstRowAndColumn.setRow(row); |
| | } |
| | if (column < firstRowAndColumn.col() || !firstRowAndColumn.isValid()) { |
| | firstRowAndColumn.setCol(column); |
| | } |
| | if (row > lastRowAndColumn.row() || !lastRowAndColumn.isValid()) { |
| | lastRowAndColumn.setRow(row); |
| | } |
| | if (column > lastRowAndColumn.col() || !lastRowAndColumn.isValid()) { |
| | lastRowAndColumn.setCol(column); |
| | } |
| | } |
| | return std::make_tuple(firstRowAndColumn, lastRowAndColumn); |
| | } |
| | } |
| |
|
| | std::vector<CellAddress> PropertySheet::getUsedCells() const |
| | { |
| | std::vector<CellAddress> usedSet; |
| |
|
| | for (const auto& i : data) { |
| | if (i.second->isUsed()) { |
| | usedSet.push_back(i.first); |
| | } |
| | } |
| |
|
| | return usedSet; |
| | } |
| |
|
| | std::tuple<CellAddress, CellAddress> PropertySheet::getUsedRange() const |
| | { |
| | auto usedCells = getUsedCells(); |
| | return extractRange(usedCells); |
| | } |
| |
|
| | std::vector<CellAddress> PropertySheet::getNonEmptyCells() const |
| | { |
| | std::vector<CellAddress> usedSet; |
| |
|
| | std::string str; |
| | for (const auto& i : data) { |
| | str.clear(); |
| | if (i.second->isUsed() && i.second->getStringContent(str) && !str.empty()) { |
| | usedSet.push_back(i.first); |
| | } |
| | } |
| |
|
| | return usedSet; |
| | } |
| |
|
| | std::tuple<CellAddress, CellAddress> PropertySheet::getNonEmptyRange() const |
| | { |
| | auto nonEmptyCells = getNonEmptyCells(); |
| | return extractRange(nonEmptyCells); |
| | } |
| |
|
| | void PropertySheet::setDirty(CellAddress address) |
| | { |
| | |
| | |
| | std::map<CellAddress, CellAddress>::const_iterator i = mergedCells.find(address); |
| | if (i != mergedCells.end()) { |
| | address = i->second; |
| | } |
| |
|
| | dirty.insert(address); |
| | } |
| |
|
| | void PropertySheet::setDirty() |
| | { |
| | AtomicPropertyChange signaller(*this); |
| | for (auto& address : getNonEmptyCells()) { |
| | auto cell = cellAt(address); |
| | std::string content; |
| | if (cell && cell->getStringContent(content, false)) { |
| | cell->setContent(content.c_str()); |
| | } |
| | } |
| | } |
| |
|
| | Cell* PropertySheet::createCell(CellAddress address) |
| | { |
| | Cell* cell = new Cell(address, this); |
| |
|
| | data[address] = cell; |
| |
|
| | return cell; |
| | } |
| |
|
| | PropertySheet::PropertySheet(Sheet* _owner) |
| | : owner(_owner) |
| | {} |
| |
|
| | PropertySheet::PropertySheet(const PropertySheet& other) |
| | : dirty(other.dirty) |
| | , mergedCells(other.mergedCells) |
| | , owner(other.owner) |
| | , propertyNameToCellMap(other.propertyNameToCellMap) |
| | , cellToPropertyNameMap(other.cellToPropertyNameMap) |
| | , documentObjectToCellMap(other.documentObjectToCellMap) |
| | , cellToDocumentObjectMap(other.cellToDocumentObjectMap) |
| | , aliasProp(other.aliasProp) |
| | , revAliasProp(other.revAliasProp) |
| | , updateCount(other.updateCount) |
| | { |
| | std::map<CellAddress, Cell*>::const_iterator i = other.data.begin(); |
| |
|
| | |
| | while (i != other.data.end()) { |
| | data[i->first] = new Cell(this, *i->second); |
| | ++i; |
| | } |
| | } |
| |
|
| | PropertySheet::~PropertySheet() |
| | { |
| | clear(); |
| | } |
| |
|
| | App::Property* PropertySheet::Copy() const |
| | { |
| | return new PropertySheet(*this); |
| | } |
| |
|
| | void PropertySheet::Paste(const Property& from) |
| | { |
| | const PropertySheet& froms = dynamic_cast<const PropertySheet&>(from); |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | std::map<CellAddress, Cell*>::iterator icurr = data.begin(); |
| |
|
| | |
| | while (icurr != data.end()) { |
| | icurr->second->mark(); |
| | ++icurr; |
| | } |
| |
|
| | std::map<CellAddress, Cell*>::const_iterator ifrom = froms.data.begin(); |
| | std::vector<CellAddress> spanChanges; |
| | int rows, cols; |
| | while (ifrom != froms.data.end()) { |
| | auto& cell = data[ifrom->first]; |
| |
|
| | if (cell) { |
| | int r, c; |
| | cell->getSpans(rows, cols); |
| | ifrom->second->getSpans(r, c); |
| | if (rows != r || cols != c) { |
| | spanChanges.push_back(ifrom->first); |
| | } |
| | *cell = *(ifrom->second); |
| | } |
| | else { |
| | cell = new Cell( |
| | this, |
| | *(ifrom->second) |
| | ); |
| | if (cell->getSpans(rows, cols)) { |
| | spanChanges.push_back(ifrom->first); |
| | } |
| | } |
| | recomputeDependencies(ifrom->first); |
| |
|
| | |
| | setDirty(ifrom->first); |
| | ++ifrom; |
| | } |
| |
|
| | |
| | icurr = data.begin(); |
| | while (icurr != data.end()) { |
| | Cell* cell = icurr->second; |
| |
|
| | if (cell->isMarked()) { |
| | if (cell->getSpans(rows, cols)) { |
| | spanChanges.push_back(icurr->first); |
| | } |
| |
|
| | std::map<CellAddress, Cell*>::iterator next = icurr; |
| |
|
| | ++next; |
| | clear(icurr->first); |
| | icurr = next; |
| | } |
| | else { |
| | ++icurr; |
| | } |
| | } |
| |
|
| | if (!spanChanges.empty()) { |
| | mergedCells = froms.mergedCells; |
| | if (auto sheet = freecad_cast<Sheet*>(getContainer())) { |
| | for (const auto& addr : spanChanges) { |
| | sheet->cellSpanChanged(addr); |
| | } |
| | } |
| | } |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | void PropertySheet::Save(Base::Writer& writer) const |
| | { |
| | |
| | int count = 0; |
| |
|
| | std::map<CellAddress, Cell*>::const_iterator ci = data.begin(); |
| | while (ci != data.end()) { |
| | if (ci->second->isUsed()) { |
| | ++count; |
| | } |
| | ++ci; |
| | } |
| |
|
| | writer.Stream() << writer.ind() << "<Cells Count=\"" << count << R"(" xlink="1">)" << std::endl; |
| |
|
| | writer.incInd(); |
| |
|
| | PropertyExpressionContainer::Save(writer); |
| |
|
| | ci = data.begin(); |
| | while (ci != data.end()) { |
| | ci->second->save(writer); |
| | ++ci; |
| | } |
| |
|
| | writer.decInd(); |
| | writer.Stream() << writer.ind() << "</Cells>" << std::endl; |
| | } |
| |
|
| | void PropertySheet::Restore(Base::XMLReader& reader) |
| | { |
| | int Cnt; |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | reader.readElement("Cells"); |
| | Cnt = reader.getAttribute<long>("Count"); |
| |
|
| | if (reader.hasAttribute("xlink") && reader.getAttribute<bool>("xlink")) { |
| | PropertyExpressionContainer::Restore(reader); |
| | } |
| |
|
| | for (int i = 0; i < Cnt; i++) { |
| | reader.readElement("Cell"); |
| |
|
| | const char* strAddress = reader.hasAttribute("address") |
| | ? reader.getAttribute<const char*>("address") |
| | : ""; |
| |
|
| | try { |
| | CellAddress address(strAddress); |
| | Cell* cell = createCell(address); |
| |
|
| | cell->restore(reader); |
| |
|
| | int rows, cols; |
| | if (cell->getSpans(rows, cols) && (rows > 1 || cols > 1)) { |
| | mergeCells(address, CellAddress(address.row() + rows - 1, address.col() + cols - 1)); |
| | } |
| | } |
| | catch (const Base::Exception&) { |
| | |
| | } |
| | catch (...) { |
| | } |
| | } |
| | reader.readEndElement("Cells"); |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | void PropertySheet::copyCells(Base::Writer& writer, const std::vector<Range>& ranges) const |
| | { |
| | writer.Stream() << "<?xml version='1.0' encoding='utf-8'?>" << std::endl; |
| | writer.Stream() << "<Cells count=\"" << ranges.size() << "\">" << std::endl; |
| | writer.incInd(); |
| | for (auto range : ranges) { |
| | auto r = range; |
| | int count = 0; |
| | do { |
| | auto cell = getValue(*r); |
| | if (cell && cell->isUsed()) { |
| | ++count; |
| | } |
| | } while (r.next()); |
| | writer.Stream() << writer.ind() << "<Range from=\"" << range.fromCellString() << "\" to=\"" |
| | << range.toCellString() << "\" count=\"" << count << "\">" << std::endl; |
| | writer.incInd(); |
| | do { |
| | auto cell = getValue(*range); |
| | if (cell && cell->isUsed()) { |
| | cell->save(writer); |
| | } |
| | } while (range.next()); |
| | writer.decInd(); |
| | writer.Stream() << writer.ind() << "</Range>" << std::endl; |
| | } |
| | writer.decInd(); |
| | writer.Stream() << "</Cells>" << std::endl; |
| | } |
| |
|
| | void PropertySheet::pasteCells(XMLReader& reader, Range dstRange) |
| | { |
| | reader.readElement("Cells"); |
| | int rangeCount = reader.getAttribute<long>("count"); |
| | if (rangeCount <= 0) { |
| | return; |
| | } |
| |
|
| | int dstRows = dstRange.rowCount(); |
| | int dstCols = dstRange.colCount(); |
| | CellAddress dstFrom = dstRange.from(); |
| |
|
| | int roffset = 0, coffset = 0; |
| |
|
| | AtomicPropertyChange signaller(*this); |
| | for (int ri = 0; ri < rangeCount; ++ri) { |
| | reader.readElement("Range"); |
| | CellAddress from(reader.getAttribute<const char*>("from")); |
| | CellAddress to(reader.getAttribute<const char*>("to")); |
| | int cellCount = reader.getAttribute<long>("count"); |
| |
|
| | Range range(from, to); |
| |
|
| | CellAddress addr(dstFrom); |
| | if (ri == 0) { |
| | roffset = addr.row() - from.row(); |
| | coffset = addr.col() - from.col(); |
| | } |
| |
|
| | int rcount, ccount; |
| | if (rangeCount > 1) { |
| | rcount = 1; |
| | ccount = 1; |
| | } |
| | else { |
| | rcount = dstRows / range.rowCount(); |
| | if (rcount == 0) { |
| | rcount = 1; |
| | } |
| | ccount = dstCols / range.colCount(); |
| | if (ccount == 0) { |
| | ccount = 1; |
| | } |
| | } |
| | for (int ci = 0; ci < cellCount; ++ci) { |
| | reader.readElement("Cell"); |
| | CellAddress src(reader.getAttribute<const char*>("address")); |
| |
|
| | if (ci) { |
| | range.next(); |
| | } |
| |
|
| | while (src != *range) { |
| | for (int r = 0; r < rcount; ++r) { |
| | for (int c = 0; c < ccount; ++c) { |
| | CellAddress dst( |
| | range.row() + roffset + r * range.rowCount(), |
| | range.column() + coffset + c * range.colCount() |
| | ); |
| | if (!dst.isValid()) { |
| | continue; |
| | } |
| | owner->clear(dst); |
| | } |
| | } |
| | range.next(); |
| | } |
| |
|
| | CellAddress newCellAddr; |
| | for (int r = 0; r < rcount; ++r) { |
| | for (int c = 0; c < ccount; ++c) { |
| | CellAddress dst( |
| | src.row() + roffset + r * range.rowCount(), |
| | src.col() + coffset + c * range.colCount() |
| | ); |
| | if (!dst.isValid()) { |
| | continue; |
| | } |
| |
|
| | auto cell = owner->getNewCell(dst); |
| | splitCell(dst); |
| |
|
| | int roffset_cur, coffset_cur; |
| | if (!newCellAddr.isValid()) { |
| | roffset_cur = roffset; |
| | coffset_cur = coffset; |
| | newCellAddr = dst; |
| | cell->restore(reader, true); |
| | } |
| | else { |
| | roffset_cur = r * range.rowCount(); |
| | coffset_cur = c * range.colCount(); |
| | auto newCell = owner->getCell(newCellAddr); |
| | const Expression* expr; |
| | if (!newCell || !(expr = newCell->getExpression(true))) { |
| | FC_THROWM( |
| | Base::RuntimeError, |
| | "Failed to copy cell " << getFullName() << '.' << dst.toString() |
| | << " from " << newCellAddr.toString() |
| | ); |
| | } |
| | cell->setExpression(ExpressionPtr(expr->copy())); |
| | } |
| |
|
| | int rows, cols; |
| | if (cell->getSpans(rows, cols)) { |
| | mergeCells(dst, CellAddress(dst.row() + rows - 1, dst.col() + cols - 1)); |
| | } |
| | else { |
| | splitCell(dst); |
| | } |
| |
|
| | if (roffset_cur || coffset_cur) { |
| | OffsetCellsExpressionVisitor<PropertySheet> visitor( |
| | *this, |
| | roffset_cur, |
| | coffset_cur |
| | ); |
| | cell->visit(visitor); |
| | if (visitor.changed()) { |
| | recomputeDependencies(dst); |
| | } |
| | } |
| | dirty.insert(dst); |
| | } |
| | } |
| | } |
| | if (cellCount == 0 || range.next()) { |
| | do { |
| | for (int r = 0; r < rcount; ++r) { |
| | for (int c = 0; c < ccount; ++c) { |
| | CellAddress dst( |
| | range.row() + roffset + r * range.rowCount(), |
| | range.column() + coffset + c * range.colCount() |
| | ); |
| | if (!dst.isValid()) { |
| | continue; |
| | } |
| | owner->clear(dst); |
| | } |
| | } |
| | } while (range.next()); |
| | } |
| | owner->rangeUpdated(Range(from, to)); |
| | } |
| | signaller.tryInvoke(); |
| | } |
| |
|
| |
|
| | Cell* PropertySheet::cellAt(CellAddress address) |
| | { |
| | std::map<CellAddress, CellAddress>::const_iterator j = mergedCells.find(address); |
| |
|
| | |
| | if (j != mergedCells.end()) { |
| | std::map<CellAddress, Cell*>::const_iterator i = data.find(j->second); |
| | assert(i != data.end()); |
| |
|
| | return i->second; |
| | } |
| |
|
| | std::map<CellAddress, Cell*>::const_iterator i = data.find(address); |
| |
|
| | if (i == data.end()) { |
| | return nullptr; |
| | } |
| | else { |
| | return i->second; |
| | } |
| | } |
| |
|
| | const Cell* PropertySheet::cellAt(CellAddress address) const |
| | { |
| | std::map<CellAddress, CellAddress>::const_iterator j = mergedCells.find(address); |
| |
|
| | |
| | if (j != mergedCells.end()) { |
| | std::map<CellAddress, Cell*>::const_iterator i = data.find(j->second); |
| | assert(i != data.end()); |
| |
|
| | return i->second; |
| | } |
| |
|
| | std::map<CellAddress, Cell*>::const_iterator i = data.find(address); |
| |
|
| | if (i == data.end()) { |
| | return nullptr; |
| | } |
| | else { |
| | return i->second; |
| | } |
| | } |
| |
|
| | Cell* PropertySheet::nonNullCellAt(CellAddress address) |
| | { |
| | std::map<CellAddress, CellAddress>::const_iterator j = mergedCells.find(address); |
| |
|
| | if (j != mergedCells.end()) { |
| | std::map<CellAddress, Cell*>::const_iterator i = data.find(j->second); |
| |
|
| | if (i == data.end()) { |
| | return createCell(address); |
| | } |
| | else { |
| | return i->second; |
| | } |
| | } |
| |
|
| | std::map<CellAddress, Cell*>::const_iterator i = data.find(address); |
| |
|
| | if (i == data.end()) { |
| | return createCell(address); |
| | } |
| | else { |
| | return i->second; |
| | } |
| | } |
| |
|
| | void PropertySheet::setContent(CellAddress address, const char* value) |
| | { |
| | Cell* cell = nonNullCellAt(address); |
| | assert(cell); |
| | cell->setContent(value); |
| | } |
| |
|
| | void PropertySheet::setAlignment(CellAddress address, int _alignment) |
| | { |
| | Cell* cell = nonNullCellAt(address); |
| | assert(cell); |
| | if (cell->address != address) { |
| | return; |
| | } |
| | cell->setAlignment(_alignment); |
| | } |
| |
|
| | void PropertySheet::setStyle(CellAddress address, const std::set<std::string>& _style) |
| | { |
| | Cell* cell = nonNullCellAt(address); |
| | assert(cell); |
| | cell->setStyle(_style); |
| | } |
| |
|
| | void PropertySheet::setForeground(CellAddress address, const Base::Color& color) |
| | { |
| | Cell* cell = nonNullCellAt(address); |
| | assert(cell); |
| | cell->setForeground(color); |
| | } |
| |
|
| | void PropertySheet::setBackground(CellAddress address, const Base::Color& color) |
| | { |
| | Cell* cell = nonNullCellAt(address); |
| | assert(cell); |
| | cell->setBackground(color); |
| | } |
| |
|
| | void PropertySheet::setDisplayUnit(CellAddress address, const std::string& unit) |
| | { |
| | Cell* cell = nonNullCellAt(address); |
| | assert(cell); |
| | cell->setDisplayUnit(unit); |
| | } |
| |
|
| |
|
| | void PropertySheet::setAlias(CellAddress address, const std::string& alias) |
| | { |
| | if (!alias.empty() && !isValidAlias(alias)) { |
| | throw Base::ValueError("Invalid alias"); |
| | } |
| |
|
| | const Cell* aliasedCell = getValueFromAlias(alias); |
| | Cell* cell = nonNullCellAt(address); |
| | assert(cell); |
| |
|
| | if (aliasedCell == cell) { |
| | return; |
| | } |
| |
|
| | if (aliasedCell) { |
| | throw Base::ValueError("Alias already defined."); |
| | } |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | |
| | |
| | std::string fullName = owner->getFullName() + "." + address.toString(); |
| |
|
| | std::map<std::string, std::set<CellAddress>>::const_iterator j = propertyNameToCellMap.find( |
| | fullName |
| | ); |
| | if (j != propertyNameToCellMap.end()) { |
| | std::set<CellAddress>::const_iterator k = j->second.begin(); |
| |
|
| | while (k != j->second.end()) { |
| | setDirty(*k); |
| | ++k; |
| | } |
| | } |
| |
|
| | std::string oldAlias; |
| | cell->getAlias(oldAlias); |
| | cell->setAlias(alias); |
| |
|
| | if (!oldAlias.empty()) { |
| | std::map<App::ObjectIdentifier, App::ObjectIdentifier> m; |
| |
|
| | App::ObjectIdentifier key(owner, oldAlias); |
| | App::ObjectIdentifier value(owner, alias.empty() ? address.toString() : alias); |
| |
|
| | m[key] = std::move(value); |
| |
|
| | owner->getDocument()->renameObjectIdentifiers(m); |
| | } |
| |
|
| | signaller.tryInvoke(); |
| | } |
| |
|
| | void PropertySheet::setComputedUnit(CellAddress address, const Base::Unit& unit) |
| | { |
| | Cell* cell = nonNullCellAt(address); |
| | assert(cell); |
| | cell->setComputedUnit(unit); |
| | } |
| |
|
| | void PropertySheet::setSpans(CellAddress address, int rows, int columns) |
| | { |
| | Cell* cell = nonNullCellAt(address); |
| | assert(cell); |
| | cell->setSpans(rows, columns); |
| | owner->cellSpanChanged(address); |
| | } |
| |
|
| | void PropertySheet::clearAlias(CellAddress address) |
| | { |
| | |
| | std::map<CellAddress, std::string>::iterator j = aliasProp.find(address); |
| | if (j != aliasProp.end()) { |
| | revAliasProp.erase(j->second); |
| | aliasProp.erase(j); |
| | } |
| | } |
| |
|
| | void PropertySheet::clear(CellAddress address, bool toClearAlias) |
| | { |
| | std::map<CellAddress, Cell*>::iterator i = data.find(address); |
| |
|
| | if (i == data.end()) { |
| | return; |
| | } |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | |
| | splitCell(address); |
| |
|
| | |
| | removeDependencies(address); |
| | delete i->second; |
| |
|
| | |
| | dirty.insert(i->first); |
| |
|
| | if (toClearAlias) { |
| | clearAlias(address); |
| | } |
| |
|
| | |
| | data.erase(i); |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | void PropertySheet::moveAlias(CellAddress currPos, CellAddress newPos) |
| | { |
| | std::map<CellAddress, std::string>::iterator j = aliasProp.find(currPos); |
| | if (j != aliasProp.end()) { |
| | aliasProp[newPos] = j->second; |
| | revAliasProp[j->second] = newPos; |
| | aliasProp.erase(currPos); |
| | } |
| | } |
| |
|
| | void PropertySheet::moveCell( |
| | CellAddress currPos, |
| | CellAddress newPos, |
| | std::map<App::ObjectIdentifier, App::ObjectIdentifier>& renames |
| | ) |
| | { |
| | std::map<CellAddress, Cell*>::const_iterator i = data.find(currPos); |
| | std::map<CellAddress, Cell*>::const_iterator j = data.find(newPos); |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | if (j != data.end()) { |
| | |
| | clear(newPos, false); |
| | } |
| |
|
| | if (i != data.end()) { |
| | Cell* cell = i->second; |
| | int rows, columns; |
| |
|
| | |
| | bool hasSpan = cell->getSpans(rows, columns); |
| |
|
| | |
| | splitCell(currPos); |
| |
|
| | |
| | removeDependencies(currPos); |
| | data.erase(currPos); |
| | setDirty(currPos); |
| |
|
| | |
| | cell->moveAbsolute(newPos); |
| | data[newPos] = cell; |
| |
|
| | if (hasSpan) { |
| | CellAddress toPos(newPos.row() + rows - 1, newPos.col() + columns - 1); |
| | mergeCells(newPos, toPos); |
| | } |
| |
|
| | addDependencies(newPos); |
| |
|
| | setDirty(newPos); |
| |
|
| | renames[ObjectIdentifier(owner, currPos.toString())] |
| | = ObjectIdentifier(owner, newPos.toString()); |
| | } |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | void PropertySheet::insertRows(int row, int count) |
| | { |
| | std::vector<CellAddress> keys; |
| | std::map<App::ObjectIdentifier, App::ObjectIdentifier> renames; |
| |
|
| | |
| | boost::copy(data | boost::adaptors::map_keys, std::back_inserter(keys)); |
| |
|
| | |
| | std::sort(keys.begin(), keys.end(), std::bind(&PropertySheet::rowSortFunc, this, sp::_1, sp::_2)); |
| |
|
| | MoveCellsExpressionVisitor<PropertySheet> |
| | visitor(*this, CellAddress(row, CellAddress::MAX_COLUMNS), count, 0); |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | |
| | for (std::vector<CellAddress>::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) { |
| | if (i->row() >= row) { |
| | moveAlias(*i, CellAddress(i->row() + count, i->col())); |
| | } |
| | } |
| |
|
| | for (std::vector<CellAddress>::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) { |
| | std::map<CellAddress, Cell*>::iterator j = data.find(*i); |
| |
|
| | assert(j != data.end()); |
| |
|
| | Cell* cell = j->second; |
| |
|
| | |
| | visitor.reset(); |
| | cell->visit(visitor); |
| | if (visitor.changed()) { |
| | setDirty(*i); |
| | recomputeDependencies(*i); |
| | } |
| |
|
| | if (i->row() >= row) { |
| | moveCell(*i, CellAddress(i->row() + count, i->col()), renames); |
| | } |
| | } |
| |
|
| | const App::DocumentObject* docObj = static_cast<const App::DocumentObject*>(getContainer()); |
| | owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject* obj) { |
| | return obj != docObj; |
| | }); |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | bool PropertySheet::rowSortFunc(const CellAddress& a, const CellAddress& b) |
| | { |
| | if (a.row() < b.row()) { |
| | return true; |
| | } |
| | else { |
| | return false; |
| | } |
| | } |
| |
|
| | std::vector<CellAddress> PropertySheet::getRows(int row, int count) const |
| | { |
| | std::vector<CellAddress> keys; |
| |
|
| | for (const auto& i : data) { |
| | auto key = i.first; |
| | if (key.row() >= row && key.row() < row + count) { |
| | keys.push_back(key); |
| | } |
| | } |
| | return keys; |
| | } |
| |
|
| | void PropertySheet::removeRows(int row, int count) |
| | { |
| | std::vector<CellAddress> keys; |
| | std::map<App::ObjectIdentifier, App::ObjectIdentifier> renames; |
| |
|
| | |
| | boost::copy(data | boost::adaptors::map_keys, std::back_inserter(keys)); |
| |
|
| | |
| | std::sort(keys.begin(), keys.end(), std::bind(&PropertySheet::rowSortFunc, this, sp::_1, sp::_2)); |
| |
|
| | MoveCellsExpressionVisitor<PropertySheet> |
| | visitor(*this, CellAddress(row + count - 1, CellAddress::MAX_COLUMNS), -count, 0); |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | |
| | for (const auto& key : keys) { |
| | if (key.row() >= row && key.row() < row + count) { |
| | clearAlias(key); |
| | } |
| | else if (key.row() >= row + count) { |
| | moveAlias(key, CellAddress(key.row() - count, key.col())); |
| | } |
| | } |
| |
|
| | int spanRows, spanCols; |
| | for (const auto& key : keys) { |
| | std::map<CellAddress, Cell*>::iterator j = data.find(key); |
| |
|
| | assert(j != data.end()); |
| |
|
| | Cell* cell = j->second; |
| |
|
| | |
| | visitor.reset(); |
| | cell->visit(visitor); |
| | if (visitor.changed()) { |
| | setDirty(key); |
| | recomputeDependencies(key); |
| | } |
| |
|
| | if (key.row() >= row && key.row() < row + count) { |
| | clear(key, false); |
| | } |
| | else if (key.row() >= row + count) { |
| | moveCell(key, CellAddress(key.row() - count, key.col()), renames); |
| | } |
| | else if (cell->getSpans(spanRows, spanCols) && key.row() + spanRows >= row) { |
| | if (key.row() + spanRows >= row + count) { |
| | spanRows -= count; |
| | } |
| | else { |
| | spanRows = key.row() - row; |
| | } |
| | mergeCells( |
| | j->first, |
| | CellAddress(j->first.row() + spanRows - 1, j->first.col() + spanCols - 1) |
| | ); |
| | } |
| | } |
| |
|
| | const App::DocumentObject* docObj = static_cast<const App::DocumentObject*>(getContainer()); |
| | owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject* obj) { |
| | return obj != docObj; |
| | }); |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | void PropertySheet::insertColumns(int col, int count) |
| | { |
| | std::vector<CellAddress> keys; |
| | std::map<App::ObjectIdentifier, App::ObjectIdentifier> renames; |
| |
|
| | |
| | boost::copy(data | boost::adaptors::map_keys, std::back_inserter(keys)); |
| |
|
| | |
| | std::sort(keys.begin(), keys.end()); |
| |
|
| | MoveCellsExpressionVisitor<PropertySheet> |
| | visitor(*this, CellAddress(CellAddress::MAX_ROWS, col), 0, count); |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | |
| | for (std::vector<CellAddress>::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) { |
| | if (i->col() >= col) { |
| | moveAlias(*i, CellAddress(i->row(), i->col() + count)); |
| | } |
| | } |
| |
|
| | for (std::vector<CellAddress>::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) { |
| | std::map<CellAddress, Cell*>::iterator j = data.find(*i); |
| |
|
| | assert(j != data.end()); |
| |
|
| | Cell* cell = j->second; |
| |
|
| | |
| | visitor.reset(); |
| | cell->visit(visitor); |
| | if (visitor.changed()) { |
| | setDirty(*i); |
| | recomputeDependencies(*i); |
| | } |
| |
|
| | if (i->col() >= col) { |
| | moveCell(*i, CellAddress(i->row(), i->col() + count), renames); |
| | } |
| | } |
| |
|
| | const App::DocumentObject* docObj = static_cast<const App::DocumentObject*>(getContainer()); |
| | owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject* obj) { |
| | return obj != docObj; |
| | }); |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | bool PropertySheet::colSortFunc(const CellAddress& a, const CellAddress& b) |
| | { |
| | if (a.col() < b.col()) { |
| | return true; |
| | } |
| | else { |
| | return false; |
| | } |
| | } |
| |
|
| | std::vector<CellAddress> PropertySheet::getColumns(int column, int count) const |
| | { |
| | std::vector<CellAddress> keys; |
| |
|
| | for (const auto& i : data) { |
| | auto key = i.first; |
| | if (key.col() >= column && key.col() < column + count) { |
| | keys.push_back(key); |
| | } |
| | } |
| | return keys; |
| | } |
| |
|
| | void PropertySheet::removeColumns(int col, int count) |
| | { |
| | std::vector<CellAddress> keys; |
| | std::map<App::ObjectIdentifier, App::ObjectIdentifier> renames; |
| |
|
| | |
| | boost::copy(data | boost::adaptors::map_keys, std::back_inserter(keys)); |
| |
|
| | |
| | std::sort(keys.begin(), keys.end(), std::bind(&PropertySheet::colSortFunc, this, sp::_1, sp::_2)); |
| |
|
| | MoveCellsExpressionVisitor<PropertySheet> |
| | visitor(*this, CellAddress(CellAddress::MAX_ROWS, col + count - 1), 0, -count); |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | |
| | for (const auto& key : keys) { |
| | if (key.col() >= col && key.col() < col + count) { |
| | clearAlias(key); |
| | } |
| | else if (key.col() >= col + count) { |
| | moveAlias(key, CellAddress(key.row(), key.col() - count)); |
| | } |
| | } |
| |
|
| | int spanRows, spanCols; |
| | for (const auto& key : keys) { |
| | std::map<CellAddress, Cell*>::iterator j = data.find(key); |
| |
|
| | assert(j != data.end()); |
| |
|
| | Cell* cell = j->second; |
| |
|
| | |
| | visitor.reset(); |
| | cell->visit(visitor); |
| | if (visitor.changed()) { |
| | setDirty(key); |
| | recomputeDependencies(key); |
| | } |
| |
|
| | if (key.col() >= col && key.col() < col + count) { |
| | clear(key, false); |
| | } |
| | else if (key.col() >= col + count) { |
| | moveCell(key, CellAddress(key.row(), key.col() - count), renames); |
| | } |
| | else if (cell->getSpans(spanRows, spanCols) && key.col() + spanCols >= col) { |
| | if (key.col() + spanCols >= col + count) { |
| | spanCols -= count; |
| | } |
| | else { |
| | spanCols = key.col() - col; |
| | } |
| | mergeCells( |
| | j->first, |
| | CellAddress(j->first.row() + spanRows - 1, j->first.col() + spanCols - 1) |
| | ); |
| | } |
| | } |
| |
|
| | const App::DocumentObject* docObj = static_cast<const App::DocumentObject*>(getContainer()); |
| | owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject* obj) { |
| | return obj != docObj; |
| | }); |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | unsigned int PropertySheet::getMemSize() const |
| | { |
| | return sizeof(*this); |
| | } |
| |
|
| |
|
| | bool PropertySheet::mergeCells(CellAddress from, CellAddress to) |
| | { |
| | if (from == to) { |
| | splitCell(from); |
| | return true; |
| | } |
| |
|
| | auto it = mergedCells.find(from); |
| | if (it != mergedCells.end()) { |
| | int rows, cols; |
| | cellAt(it->second)->getSpans(rows, cols); |
| | if (to.row() - from.row() + 1 == rows && to.col() - from.col() + 1 == cols) { |
| | return false; |
| | } |
| | } |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | |
| | for (int r = from.row(); r <= to.row(); ++r) { |
| | for (int c = from.col(); c <= to.col(); ++c) { |
| | splitCell(CellAddress(r, c)); |
| | } |
| | } |
| |
|
| | |
| | for (int r = from.row(); r <= to.row(); ++r) { |
| | for (int c = from.col(); c <= to.col(); ++c) { |
| | if (!(r == from.row() && c == from.col())) { |
| | clear(CellAddress(r, c)); |
| | } |
| | } |
| | } |
| |
|
| | |
| | for (int r = from.row(); r <= to.row(); ++r) { |
| | for (int c = from.col(); c <= to.col(); ++c) { |
| | mergedCells[CellAddress(r, c)] = from; |
| | setDirty(CellAddress(r, c)); |
| | } |
| | } |
| |
|
| | setSpans(from, to.row() - from.row() + 1, to.col() - from.col() + 1); |
| |
|
| | signaller.tryInvoke(); |
| |
|
| | return true; |
| | } |
| |
|
| | void PropertySheet::splitCell(CellAddress address) |
| | { |
| | int rows, cols; |
| | std::map<CellAddress, CellAddress>::const_iterator i = mergedCells.find(address); |
| |
|
| | if (i == mergedCells.end()) { |
| | return; |
| | } |
| |
|
| | CellAddress anchor = i->second; |
| | AtomicPropertyChange signaller(*this); |
| | cellAt(anchor)->getSpans(rows, cols); |
| |
|
| | for (int r = anchor.row(); r < anchor.row() + rows; ++r) { |
| | for (int c = anchor.col(); c < anchor.col() + cols; ++c) { |
| | setDirty(CellAddress(r, c)); |
| | mergedCells.erase(CellAddress(r, c)); |
| | } |
| | } |
| |
|
| | setSpans(anchor, -1, -1); |
| |
|
| | signaller.tryInvoke(); |
| | } |
| |
|
| | void PropertySheet::getSpans(CellAddress address, int& rows, int& cols) const |
| | { |
| | std::map<CellAddress, CellAddress>::const_iterator i = mergedCells.find(address); |
| |
|
| | if (i != mergedCells.end()) { |
| | CellAddress anchor = i->second; |
| |
|
| | if (anchor == address) { |
| | cellAt(anchor)->getSpans(rows, cols); |
| | } |
| | else { |
| | rows = cols = 1; |
| | } |
| | } |
| | else { |
| | rows = cols = 1; |
| | } |
| | } |
| |
|
| | App::CellAddress Spreadsheet::PropertySheet::getAnchor(App::CellAddress address) const |
| | { |
| | if (auto anchor = mergedCells.find(address); anchor != mergedCells.end()) { |
| | return anchor->second; |
| | } |
| | else { |
| | return address; |
| | } |
| | } |
| |
|
| | bool PropertySheet::isMergedCell(CellAddress address) const |
| | { |
| | return mergedCells.find(address) != mergedCells.end(); |
| | } |
| |
|
| | bool PropertySheet::isHidden(CellAddress address) const |
| | { |
| | std::map<CellAddress, CellAddress>::const_iterator i = mergedCells.find(address); |
| |
|
| | return i != mergedCells.end() && i->second != address; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | void PropertySheet::addDependencies(CellAddress key) |
| | { |
| | Cell* cell = getValue(key); |
| |
|
| | if (!cell) { |
| | return; |
| | } |
| |
|
| | cell->clearResolveException(); |
| |
|
| | const Expression* expression = cell->getExpression(); |
| |
|
| | if (!expression) { |
| | return; |
| | } |
| |
|
| | for (auto& var : expression->getIdentifiers()) { |
| | for (auto& dep : var.first.getDep(true)) { |
| | App::DocumentObject* docObj = dep.first; |
| |
|
| | std::string docObjName = docObj->getFullName(); |
| |
|
| | documentObjectToCellMap[docObjName].insert(key); |
| | cellToDocumentObjectMap[key].insert(docObjName); |
| | ++updateCount; |
| |
|
| | for (auto& name : dep.second) { |
| | std::string propName = docObjName + "." + name; |
| | FC_LOG("dep " << key.toString() << " -> " << name); |
| |
|
| | |
| | propertyNameToCellMap[propName].insert(key); |
| | cellToPropertyNameMap[key].insert(propName); |
| |
|
| | |
| | if (!name.empty() && docObj->isDerivedFrom<Sheet>()) { |
| | auto other = static_cast<Sheet*>(docObj); |
| | auto j = other->cells.revAliasProp.find(name); |
| |
|
| | if (j != other->cells.revAliasProp.end()) { |
| | propName = docObjName + "." + j->second.toString(); |
| | FC_LOG("dep " << key.toString() << " -> " << propName); |
| |
|
| | |
| | propertyNameToCellMap[propName].insert(key); |
| | cellToPropertyNameMap[key].insert(std::move(propName)); |
| | } |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | void PropertySheet::removeDependencies(CellAddress key) |
| | { |
| | |
| |
|
| | std::map<CellAddress, std::set<std::string>>::iterator i1 = cellToPropertyNameMap.find(key); |
| |
|
| | if (i1 != cellToPropertyNameMap.end()) { |
| | std::set<std::string>::const_iterator j = i1->second.begin(); |
| |
|
| | while (j != i1->second.end()) { |
| | std::map<std::string, std::set<CellAddress>>::iterator k = propertyNameToCellMap.find(*j); |
| |
|
| | |
| | if (k != propertyNameToCellMap.end()) { |
| | k->second.erase(key); |
| | } |
| | ++j; |
| | } |
| |
|
| | cellToPropertyNameMap.erase(i1); |
| | } |
| |
|
| | |
| |
|
| | std::map<CellAddress, std::set<std::string>>::iterator i2 = cellToDocumentObjectMap.find(key); |
| |
|
| | if (i2 != cellToDocumentObjectMap.end()) { |
| | std::set<std::string>::const_iterator j = i2->second.begin(); |
| |
|
| | while (j != i2->second.end()) { |
| | std::map<std::string, std::set<CellAddress>>::iterator k = documentObjectToCellMap.find(*j); |
| |
|
| | if (k != documentObjectToCellMap.end()) { |
| | k->second.erase(key); |
| |
|
| | if (k->second.empty()) { |
| | documentObjectToCellMap.erase(*j); |
| | } |
| | } |
| |
|
| | ++j; |
| | } |
| |
|
| | cellToDocumentObjectMap.erase(i2); |
| | ++updateCount; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | void PropertySheet::recomputeDependants(const App::DocumentObject* owner, const char* propName) |
| | { |
| | auto itD = _Deps.find(const_cast<App::DocumentObject*>(owner)); |
| | if (itD != _Deps.end() && itD->second) { |
| | |
| | |
| | |
| | Sheet* sheet = freecad_cast<Sheet*>(getContainer()); |
| | if (!sheet || sheet->testStatus(App::ObjectStatus::Recompute2) || !owner |
| | || owner->testStatus(App::ObjectStatus::Recompute2)) { |
| | return; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | std::string fullName = owner->getFullName() + "."; |
| | auto it = propertyNameToCellMap.find(fullName); |
| | if (it != propertyNameToCellMap.end()) { |
| | for (auto& cell : it->second) { |
| | setDirty(cell); |
| | } |
| | } |
| |
|
| | if (propName && *propName) { |
| | |
| | it = propertyNameToCellMap.find(fullName + propName); |
| | if (it != propertyNameToCellMap.end()) { |
| | for (auto& cell : it->second) { |
| | setDirty(cell); |
| | } |
| | } |
| | } |
| | } |
| |
|
| | void PropertySheet::breakLink(App::DocumentObject* obj, bool clear) |
| | { |
| | AtomicPropertyChange signaller(*this, false); |
| | PropertyExpressionContainer::breakLink(obj, clear); |
| | } |
| |
|
| | void PropertySheet::onBreakLink(App::DocumentObject* obj) |
| | { |
| | invalidateDependants(obj); |
| | } |
| |
|
| | void PropertySheet::hasSetChildValue(App::Property& prop) |
| | { |
| | ++updateCount; |
| | PropertyExpressionContainer::hasSetChildValue(prop); |
| | } |
| |
|
| | void PropertySheet::invalidateDependants(const App::DocumentObject* docObj) |
| | { |
| | depConnections.erase(docObj); |
| |
|
| | |
| | auto iter = documentObjectToCellMap.find(docObj->getFullName()); |
| | if (iter == documentObjectToCellMap.end()) { |
| | return; |
| | } |
| |
|
| | |
| | touch(); |
| |
|
| | AtomicPropertyChange signaller(*this); |
| |
|
| | for (const auto& address : iter->second) { |
| | Cell* cell = getValue(address); |
| | cell->setResolveException("Unresolved dependency"); |
| | setDirty(address); |
| | } |
| | } |
| |
|
| | void PropertySheet::slotChangedObject(const App::DocumentObject& obj, const App::Property& prop) |
| | { |
| | if (&obj == getContainer()) { |
| | if (&prop == this || !prop.getName() || revAliasProp.count(prop.getName())) { |
| | return; |
| | } |
| | if (stringToAddress(prop.getName(), true).isValid()) { |
| | return; |
| | } |
| | } |
| | recomputeDependants(&obj, prop.getName()); |
| | } |
| |
|
| | void PropertySheet::onAddDep(App::DocumentObject* obj) |
| | { |
| | |
| | depConnections[obj] = obj->signalChanged.connect( |
| | std::bind(&PropertySheet::slotChangedObject, this, sp::_1, sp::_2) |
| | ); |
| | |
| | } |
| |
|
| | void PropertySheet::onRemoveDep(App::DocumentObject* obj) |
| | { |
| | depConnections.erase(obj); |
| | } |
| |
|
| |
|
| | void PropertySheet::onRelabeledDocument(const App::Document& doc) |
| | { |
| | RelabelDocumentExpressionVisitor v(doc); |
| | for (auto& c : data) { |
| | c.second->visit(v); |
| | } |
| | } |
| |
|
| | void PropertySheet::onRenameDynamicProperty(const App::Property& prop, const char* oldName) |
| | { |
| | ObjectIdentifier oldNameId = ObjectIdentifier(prop.getContainer(), std::string(oldName)); |
| | ObjectIdentifier newNameId = ObjectIdentifier(prop); |
| | const std::map<ObjectIdentifier, ObjectIdentifier> paths = { |
| | {oldNameId, newNameId}, |
| | }; |
| |
|
| | renameObjectIdentifiers(paths); |
| | } |
| |
|
| | void PropertySheet::renameObjectIdentifiers( |
| | const std::map<App::ObjectIdentifier, App::ObjectIdentifier>& paths |
| | ) |
| | { |
| | RenameObjectIdentifierExpressionVisitor<PropertySheet> v {*this, paths, *this}; |
| | for (auto& c : data) { |
| | c.second->visit(v); |
| | if (v.changed()) { |
| | v.reset(); |
| | recomputeDependencies(c.first); |
| | setDirty(c.first); |
| | } |
| | } |
| | } |
| |
|
| | void PropertySheet::deletedDocumentObject(const App::DocumentObject* docObj) |
| | { |
| | (void)docObj; |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | } |
| |
|
| | void PropertySheet::documentSet() |
| | {} |
| |
|
| | const std::set<CellAddress>& PropertySheet::getDeps(const std::string& name) const |
| | { |
| | static std::set<CellAddress> empty; |
| | std::map<std::string, std::set<CellAddress>>::const_iterator i = propertyNameToCellMap.find(name); |
| |
|
| | if (i != propertyNameToCellMap.end()) { |
| | return i->second; |
| | } |
| | else { |
| | return empty; |
| | } |
| | } |
| |
|
| | const std::set<std::string>& PropertySheet::getDeps(CellAddress pos) const |
| | { |
| | static std::set<std::string> empty; |
| | std::map<CellAddress, std::set<std::string>>::const_iterator i = cellToPropertyNameMap.find(pos); |
| |
|
| | if (i != cellToPropertyNameMap.end()) { |
| | return i->second; |
| | } |
| | else { |
| | return empty; |
| | } |
| | } |
| |
|
| | void PropertySheet::recomputeDependencies(CellAddress key) |
| | { |
| | AtomicPropertyChange signaller(*this); |
| |
|
| | removeDependencies(key); |
| | addDependencies(key); |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | void PropertySheet::hasSetValue() |
| | { |
| | if (updateCount == 0 || !owner || !owner->isAttachedToDocument() || owner->isRestoring() |
| | || this != &owner->cells || testFlag(LinkDetached)) { |
| | PropertyExpressionContainer::hasSetValue(); |
| | return; |
| | } |
| |
|
| | updateCount = 0; |
| |
|
| | std::map<App::DocumentObject*, bool> deps; |
| | std::vector<std::string> labels; |
| | unregisterElementReference(); |
| | UpdateElementReferenceExpressionVisitor<PropertySheet> v(*this); |
| | for (auto& d : data) { |
| | auto expr = d.second->expression.get(); |
| | if (expr) { |
| | expr->getDepObjects(deps, &labels); |
| | if (!restoring) { |
| | expr->visit(v); |
| | } |
| | } |
| | } |
| | registerLabelReferences(std::move(labels)); |
| |
|
| | updateDeps(std::move(deps)); |
| |
|
| | PropertyExpressionContainer::hasSetValue(); |
| | } |
| |
|
| | PyObject* PropertySheet::getPyObject() |
| | { |
| | if (PythonObject.is(Py::_None())) { |
| | |
| | PythonObject = Py::Object(new PropertySheetPy(this), true); |
| | } |
| | return Py::new_reference_to(PythonObject); |
| | } |
| |
|
| | void PropertySheet::setPyObject(PyObject* obj) |
| | { |
| | if (!obj || !PyObject_TypeCheck(obj, &PropertySheetPy::Type)) { |
| | throw Base::TypeError("Invalid type"); |
| | } |
| | if (obj != PythonObject.ptr()) { |
| | Paste(*static_cast<PropertySheetPy*>(obj)->getPropertySheetPtr()); |
| | } |
| | } |
| |
|
| | PyObject* PropertySheet::getPyValue(PyObject* key) |
| | { |
| | assert(key); |
| |
|
| | PY_TRY |
| | { |
| | std::string addr = Py::Object(key).as_string(); |
| | CellAddress caddr = getCellAddress(addr.c_str(), true); |
| | if (caddr.isValid()) { |
| | auto prop = owner->getPropertyByName(caddr.toString().c_str()); |
| | if (prop) { |
| | return prop->getPyObject(); |
| | } |
| | Py_Return; |
| | } |
| |
|
| | Range range = getRange(Py::Object(key).as_string().c_str(), false); |
| | if (!range.from().isValid() || !range.to().isValid()) { |
| | return Py::new_reference_to(Py::Tuple()); |
| | } |
| |
|
| | Py::Tuple res(range.size()); |
| | int i = 0; |
| | do { |
| | addr = range.address(); |
| | auto prop = owner->getPropertyByName(addr.c_str()); |
| | res.setItem(i++, prop ? Py::asObject(prop->getPyObject()) : Py::Object()); |
| | } while (range.next()); |
| |
|
| | return Py::new_reference_to(res); |
| | } |
| | PY_CATCH |
| | } |
| |
|
| | void PropertySheet::afterRestore() |
| | { |
| | Base::FlagToggler<bool> flag(restoring); |
| | AtomicPropertyChange signaller(*this); |
| |
|
| | PropertyExpressionContainer::afterRestore(); |
| | { |
| | ObjectIdentifier::DocumentMapper mapper(this->_DocMap); |
| | for (auto& d : data) { |
| | d.second->afterRestore(); |
| | } |
| | } |
| |
|
| | for (auto& v : _XLinks) { |
| | auto& xlink = *v.second; |
| | if (!xlink.checkRestore()) { |
| | continue; |
| | } |
| | auto iter = documentObjectToCellMap.find(xlink.getValue()->getFullName()); |
| | if (iter == documentObjectToCellMap.end()) { |
| | continue; |
| | } |
| | touch(); |
| | for (const auto& address : iter->second) { |
| | setDirty(address); |
| | } |
| | } |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | void PropertySheet::onContainerRestored() |
| | { |
| | Base::FlagToggler<bool> flag(restoring); |
| | unregisterElementReference(); |
| | UpdateElementReferenceExpressionVisitor<PropertySheet> v(*this); |
| | for (auto& d : data) { |
| | auto expr = d.second->expression.get(); |
| | if (expr) { |
| | expr->visit(v); |
| | } |
| | } |
| | } |
| |
|
| | bool PropertySheet::adjustLink(const std::set<DocumentObject*>& inList) |
| | { |
| | AtomicPropertyChange signaller(*this, false); |
| | bool changed = false; |
| |
|
| | for (auto& d : data) { |
| | auto expr = d.second->expression.get(); |
| | if (!expr) { |
| | continue; |
| | } |
| | try { |
| | bool need_adjust = false; |
| | for (auto& v : expr->getDepObjects()) { |
| | auto docObj = v.first; |
| | if (v.second && docObj && docObj != owner && inList.count(docObj)) { |
| | need_adjust = true; |
| | break; |
| | } |
| | } |
| | if (!need_adjust) { |
| | continue; |
| | } |
| |
|
| | signaller.aboutToChange(); |
| | changed = true; |
| |
|
| | removeDependencies(d.first); |
| | expr->adjustLinks(inList); |
| | addDependencies(d.first); |
| | } |
| | catch (Base::Exception& e) { |
| | addDependencies(d.first); |
| | std::ostringstream ss; |
| | ss << "Failed to adjust link for " << owner->getFullName() << " in expression " |
| | << expr->toString() << ": " << e.what(); |
| | throw Base::RuntimeError(ss.str()); |
| | } |
| | } |
| | return changed; |
| | } |
| |
|
| | void PropertySheet::updateElementReference(DocumentObject* feature, bool reverse, bool notify) |
| | { |
| | (void)notify; |
| | if (!feature) { |
| | unregisterElementReference(); |
| | } |
| | UpdateElementReferenceExpressionVisitor<PropertySheet> visitor(*this, feature, reverse); |
| | for (auto& d : data) { |
| | auto expr = d.second->expression.get(); |
| | if (!expr) { |
| | continue; |
| | } |
| | expr->visit(visitor); |
| | } |
| | if (feature && visitor.changed()) { |
| | auto owner = dynamic_cast<App::DocumentObject*>(getContainer()); |
| | if (owner) { |
| | owner->onUpdateElementReference(this); |
| | } |
| | } |
| | } |
| |
|
| | bool PropertySheet::referenceChanged() const |
| | { |
| | return false; |
| | } |
| |
|
| | Property* PropertySheet::CopyOnImportExternal(const std::map<std::string, std::string>& nameMap) const |
| | { |
| | std::map<CellAddress, std::unique_ptr<Expression>> changed; |
| | for (auto& d : data) { |
| | auto e = d.second->expression.get(); |
| | if (!e) { |
| | continue; |
| | } |
| | auto expr = e->importSubNames(nameMap); |
| | if (!expr) { |
| | continue; |
| | } |
| | changed[d.first] = std::move(expr); |
| | } |
| | if (changed.empty()) { |
| | return nullptr; |
| | } |
| | std::unique_ptr<PropertySheet> copy(new PropertySheet(*this)); |
| | for (auto& change : changed) { |
| | copy->data[change.first]->setExpression(std::move(change.second)); |
| | } |
| | return copy.release(); |
| | } |
| |
|
| | Property* PropertySheet::CopyOnLabelChange( |
| | App::DocumentObject* obj, |
| | const std::string& ref, |
| | const char* newLabel |
| | ) const |
| | { |
| | std::map<CellAddress, std::unique_ptr<Expression>> changed; |
| | for (auto& d : data) { |
| | auto e = d.second->expression.get(); |
| | if (!e) { |
| | continue; |
| | } |
| | auto expr = e->updateLabelReference(obj, ref, newLabel); |
| | if (!expr) { |
| | continue; |
| | } |
| | changed[d.first] = std::move(expr); |
| | } |
| | if (changed.empty()) { |
| | return nullptr; |
| | } |
| | std::unique_ptr<PropertySheet> copy(new PropertySheet(*this)); |
| | for (auto& change : changed) { |
| | copy->data[change.first]->setExpression(std::move(change.second)); |
| | } |
| | return copy.release(); |
| | } |
| |
|
| | Property* PropertySheet::CopyOnLinkReplace( |
| | const App::DocumentObject* parent, |
| | App::DocumentObject* oldObj, |
| | App::DocumentObject* newObj |
| | ) const |
| | { |
| | std::map<CellAddress, std::unique_ptr<Expression>> changed; |
| | for (auto& d : data) { |
| | auto e = d.second->expression.get(); |
| | if (!e) { |
| | continue; |
| | } |
| | auto expr = e->replaceObject(parent, oldObj, newObj); |
| | if (!expr) { |
| | continue; |
| | } |
| | changed[d.first] = std::move(expr); |
| | } |
| | if (changed.empty()) { |
| | return nullptr; |
| | } |
| | std::unique_ptr<PropertySheet> copy(new PropertySheet(*this)); |
| | for (auto& change : changed) { |
| | copy->data[change.first]->setExpression(std::move(change.second)); |
| | } |
| | return copy.release(); |
| | } |
| |
|
| | std::map<App::ObjectIdentifier, const App::Expression*> PropertySheet::getExpressions() const |
| | { |
| | std::map<App::ObjectIdentifier, const Expression*> res; |
| | for (auto& d : data) { |
| | if (d.second->expression) { |
| | res[ObjectIdentifier(owner, d.first.toString())] = d.second->getExpression(true); |
| | } |
| | } |
| | return res; |
| | } |
| |
|
| | void PropertySheet::setExpressions(std::map<App::ObjectIdentifier, App::ExpressionPtr>&& exprs) |
| | { |
| | AtomicPropertyChange signaller(*this); |
| | for (auto& v : exprs) { |
| | CellAddress addr(v.first.getPropertyName().c_str()); |
| | auto& cell = data[addr]; |
| | if (!cell) { |
| | if (!v.second) { |
| | continue; |
| | } |
| | cell = new Cell(addr, this); |
| | } |
| | if (!v.second) { |
| | clear(addr); |
| | } |
| | else { |
| | cell->setExpression(std::move(v.second)); |
| | } |
| | } |
| | signaller.tryInvoke(); |
| | } |
| |
|
| | App::CellAddress PropertySheet::getCellAddress(const char* addr, bool silent) const |
| | { |
| | assert(addr); |
| | CellAddress caddr; |
| | const Cell* cell = getValueFromAlias(addr); |
| | if (cell) { |
| | return cell->getAddress(); |
| | } |
| | else { |
| | return stringToAddress(addr, silent); |
| | } |
| | } |
| |
|
| | App::Range PropertySheet::getRange(const char* range, bool silent) const |
| | { |
| | assert(range); |
| | const char* sep = strchr(range, ':'); |
| | CellAddress from, to; |
| | if (!sep) { |
| | from = to = getCellAddress(range, silent); |
| | } |
| | else { |
| | std::string addr(range, sep); |
| |
|
| | auto findCell = [this, &addr](CellAddress caddr, int r, int c) -> CellAddress { |
| | if (!getValue(caddr)) { |
| | return CellAddress(); |
| | } |
| | if (addr == "-") { |
| | r = 0; |
| | } |
| | else { |
| | c = 0; |
| | } |
| | for (;;) { |
| | caddr.setRow(caddr.row() + r); |
| | caddr.setCol(caddr.col() + c); |
| | if (!caddr.isValid() || !getValue(caddr)) { |
| | break; |
| | } |
| | } |
| | caddr.setRow(caddr.row() - r); |
| | caddr.setCol(caddr.col() - c); |
| | return caddr; |
| | }; |
| |
|
| | if (addr == "-" || addr == "|") { |
| | to = getCellAddress(sep + 1, silent); |
| | return Range(findCell(to, -1, -1), from); |
| | } |
| | else { |
| | from = getCellAddress(addr.c_str(), silent); |
| | addr = sep + 1; |
| | if (addr == "-" || addr == "|") { |
| | return Range(from, findCell(from, 1, 1)); |
| | } |
| | to = getCellAddress(addr.c_str(), silent); |
| | } |
| | } |
| |
|
| | if (!from.isValid() || !to.isValid()) { |
| | return App::Range(App::CellAddress(), App::CellAddress()); |
| | } |
| | return App::Range(from, to); |
| | } |
| |
|
| | bool PropertySheet::isBindingPath( |
| | const ObjectIdentifier& path, |
| | CellAddress* from, |
| | CellAddress* to, |
| | bool* href |
| | ) const |
| | { |
| | const auto& comps = path.getComponents(); |
| | if (comps.size() != 4 || !comps[2].isSimple() || !comps[3].isSimple() |
| | || (comps[1].getName() != "Bind" && comps[1].getName() != "BindHREF" |
| | && comps[1].getName() != "BindHiddenRef") |
| | || path.getProperty() != this) { |
| | return false; |
| | } |
| | if (href) { |
| | *href = (comps[1].getName() == "BindHREF" || comps[1].getName() == "BindHiddenRef"); |
| | } |
| | if (from) { |
| | *from = CellAddress(comps[2].getName()); |
| | } |
| | if (to) { |
| | *to = CellAddress(comps[3].getName()); |
| | } |
| | return true; |
| | } |
| |
|
| | PropertySheet::BindingType PropertySheet::getBinding( |
| | const Range& range, |
| | ExpressionPtr* pStart, |
| | ExpressionPtr* pEnd, |
| | App::ObjectIdentifier* pTarget |
| | ) const |
| | { |
| | if (!owner) { |
| | return BindingNone; |
| | } |
| |
|
| | for (int href = 0; href < 2; ++href) { |
| | ObjectIdentifier path(*this); |
| | path << ObjectIdentifier::SimpleComponent(href ? "BindHiddenRef" : "Bind"); |
| | path << ObjectIdentifier::SimpleComponent(range.from().toString().c_str()); |
| | path << ObjectIdentifier::SimpleComponent(range.to().toString().c_str()); |
| | auto res = owner->getExpression(path); |
| | if (res.expression && res.expression->isDerivedFrom<FunctionExpression>()) { |
| | auto expr = static_cast<FunctionExpression*>(res.expression.get()); |
| | if (href) { |
| | if ((expr->getFunction() != FunctionExpression::HIDDENREF |
| | && expr->getFunction() != FunctionExpression::HREF) |
| | || expr->getArgs().size() != 1 |
| | || !expr->getArgs().front()->isDerivedFrom(FunctionExpression::getClassTypeId())) { |
| | continue; |
| | } |
| | expr = static_cast<FunctionExpression*>(expr->getArgs().front()); |
| | } |
| |
|
| | if (expr->getFunction() == FunctionExpression::TUPLE && expr->getArgs().size() == 3) { |
| | if (pTarget) { |
| | if (auto e = freecad_cast<VariableExpression*>(expr->getArgs()[0])) { |
| | *pTarget = e->getPath(); |
| | } |
| | } |
| | if (pStart) { |
| | pStart->reset(expr->getArgs()[1]->copy()); |
| | } |
| | if (pEnd) { |
| | pEnd->reset(expr->getArgs()[2]->copy()); |
| | } |
| | return href ? BindingHiddenRef : BindingNormal; |
| | } |
| | } |
| | } |
| | return BindingNone; |
| | } |
| |
|
| | void PropertySheet::setPathValue(const ObjectIdentifier& path, const boost::any& value) |
| | { |
| | if (!owner) { |
| | FC_THROWM(Base::RuntimeError, "Invalid state"); |
| | } |
| |
|
| | bool href = false; |
| | CellAddress from, to; |
| | if (!isBindingPath(path, &from, &to, &href)) { |
| | FC_THROWM( |
| | Base::IndexError, |
| | "Invalid binding of '" << path.toString() << "' in " << getFullName() |
| | ); |
| | } |
| |
|
| | Base::PyGILStateLocker lock; |
| | Py::Object pyValue = pyObjectFromAny(value); |
| |
|
| | if (pyValue.isSequence()) { |
| | Py::Sequence seq(pyValue); |
| | if (seq.size() == 3 && PyObject_TypeCheck(seq[0].ptr(), &PropertySheetPy::Type) |
| | && Py::Object(seq[1].ptr()).isString() && Py::Object(seq[2].ptr()).isString()) { |
| | AtomicPropertyChange signaller(*this, false); |
| | auto other = static_cast<PropertySheetPy*>(seq[0].ptr())->getPropertySheetPtr(); |
| | auto otherOwner = freecad_cast<App::DocumentObject*>(other->getContainer()); |
| | if (!otherOwner) { |
| | FC_THROWM( |
| | Base::RuntimeError, |
| | "Invalid binding of '" << other->getFullName() << " in " << getFullName() |
| | ); |
| | } |
| |
|
| | App::CellAddress targetFrom |
| | = other->getCellAddress(Py::Object(seq[1].ptr()).as_string().c_str(), false); |
| |
|
| | App::CellAddress targetTo |
| | = other->getCellAddress(Py::Object(seq[2].ptr()).as_string().c_str(), false); |
| |
|
| | std::string expr(href ? "hiddenref(" : ""); |
| | if (other != this) { |
| | if (otherOwner->getDocument() == owner->getDocument()) { |
| | expr += otherOwner->getNameInDocument(); |
| | } |
| | else { |
| | expr += otherOwner->getFullName(); |
| | } |
| | } |
| | expr += "."; |
| | std::size_t exprSize = expr.size(); |
| |
|
| | auto normalize = [](CellAddress& from, CellAddress& to) { |
| | if (from.row() > to.row()) { |
| | int tmp = from.row(); |
| | from.setRow(to.row()); |
| | to.setRow(tmp); |
| | } |
| | if (from.col() > to.col()) { |
| | int tmp = from.col(); |
| | from.setCol(to.col()); |
| | to.setCol(tmp); |
| | } |
| | }; |
| |
|
| | normalize(from, to); |
| | normalize(targetFrom, targetTo); |
| | App::Range totalRange(from, to); |
| | std::set<CellAddress> touched; |
| |
|
| | while (from.row() <= to.row() && from.col() <= to.col() |
| | && targetFrom.row() <= targetTo.row() && targetFrom.col() <= targetTo.col()) { |
| | App::Range range(from, to); |
| | App::Range rangeTarget(targetFrom, targetTo); |
| | int rowCount = std::min(range.rowCount(), rangeTarget.rowCount()); |
| | int colCount = std::min(range.colCount(), rangeTarget.colCount()); |
| | if (rowCount == range.rowCount()) { |
| | from.setCol(from.col() + colCount); |
| | } |
| | else if (colCount == range.colCount()) { |
| | from.setRow(from.row() + rowCount); |
| | } |
| | if (rowCount == rangeTarget.rowCount()) { |
| | targetFrom.setCol(targetFrom.col() + colCount); |
| | } |
| | else if (colCount == rangeTarget.colCount()) { |
| | targetFrom.setRow(targetFrom.row() + rowCount); |
| | } |
| |
|
| | range = App::Range( |
| | range.from().row(), |
| | range.from().col(), |
| | range.from().row() + rowCount - 1, |
| | range.from().col() + colCount - 1 |
| | ); |
| | rangeTarget = App::Range( |
| | rangeTarget.from().row(), |
| | rangeTarget.from().col(), |
| | rangeTarget.from().row() + rowCount - 1, |
| | rangeTarget.from().col() + colCount - 1 |
| | ); |
| | do { |
| | CellAddress target(*rangeTarget); |
| | CellAddress source(*range); |
| | if (other == this && source.row() >= rangeTarget.from().row() |
| | && source.row() <= rangeTarget.to().row() |
| | && source.col() >= rangeTarget.from().col() |
| | && source.col() <= rangeTarget.to().col()) { |
| | continue; |
| | } |
| |
|
| | Cell* dst = other->getValue(target); |
| | Cell* src = getValue(source); |
| | if (!dst || !dst->getExpression()) { |
| | continue; |
| | } |
| |
|
| | touched.insert(source); |
| |
|
| | if (!src) { |
| | signaller.aboutToChange(); |
| | src = createCell(source); |
| | } |
| |
|
| | std::string alias; |
| | if (this != other && dst->getAlias(alias)) { |
| | auto* oldCell = getValueFromAlias(alias); |
| | if (oldCell && oldCell != dst) { |
| | signaller.aboutToChange(); |
| | oldCell->setAlias(""); |
| | } |
| | std::string oldAlias; |
| | if (!src->getAlias(oldAlias) || oldAlias != alias) { |
| | signaller.aboutToChange(); |
| | setAlias(source, alias); |
| | } |
| | } |
| |
|
| | expr.resize(exprSize); |
| | expr += rangeTarget.address(); |
| | if (href) { |
| | expr += ")"; |
| | } |
| | auto e = App::ExpressionPtr(App::Expression::parse(owner, expr)); |
| | auto e2 = src->getExpression(); |
| | if (!e2 || !e->isSame(*e2, false)) { |
| | signaller.aboutToChange(); |
| | src->setExpression(std::move(e)); |
| | } |
| |
|
| | } while (range.next() && rangeTarget.next()); |
| | } |
| |
|
| | if (totalRange.size() != (int)touched.size()) { |
| | do { |
| | CellAddress addr(*totalRange); |
| | if (touched.count(addr)) { |
| | continue; |
| | } |
| | Cell* src = getValue(addr); |
| | if (src && src->getExpression()) { |
| | signaller.aboutToChange(); |
| | src->setExpression(nullptr); |
| | } |
| | } while (totalRange.next()); |
| | } |
| |
|
| | owner->rangeUpdated(totalRange); |
| | signaller.tryInvoke(); |
| | return; |
| | } |
| | } |
| |
|
| | FC_THROWM(Base::TypeError, "Invalid path value '" << "' for " << getFullName()); |
| | } |
| |
|
| | const boost::any PropertySheet::getPathValue(const App::ObjectIdentifier& path) const |
| | { |
| | if (isBindingPath(path)) { |
| | return boost::any(); |
| | } |
| | return path.getValue(); |
| | } |
| |
|
| | bool PropertySheet::hasSpan() const |
| | { |
| | return !mergedCells.empty(); |
| | } |
| |
|
| | void PropertySheet::getLinksTo( |
| | std::vector<App::ObjectIdentifier>& identifiers, |
| | App::DocumentObject* obj, |
| | const char* subname, |
| | bool all |
| | ) const |
| | { |
| | Expression::DepOption option = all ? Expression::DepOption::DepAll |
| | : Expression::DepOption::DepNormal; |
| |
|
| | App::SubObjectT objT(obj, subname); |
| | auto subObject = objT.getSubObject(); |
| | auto subElement = objT.getOldElementName(); |
| |
|
| | auto owner = freecad_cast<App::DocumentObject*>(getContainer()); |
| | for (const auto& [cellName, cellExpression] : data) { |
| | if (auto expr = cellExpression->getExpression()) { |
| | const auto& deps = expr->getDeps(option); |
| | auto it = deps.find(obj); |
| | if (it == deps.end()) { |
| | continue; |
| | } |
| | const auto [docObj, depsList] = *it; |
| | for (auto& [depName, paths] : depsList) { |
| | if (!subname) { |
| | identifiers.emplace_back(owner, cellName.toString()); |
| | break; |
| | } |
| | if (std::any_of( |
| | paths.begin(), |
| | paths.end(), |
| | [subname, obj, subObject, &subElement](const auto& path) { |
| | if (path.getSubObjectName() == subname) { |
| | return true; |
| | } |
| |
|
| | App::SubObjectT sobjT(obj, path.getSubObjectName().c_str()); |
| | return ( |
| | sobjT.getSubObject() == subObject |
| | && sobjT.getOldElementName() == subElement |
| | ); |
| | } |
| | )) { |
| | identifiers.emplace_back(owner, cellName.toString()); |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|