From b4751145b4fd0cb9fbd1b01fdb21998b1174a73f Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 29 Jun 2019 17:36:37 +0800 Subject: [PATCH] Spreadsheet: convert PropertySheet to link type property PropertySheet is changed to derive from PropertyExpressionContainer, which makes it a link type property that is capable of external linking. It now relies on the unified link property API to manage object depenency, and tracking of object life time, relabeling, etc. This patch also includes various fix and improvement of Spreadsheet, such as improved recompute efficiency, correct handling of document label change, etc. --- src/Mod/Spreadsheet/App/Cell.cpp | 219 ++++-- src/Mod/Spreadsheet/App/Cell.h | 13 +- src/Mod/Spreadsheet/App/PropertySheet.cpp | 826 +++++++++++++--------- src/Mod/Spreadsheet/App/PropertySheet.h | 71 +- src/Mod/Spreadsheet/App/Sheet.cpp | 466 +++++++----- src/Mod/Spreadsheet/App/Sheet.h | 83 +-- 6 files changed, 1024 insertions(+), 654 deletions(-) diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index 4797bbaff2..4cc3f7c631 100644 --- a/src/Mod/Spreadsheet/App/Cell.cpp +++ b/src/Mod/Spreadsheet/App/Cell.cpp @@ -25,16 +25,21 @@ #ifndef _PreComp_ #endif +#include +#include #include "Cell.h" #include "Utils.h" #include #include #include #include +#include #include #include "Sheet.h" #include +FC_LOG_LEVEL_INIT("Spreadsheet",true,true); + #ifdef _MSC_VER #define __func__ __FUNCTION__ #endif @@ -43,6 +48,22 @@ using namespace App; using namespace Base; using namespace Spreadsheet; +///////////////////////////////////////////////////////// + +// expose the read() function for simpler partial xml reading in setExpression() +class ReaderPrivate: public Base::XMLReader { +public: + ReaderPrivate(const char* FileName, std::istream &is) + :XMLReader(FileName,is) + {} + + bool read() { + return XMLReader::read(); + } +}; + +/////////////////////////////////////////////////////////// + const int Cell::EXPRESSION_SET = 1; const int Cell::ALIGNMENT_SET = 4; const int Cell::STYLE_SET = 8; @@ -85,7 +106,7 @@ Cell::Cell(const CellAddress &_address, PropertySheet *_owner) , alignment(ALIGNMENT_HIMPLIED | ALIGNMENT_LEFT | ALIGNMENT_VIMPLIED | ALIGNMENT_VCENTER) , style() , foregroundColor(0, 0, 0, 1) - , backgroundColor(1, 1, 1, 0) + , backgroundColor(1, 1, 1, 1) , displayUnit() , alias() , computedUnit() @@ -100,7 +121,7 @@ Cell::Cell(PropertySheet *_owner, const Cell &other) : address(other.address) , owner(_owner) , used(other.used) - , expression(other.expression ? other.expression->copy() : 0) + , expression(other.expression ? other.expression->copy() : nullptr) , alignment(other.alignment) , style(other.style) , foregroundColor(other.foregroundColor) @@ -112,6 +133,7 @@ Cell::Cell(PropertySheet *_owner, const Cell &other) , colSpan(other.colSpan) { setUsed(MARK_SET, false); + setDirty(); } Cell &Cell::operator =(const Cell &rhs) @@ -131,7 +153,9 @@ Cell &Cell::operator =(const Cell &rhs) setSpans(rhs.rowSpan, rhs.colSpan); setUsed(MARK_SET, false); + setDirty(); + signaller.tryInvoke(); return *this; } @@ -155,18 +179,40 @@ void Cell::setExpression(App::Expression *expr) { PropertySheet::AtomicPropertyChange signaller(*owner); + owner->setDirty(address); + /* Remove dependencies */ owner->removeDependencies(address); + if(expr && expr->comment.size()) { + if(!boost::starts_with(expr->comment,"sheet()->getFullName() << '.' << address.toString()); + else { + try { + std::istringstream in(expr->comment); + ReaderPrivate reader("", in); + reader.read(); + restore(reader,true); + }catch(Base::Exception &e) { + e.ReportException(); + FC_ERR("Failed to restore style of cell " + << owner->sheet()->getFullName() << '.' + << address.toString() << ": " << e.what()); + } + } + expr->comment.clear(); + } + if (expression) delete expression; expression = expr; - setUsed(EXPRESSION_SET, expression != 0); + setUsed(EXPRESSION_SET, !!expression); /* Update dependencies */ owner->addDependencies(address); - owner->rebuildDocDepList(); + signaller.tryInvoke(); } /** @@ -174,9 +220,23 @@ void Cell::setExpression(App::Expression *expr) * */ -const App::Expression *Cell::getExpression() const +const App::Expression *Cell::getExpression(bool withFormat) const { - return expression; + if(withFormat && expression) { + if((used & (ALIGNMENT_SET + | STYLE_SET + | FOREGROUND_COLOR_SET + | BACKGROUND_COLOR_SET + | DISPLAY_UNIT_SET + | ALIAS_SET + | SPANS_SET))) + { + std::ostringstream ss; + save(ss,"",true); + expression->comment = ss.str(); + } + } + return expression.get(); } /** @@ -184,7 +244,7 @@ const App::Expression *Cell::getExpression() const * */ -bool Cell::getStringContent(std::string & s) const +bool Cell::getStringContent(std::string & s, bool persistent) const { if (expression) { if (freecad_dynamic_cast(expression)) { @@ -211,13 +271,24 @@ bool Cell::getStringContent(std::string & s) const } } +void Cell::afterRestore() { + auto expr = freecad_dynamic_cast(expression); + if(expr) + setContent(expr->getText().c_str()); +} + void Cell::setContent(const char * value) { PropertySheet::AtomicPropertyChange signaller(*owner); App::Expression * expr = 0; - setUsed(PARSE_EXCEPTION_SET, false); + clearException(); if (value != 0) { + if(owner->sheet()->isRestoring()) { + expression = new App::StringExpression(owner->sheet(),value); + setUsed(EXPRESSION_SET, true); + return; + } if (*value == '=') { try { expr = App::ExpressionParser::parse(owner->sheet(), value + 1); @@ -248,7 +319,21 @@ void Cell::setContent(const char * value) } } - setExpression(expr); + try { + setExpression(expr); + signaller.tryInvoke(); + } catch (Base::Exception &e) { + if(value) { + std::string _value; + if(*value != '=') { + _value = "="; + _value += value; + value = _value.c_str(); + } + setExpression(new App::StringExpression(owner->sheet(), value)); + setParseException(e.what()); + } + } } /** @@ -265,6 +350,8 @@ void Cell::setAlignment(int _alignment) alignment = _alignment; setUsed(ALIGNMENT_SET, alignment != (ALIGNMENT_HIMPLIED | ALIGNMENT_LEFT | ALIGNMENT_VIMPLIED | ALIGNMENT_VCENTER)); + setDirty(); + signaller.tryInvoke(); } } @@ -291,6 +378,9 @@ void Cell::setStyle(const std::set & _style) style = _style; setUsed(STYLE_SET, style.size() > 0); + setDirty(); + + signaller.tryInvoke(); } } @@ -317,6 +407,9 @@ void Cell::setForeground(const App::Color &color) foregroundColor = color; setUsed(FOREGROUND_COLOR_SET, foregroundColor != App::Color(0, 0, 0, 1)); + setDirty(); + + signaller.tryInvoke(); } } @@ -343,6 +436,9 @@ void Cell::setBackground(const App::Color &color) backgroundColor = color; setUsed(BACKGROUND_COLOR_SET, backgroundColor != App::Color(1, 1, 1, 0)); + setDirty(); + + signaller.tryInvoke(); } } @@ -380,6 +476,9 @@ void Cell::setDisplayUnit(const std::string &unit) displayUnit = newDisplayUnit; setUsed(DISPLAY_UNIT_SET, !displayUnit.isEmpty()); + setDirty(); + + signaller.tryInvoke(); } } @@ -414,7 +513,9 @@ void Cell::setAlias(const std::string &n) owner->aliasProp.erase(address); setUsed(ALIAS_SET, !alias.empty()); + setDirty(); + signaller.tryInvoke(); } } @@ -435,6 +536,9 @@ void Cell::setComputedUnit(const Base::Unit &unit) computedUnit = unit; setUsed(COMPUTED_UNIT_SET, !computedUnit.isEmpty()); + setDirty(); + + signaller.tryInvoke(); } /** @@ -465,6 +569,8 @@ void Cell::setSpans(int rows, int columns) colSpan = (columns == -1 ? 1 : columns); setUsed(SPANS_SET, (rowSpan != 1 || colSpan != 1) ); setUsed(SPANS_UPDATED); + setDirty(); + signaller.tryInvoke(); } } @@ -482,18 +588,30 @@ bool Cell::getSpans(int &rows, int &columns) const void Cell::setException(const std::string &e) { + if(e.size() && owner && owner->sheet()) { + FC_ERR(owner->sheet()->getFullName() << '.' + << address.toString() << ": " << e); + } exceptionStr = e; setUsed(EXCEPTION_SET); } void Cell::setParseException(const std::string &e) { + if(e.size() && owner && owner->sheet()) { + FC_ERR(owner->sheet()->getFullName() << '.' + << address.toString() << ": " << e); + } exceptionStr = e; setUsed(PARSE_EXCEPTION_SET); } void Cell::setResolveException(const std::string &e) { + if(e.size() && owner && owner->sheet()) { + FC_LOG(owner->sheet()->getFullName() << '.' + << address.toString() << ": " << e); + } exceptionStr = e; setUsed(RESOLVE_EXCEPTION_SET); } @@ -505,14 +623,22 @@ void Cell::clearResolveException() void Cell::clearException() { - if (!isUsed(PARSE_EXCEPTION_SET)) - exceptionStr = ""; + exceptionStr.clear(); setUsed(EXCEPTION_SET, false); + setUsed(RESOLVE_EXCEPTION_SET, false); + setUsed(PARSE_EXCEPTION_SET, false); } void Cell::clearDirty() { - owner->clearDirty(address); + if(owner) + owner->clearDirty(address); +} + +void Cell::setDirty() +{ + if(owner) + owner->setDirty(address); } /** @@ -530,7 +656,7 @@ void Cell::moveAbsolute(CellAddress newAddress) * */ -void Cell::restore(Base::XMLReader &reader) +void Cell::restore(Base::XMLReader &reader, bool checkAlias) { const char* style = reader.hasAttribute("style") ? reader.getAttribute("style") : 0; const char* alignment = reader.hasAttribute("alignment") ? reader.getAttribute("alignment") : 0; @@ -585,7 +711,7 @@ void Cell::restore(Base::XMLReader &reader) } if (displayUnit) setDisplayUnit(displayUnit); - if (alias) + if (alias && (!checkAlias || !owner->revAliasProp.count(alias))) setAlias(alias); if (rowSpan || colSpan) { @@ -601,46 +727,55 @@ void Cell::restore(Base::XMLReader &reader) * */ -void Cell::save(Base::Writer &writer) const -{ +void Cell::save(Base::Writer &writer) const { + save(writer.Stream(),writer.ind(),false); +} + +void Cell::save(std::ostream &os, const char *indent, bool noContent) const { if (!isUsed()) return; - writer.Stream() << writer.ind() << "" << std::endl; + if(editMode) + os << "editMode=\"" << editMode << "\" "; + + os << "/>"; + if(!noContent) + os << std::endl; } /** @@ -654,8 +789,6 @@ void Cell::setUsed(int mask, bool state) used |= mask; else used &= ~mask; - - owner->setDirty(address); } /** @@ -696,23 +829,27 @@ void Cell::visit(App::ExpressionVisitor &v) int Cell::decodeAlignment(const std::string & itemStr, int alignment) { - if (itemStr == "himplied") - alignment = (alignment & ~Cell::ALIGNMENT_HORIZONTAL) | Cell::ALIGNMENT_HIMPLIED; - else if (itemStr == "left") + if (itemStr == "himplied") { + if(!(alignment & ALIGNMENT_HORIZONTAL)) + alignment |= ALIGNMENT_LEFT; + alignment |= Cell::ALIGNMENT_HIMPLIED; + } else if (itemStr == "left") alignment = (alignment & ~Cell::ALIGNMENT_HORIZONTAL) | Cell::ALIGNMENT_LEFT; else if (itemStr == "center") alignment = (alignment & ~Cell::ALIGNMENT_HORIZONTAL) | Cell::ALIGNMENT_HCENTER; else if (itemStr == "right") alignment = (alignment & ~Cell::ALIGNMENT_HORIZONTAL) | Cell::ALIGNMENT_RIGHT; - else if (itemStr == "vimplied") - alignment = (alignment & ~Cell::ALIGNMENT_VERTICAL) | Cell::ALIGNMENT_VIMPLIED; - else if (itemStr == "top") + else if (itemStr == "vimplied") { + if(!(alignment & ALIGNMENT_VERTICAL)) + alignment |= ALIGNMENT_VCENTER; + alignment |= Cell::ALIGNMENT_VIMPLIED; + } else if (itemStr == "top") alignment = (alignment & ~Cell::ALIGNMENT_VERTICAL) | Cell::ALIGNMENT_TOP; else if (itemStr == "vcenter") alignment = (alignment & ~Cell::ALIGNMENT_VERTICAL) | Cell::ALIGNMENT_VCENTER; else if (itemStr == "bottom") alignment = (alignment & ~Cell::ALIGNMENT_VERTICAL) | Cell::ALIGNMENT_BOTTOM; - else + else if(itemStr.size()) throw Base::ValueError("Invalid alignment."); return alignment; diff --git a/src/Mod/Spreadsheet/App/Cell.h b/src/Mod/Spreadsheet/App/Cell.h index 0f23c8ce73..b7f088a48b 100644 --- a/src/Mod/Spreadsheet/App/Cell.h +++ b/src/Mod/Spreadsheet/App/Cell.h @@ -27,6 +27,7 @@ #include #include #include +#include #include "DisplayUnit.h" #include "Utils.h" @@ -59,9 +60,9 @@ public: ~Cell(); - const App::Expression * getExpression() const; + const App::Expression * getExpression(bool withFormat=false) const; - bool getStringContent(std::string & s) const; + bool getStringContent(std::string & s, bool persistent=false) const; void setContent(const char * value); @@ -95,6 +96,8 @@ public: void clearDirty(); + void setDirty(); + void setResolveException(const std::string &e); void clearResolveException(); @@ -105,9 +108,12 @@ public: void moveAbsolute(App::CellAddress newAddress); - void restore(Base::XMLReader &reader); + void restore(Base::XMLReader &reader, bool checkAlias=false); + + void afterRestore(); void save(Base::Writer &writer) const; + void save(std::ostream &os, const char *indent, bool noContent) const; bool isUsed() const; @@ -190,6 +196,7 @@ private: int colSpan; std::string exceptionStr; App::CellAddress anchor; + friend class PropertySheet; }; } diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 8ed557f445..3c358fc645 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -30,60 +30,26 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include "PropertySheet.h" #include "Sheet.h" #include "Utils.h" #include #include +FC_LOG_LEVEL_INIT("Spreadsheet", true, true); using namespace App; using namespace Base; using namespace Spreadsheet; -namespace Spreadsheet { - -class BuildDocDepsExpressionVisitor : public ExpressionModifier { -public: - - BuildDocDepsExpressionVisitor(PropertySheet & prop, std::set & _docDeps) - : ExpressionModifier(prop) - , docDeps(_docDeps) - { - - } - - void visit(Expression * node) { - VariableExpression *expr = freecad_dynamic_cast(node); - - if (expr) { - try { - const App::Property * prop = expr->getProperty(); - App::DocumentObject * docObj = freecad_dynamic_cast(prop->getContainer()); - - if (docObj) { - setExpressionChanged(); - docDeps.insert(docObj); - } - } - catch (const Base::Exception &) { - // Ignore this type of exception; it means that the property was not found, which is ok here - } - } - } - -private: - std::set & docDeps; -}; - -} - -TYPESYSTEM_SOURCE(Spreadsheet::PropertySheet , App::Property); +TYPESYSTEM_SOURCE(Spreadsheet::PropertySheet , App::PropertyExpressionContainer); void PropertySheet::clear() { @@ -105,9 +71,10 @@ void PropertySheet::clear() cellToPropertyNameMap.clear(); documentObjectToCellMap.clear(); cellToDocumentObjectMap.clear(); - docDeps.clear(); aliasProp.clear(); revAliasProp.clear(); + + clearDeps(); } Cell *PropertySheet::getValue(CellAddress key) @@ -196,6 +163,18 @@ void PropertySheet::setDirty(CellAddress address) dirty.insert(address); } +void PropertySheet::setDirty() +{ + AtomicPropertyChange signaller(*this); + for(auto &address : getUsedCells()) { + 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); @@ -206,27 +185,22 @@ Cell * PropertySheet::createCell(CellAddress address) } PropertySheet::PropertySheet(Sheet *_owner) - : Property() - , AtomicPropertyChangeInterface() - , owner(_owner) + : owner(_owner) + , updateCount(0) { } PropertySheet::PropertySheet(const PropertySheet &other) - : Property() - , AtomicPropertyChangeInterface() - , dirty(other.dirty) + : dirty(other.dirty) , mergedCells(other.mergedCells) , owner(other.owner) , propertyNameToCellMap(other.propertyNameToCellMap) , cellToPropertyNameMap(other.cellToPropertyNameMap) , documentObjectToCellMap(other.documentObjectToCellMap) , cellToDocumentObjectMap(other.cellToDocumentObjectMap) - , docDeps(other.docDeps) - , documentObjectName(other.documentObjectName) - , documentName(other.documentName) , aliasProp(other.aliasProp) , revAliasProp(other.revAliasProp) + , updateCount(other.updateCount) { std::map::const_iterator i = other.data.begin(); @@ -267,15 +241,14 @@ void PropertySheet::Paste(const Property &from) if (i != data.end()) { *(data[ifrom->first]) = *(ifrom->second); // Exists; assign cell directly - recomputeDependencies(ifrom->first); } else { data[ifrom->first] = new Cell(this, *(ifrom->second)); // Doesn't exist, copy using Cell's copy constructor } + recomputeDependencies(ifrom->first); /* Set dirty */ setDirty(ifrom->first); - ++ifrom; } @@ -296,6 +269,7 @@ void PropertySheet::Paste(const Property &from) } mergedCells = froms->mergedCells; + signaller.tryInvoke(); } void PropertySheet::Save(Base::Writer &writer) const @@ -310,8 +284,13 @@ void PropertySheet::Save(Base::Writer &writer) const ++ci; } - writer.Stream() << writer.ind() << "" << std::endl; + writer.Stream() << writer.ind() << "" << std::endl; + writer.incInd(); + + PropertyExpressionContainer::Save(writer); + ci = data.begin(); while (ci != data.end()) { ci->second->save(writer); @@ -330,10 +309,14 @@ void PropertySheet::Restore(Base::XMLReader &reader) reader.readElement("Cells"); Cnt = reader.getAttributeAsInteger("Count"); + + if(reader.hasAttribute("xlink") && reader.getAttributeAsInteger("xlink")) + PropertyExpressionContainer::Restore(reader); + for (int i = 0; i < Cnt; i++) { reader.readElement("Cell"); - const char* strAddress = reader.hasAttribute("address") ? reader.getAttribute("address") : 0; + const char* strAddress = reader.hasAttribute("address") ? reader.getAttribute("address") : ""; try { CellAddress address(strAddress); @@ -353,6 +336,96 @@ void PropertySheet::Restore(Base::XMLReader &reader) } } reader.readEndElement("Cells"); + signaller.tryInvoke(); +} + +void PropertySheet::copyCells(Base::Writer &writer, const std::vector &ranges) const { + writer.Stream() << "" << std::endl; + writer.Stream() << "" << std::endl; + writer.incInd(); + for(auto range : ranges) { + auto r = range; + int count = 0; + do { + if(getValue(*r)) + ++count; + }while(r.next()); + writer.Stream() << writer.ind() << "" << std::endl; + writer.incInd(); + do { + auto cell = getValue(*range); + if(cell) + cell->save(writer); + }while(range.next()); + writer.decInd(); + writer.Stream() << writer.ind() << "" << std::endl; + } + writer.decInd(); + writer.Stream() << "" << std::endl; +} + +void PropertySheet::pasteCells(XMLReader &reader, const CellAddress &addr) { + AtomicPropertyChange signaller(*this); + + bool first = true; + int roffset=0,coffset=0; + + reader.readElement("Cells"); + int rangeCount = reader.getAttributeAsInteger("count"); + + for(;rangeCount;--rangeCount) { + reader.readElement("Range"); + CellAddress from(reader.getAttribute("from")); + CellAddress to(reader.getAttribute("to")); + int cellCount = reader.getAttributeAsInteger("count"); + Range range(from,to); + bool hasCells = !!cellCount; + for(;cellCount;--cellCount) { + reader.readElement("Cell"); + CellAddress src(reader.getAttribute("address")); + if(first) { + first = false; + roffset = addr.row() - from.row(); + coffset = addr.col() - from.col(); + }else + range.next(); + while(src!=*range) { + CellAddress dst(*range); + dst.setRow(dst.row()+roffset); + dst.setCol(dst.col()+coffset); + owner->clear(dst); + owner->cellUpdated(dst); + range.next(); + } + CellAddress dst(src.row()+roffset, src.col()+coffset); + auto cell = owner->getNewCell(dst); + cell->setSpans(-1,-1); + cell->restore(reader,true); + int rows, cols; + if (cell->getSpans(rows, cols) && (rows > 1 || cols > 1)) + mergeCells(dst, CellAddress(dst.row() + rows - 1, dst.col() + cols - 1)); + + if(roffset || coffset) { + OffsetCellsExpressionVisitor visitor(*this, roffset, coffset); + cell->visit(visitor); + if(visitor.changed()) + recomputeDependencies(dst); + } + dirty.insert(dst); + owner->cellUpdated(dst); + } + if(!hasCells || range.next()) { + do { + CellAddress dst(*range); + dst.setRow(dst.row()+roffset); + dst.setCol(dst.col()+coffset); + owner->clear(dst); + owner->cellUpdated(dst); + }while(range.next()); + } + } + signaller.tryInvoke(); } Cell * PropertySheet::cellAt(CellAddress address) @@ -469,9 +542,7 @@ void PropertySheet::setAlias(CellAddress address, const std::string &alias) assert(cell != 0); /* Mark cells depending on this cell dirty; they need to be resolved when an alias changes or disappears */ - const char * docName = owner->getDocument()->Label.getValue(); - const char * docObjName = owner->getNameInDocument(); - std::string fullName = std::string(docName) + "#" + std::string(docObjName) + "." + address.toString(); + std::string fullName = owner->getFullName() + "." + address.toString(); std::map >::const_iterator j = propertyNameToCellMap.find(fullName); if (j != propertyNameToCellMap.end()) { @@ -493,7 +564,10 @@ void PropertySheet::setAlias(CellAddress address, const std::string &alias) if (oldAlias.size() > 0 && alias.size() > 0) { std::map m; - m[App::ObjectIdentifier(owner, oldAlias)] = App::ObjectIdentifier(owner, alias); + App::ObjectIdentifier key(owner, oldAlias); + App::ObjectIdentifier value(owner, alias); + + m[key] = value; owner->getDocument()->renameObjectIdentifiers(m); } @@ -540,8 +614,7 @@ void PropertySheet::clear(CellAddress address) // Erase from internal struct data.erase(i); - - rebuildDocDepList(); + signaller.tryInvoke(); } void PropertySheet::moveCell(CellAddress currPos, CellAddress newPos, std::map & renames) @@ -563,7 +636,13 @@ void PropertySheet::moveCell(CellAddress currPos, CellAddress newPos, std::mapgetAlias(alias)) { + owner->aliasRemoved(currPos, alias); + cell->setAlias(""); + } + // Remove from old removeDependencies(currPos); data.erase(currPos); @@ -582,94 +661,17 @@ void PropertySheet::moveCell(CellAddress currPos, CellAddress newPos, std::mapsetSpans(-1, -1); addDependencies(newPos); + + if(alias.size()) + cell->setAlias(alias); + setDirty(newPos); renames[ObjectIdentifier(owner, currPos.toString())] = ObjectIdentifier(owner, newPos.toString()); - - rebuildDocDepList(); } + signaller.tryInvoke(); } -/** - * @brief The RewriteExpressionVisitor class - * - * A class that visits each node of an expressions, and possibly - * rewrites variables. This is a helper class to rewrite expressions - * when rows or columns are either inserted or removed, to make - * sure that formulas referencing cells being moved to a new locations - * will still be valid, i.e rewritten. - * - */ - -class RewriteExpressionVisitor : public ExpressionVisitor { -public: - RewriteExpressionVisitor(CellAddress address, int rowCount, int colCount) - : mRow(address.row()) - , mCol(address.col()) - , mRowCount(rowCount) - , mColCount(colCount) - , mChanged(false) { } - ~RewriteExpressionVisitor() { } - - void reset() { mChanged = false; } - - bool changed() const { return mChanged; } - - void visit(Expression * node) { - VariableExpression *varExpr = freecad_dynamic_cast(node); - RangeExpression *rangeExpr = freecad_dynamic_cast(node); - - - if (varExpr) { - static const boost::regex e("\\${0,1}([A-Z]{1,2})\\${0,1}([0-9]{1,5})"); - boost::cmatch cm; - std::string s = varExpr->name(); - - if (boost::regex_match(s.c_str(), cm, e)) { - const boost::sub_match colstr = cm[1]; - const boost::sub_match rowstr = cm[2]; - int thisRow, thisCol; - - try { - thisCol = decodeColumn(colstr.str()); - thisRow = decodeRow(rowstr.str()); - - if (thisRow >= mRow || thisCol >= mCol) { - thisRow += mRowCount; - thisCol += mColCount; - varExpr->setPath(ObjectIdentifier(varExpr->getOwner(), columnName(thisCol) + rowName(thisRow))); - mChanged = true; - } - } - catch (const Base::IndexError &) { - /* Ignore this error here */ - } - } - } - else if (rangeExpr) { - Range r = rangeExpr->getRange(); - CellAddress from(r.from()); - CellAddress to(r.to()); - - if (from.row() >= mRow || from.col() >= mCol) { - from = CellAddress(std::max(0, from.row() + mRowCount), std::max(0, from.col() + mColCount)); - mChanged = true; - } - if (to.row() >= mRow || to.col() >= mCol) { - to = CellAddress(std::max(0, to.row() + mRowCount), std::max(0, to.col() + mColCount)); - mChanged = true; - } - rangeExpr->setRange(Range(from, to)); - } - } -private: - int mRow; - int mCol; - int mRowCount; - int mColCount; - bool mChanged; -}; - void PropertySheet::insertRows(int row, int count) { std::vector keys; @@ -681,7 +683,8 @@ void PropertySheet::insertRows(int row, int count) /* Sort them */ std::sort(keys.begin(), keys.end(), boost::bind(&PropertySheet::rowSortFunc, this, _1, _2)); - RewriteExpressionVisitor visitor(CellAddress(row, CellAddress::MAX_COLUMNS), count, 0); + MoveCellsExpressionVisitor visitor(*this, + CellAddress(row, CellAddress::MAX_COLUMNS), count, 0); AtomicPropertyChange signaller(*this); for (std::vector::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) { @@ -705,6 +708,7 @@ void PropertySheet::insertRows(int row, int count) const App::DocumentObject * docObj = static_cast(getContainer()); owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject * obj) { return obj != docObj; }); + signaller.tryInvoke(); } /** @@ -730,7 +734,8 @@ void PropertySheet::removeRows(int row, int count) /* Sort them */ std::sort(keys.begin(), keys.end(), boost::bind(&PropertySheet::rowSortFunc, this, _1, _2)); - RewriteExpressionVisitor visitor(CellAddress(row + count - 1, CellAddress::MAX_COLUMNS), -count, 0); + MoveCellsExpressionVisitor visitor(*this, + CellAddress(row + count - 1, CellAddress::MAX_COLUMNS), -count, 0); AtomicPropertyChange signaller(*this); for (std::vector::const_iterator i = keys.begin(); i != keys.end(); ++i) { @@ -756,6 +761,7 @@ void PropertySheet::removeRows(int row, int count) const App::DocumentObject * docObj = static_cast(getContainer()); owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject * obj) { return obj != docObj; }); + signaller.tryInvoke(); } void PropertySheet::insertColumns(int col, int count) @@ -769,7 +775,8 @@ void PropertySheet::insertColumns(int col, int count) /* Sort them */ std::sort(keys.begin(), keys.end()); - RewriteExpressionVisitor visitor(CellAddress(CellAddress::MAX_ROWS, col), 0, count); + MoveCellsExpressionVisitor visitor(*this, + CellAddress(CellAddress::MAX_ROWS, col), 0, count); AtomicPropertyChange signaller(*this); for (std::vector::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) { @@ -793,6 +800,7 @@ void PropertySheet::insertColumns(int col, int count) const App::DocumentObject * docObj = static_cast(getContainer()); owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject * obj) { return obj != docObj; }); + signaller.tryInvoke(); } /** @@ -818,7 +826,8 @@ void PropertySheet::removeColumns(int col, int count) /* Sort them */ std::sort(keys.begin(), keys.end(), boost::bind(&PropertySheet::colSortFunc, this, _1, _2)); - RewriteExpressionVisitor visitor(CellAddress(CellAddress::MAX_ROWS, col + count - 1), 0, -count); + MoveCellsExpressionVisitor visitor(*this, + CellAddress(CellAddress::MAX_ROWS, col + count - 1), 0, -count); AtomicPropertyChange signaller(*this); for (std::vector::const_iterator i = keys.begin(); i != keys.end(); ++i) { @@ -844,6 +853,7 @@ void PropertySheet::removeColumns(int col, int count) const App::DocumentObject * docObj = static_cast(getContainer()); owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject * obj) { return obj != docObj; } ); + signaller.tryInvoke(); } unsigned int PropertySheet::getMemSize() const @@ -878,6 +888,7 @@ bool PropertySheet::mergeCells(CellAddress from, CellAddress to) } setSpans(from, to.row() - from.row() + 1, to.col() - from.col() + 1); + signaller.tryInvoke(); return true; } @@ -901,6 +912,7 @@ void PropertySheet::splitCell(CellAddress address) } setSpans(anchor, -1, -1); + signaller.tryInvoke(); } void PropertySheet::getSpans(CellAddress address, int & rows, int & cols) const @@ -953,55 +965,41 @@ void PropertySheet::addDependencies(CellAddress key) if (expression == 0) return; - std::set expressionDeps; + for(auto &dep : expression->getDeps()) { - // Get dependencies from expression - expression->getDeps(expressionDeps); + App::DocumentObject *docObj = dep.first; + App::Document *doc = docObj->getDocument(); - std::set::const_iterator i = expressionDeps.begin(); - while (i != expressionDeps.end()) { - const Property * prop = i->getProperty(); - const App::DocumentObject * docObj = i->getDocumentObject(); - App::Document * doc = i->getDocument(); + std::string docObjName = docObj->getFullName(); - std::string docName = doc ? doc->Label.getValue() : i->getDocumentName().getString(); - std::string docObjName = docName + "#" + (docObj ? docObj->getNameInDocument() : i->getDocumentObjectName().getString()); - std::string propName = docObjName + "." + i->getPropertyName(); - - if (!prop) - cell->setResolveException("Unresolved dependency"); - else { - App::DocumentObject * docObject = freecad_dynamic_cast(prop->getContainer()); - - documentObjectName[docObject] = docObject->Label.getValue(); - documentName[docObject->getDocument()] = docObject->getDocument()->Label.getValue(); - } - - // Observe document to trach changes to the property - if (doc) - owner->observeDocument(doc); - - // Insert into maps - propertyNameToCellMap[propName].insert(key); - cellToPropertyNameMap[key].insert(propName); - - // Also an alias? - if (docObj == owner) { - std::map::const_iterator j = revAliasProp.find(i->getPropertyName()); - - if (j != revAliasProp.end()) { - propName = docObjName + "." + j->second.toString(); - - // Insert into maps - propertyNameToCellMap[propName].insert(key); - cellToPropertyNameMap[key].insert(propName); - } - } + owner->observeDocument(doc); documentObjectToCellMap[docObjName].insert(key); cellToDocumentObjectMap[key].insert(docObjName); + ++updateCount; - ++i; + for(auto &props : dep.second) { + std::string propName = docObjName + "." + props.first; + FC_LOG("dep " << key.toString() << " -> " << propName); + + // Insert into maps + propertyNameToCellMap[propName].insert(key); + cellToPropertyNameMap[key].insert(propName); + + // Also an alias? + if (docObj==owner && props.first.size()) { + std::map::const_iterator j = revAliasProp.find(props.first); + + if (j != revAliasProp.end()) { + propName = docObjName + "." + j->second.toString(); + FC_LOG("dep " << key.toString() << " -> " << propName); + + // Insert into maps + propertyNameToCellMap[propName].insert(key); + cellToPropertyNameMap[key].insert(propName); + } + } + } } } @@ -1056,180 +1054,150 @@ void PropertySheet::removeDependencies(CellAddress key) } cellToDocumentObjectMap.erase(i2); + ++updateCount; } } /** * Recompute any cells that depend on \a prop. * - * @param prop Property that presumably has changed an triggers updates of other cells. - * */ -void PropertySheet::recomputeDependants(const Property *prop) +void PropertySheet::recomputeDependants(const App::DocumentObject *owner, const char *propName) { - App::DocumentObject * owner = freecad_dynamic_cast(prop->getContainer()); - const char * name = owner->getPropertyName(prop); + // First, search without actual property name for sub-object/link + // references, i.e indirect references. The depenedecies of these + // references are too complex to track exactly, so we only track the + // top parent object instead, and mark the involved expression + // whenever the top parent changes. + std::string fullName = owner->getFullName() + "."; + auto it = propertyNameToCellMap.find(fullName); + if (it != propertyNameToCellMap.end()) { + for(auto &cell : it->second) + setDirty(cell); + } - assert(name != 0); - - if (name) { - const char * docName = owner->getDocument()->Label.getValue(); - const char * nameInDoc = owner->getNameInDocument(); - - if (nameInDoc) { - // Recompute cells that depend on this cell - std::string fullName = std::string(docName) + "#" + std::string(nameInDoc) + "." + std::string(name); - std::map >::const_iterator i = propertyNameToCellMap.find(fullName); - - if (i != propertyNameToCellMap.end()) { - std::set::const_iterator j = i->second.begin(); - std::set::const_iterator end = i->second.end(); - - while (j != end) { - setDirty(*j); - ++j; - } - } - else if (prop->isDerivedFrom(App::PropertyLists::getClassTypeId())) { - // #0003610: - // Inside propertyNameToCellMap we keep a string including the - // index operator of the property. From the given property we - // can't build 'fullName' to include this index, so we must go - // through all elements and check for a string of the form: - // 'fullName[index]' and set dirty the appropriate cells. - std::string fullNameIndex = "^"; - fullNameIndex += fullName; - fullNameIndex += "\\[[0-9]+\\]$"; - boost::regex rx(fullNameIndex); - boost::cmatch what; - for (auto i : propertyNameToCellMap) { - if (boost::regex_match(i.first.c_str(), what, rx)) { - std::set::const_iterator j = i.second.begin(); - std::set::const_iterator end = i.second.end(); - - while (j != end) { - setDirty(*j); - ++j; - } - } - } - } + if (propName) { + // Now, we check for direct property references + 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) { - const char * docName = docObj->getDocument()->Label.getValue(); - const char * docObjName = docObj->getNameInDocument(); + depConnections.erase(docObj); // Recompute cells that depend on this cell - std::string fullName = std::string(docName) + "#" + std::string(docObjName); - std::map >::const_iterator i = documentObjectToCellMap.find(fullName); - - if (i == documentObjectToCellMap.end()) + auto iter = documentObjectToCellMap.find(docObj->getFullName()); + if (iter == documentObjectToCellMap.end()) return; // Touch to force recompute touch(); - - std::set s = i->second; - std::set::const_iterator j = s.begin(); - std::set::const_iterator end = s.end(); - while (j != end) { - Cell * cell = getValue(*j); + AtomicPropertyChange signaller(*this); + for(const auto &address : iter->second) { + Cell * cell = getValue(address); cell->setResolveException("Unresolved dependency"); - setDirty((*j)); - ++j; + setDirty(address); } } +void PropertySheet::slotChangedObject(const App::DocumentObject &obj, const App::Property &prop) { + recomputeDependants(&obj, prop.getName()); +} + +void PropertySheet::onAddDep(App::DocumentObject *obj) { + depConnections[obj] = obj->signalChanged.connect(boost::bind( + &PropertySheet::slotChangedObject, this, _1, _2)); +} + +void PropertySheet::onRemoveDep(App::DocumentObject *obj) { + depConnections.erase(obj); +} + void PropertySheet::renamedDocumentObject(const App::DocumentObject * docObj) { +#if 1 + (void)docObj; +#else if (documentObjectName.find(docObj) == documentObjectName.end()) return; - // Touch to force recompute - touch(); - std::map::iterator i = data.begin(); - AtomicPropertyChange signaller(*this); - RelabelDocumentObjectExpressionVisitor v(*this, documentObjectName[docObj], docObj->Label.getValue()); - while (i != data.end()) { + RelabelDocumentObjectExpressionVisitor v(*this, docObj); i->second->visit(v); - recomputeDependencies(i->first); - setDirty(i->first); + if(v.changed()) { + v.reset(); + recomputeDependencies(i->first); + setDirty(i->first); + } ++i; } +#endif } -void PropertySheet::renamedDocument(const App::Document * doc) +void PropertySheet::onRelabeledDocument(const App::Document &doc) { - if (documentName.find(doc) == documentName.end()) - return; - // Touch to force recompute - touch(); - - std::map::iterator i = data.begin(); - - /* Resolve all cells */ - AtomicPropertyChange signaller(*this); - RelabelDocumentExpressionVisitor v(*this, documentName[doc], doc->Label.getValue()); - - while (i != data.end()) { - i->second->visit(v); - recomputeDependencies(i->first); - setDirty(i->first); - ++i; - } + RelabelDocumentExpressionVisitor v(doc); + for(auto &c : data) + c.second->visit(v); } void PropertySheet::renameObjectIdentifiers(const std::map &paths) { RenameObjectIdentifierExpressionVisitor v(*this, paths, *this); - - for (std::map::iterator it = data.begin(); it != data.end(); ++it) - it->second->visit(v); + 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) { - docDeps.erase(const_cast(docObj)); + (void)docObj; + // This function is only used in SheetObserver, which is obselete. + // + // if(docDeps.erase(const_cast(docObj))) { + // const App::DocumentObject * docObj = dynamic_cast(getContainer()); + // if(docObj && docObj->getDocument()!=docObj->getDocument()) { + // for(auto it=xlinks.begin();it!=xlinks.end();++it) { + // if(it->getValue() == docObj) { + // xlinks.erase(it); + // break; + // } + // } + // } + // } } void PropertySheet::documentSet() { - documentName[owner->getDocument()] = owner->getDocument()->Label.getValue(); -} - -void PropertySheet::recomputeDependants(const App::DocumentObject *docObj) -{ - const char * docName = docObj->getDocument()->Label.getValue(); - const char * docObjName = docObj->getNameInDocument(); - - - // Recompute cells that depend on this cell - std::string fullName = std::string(docName) + "#" + std::string(docObjName); - std::map >::const_iterator i = documentObjectToCellMap.find(fullName); - - if (i == documentObjectToCellMap.end()) - return; - - // Touch to force recompute - touch(); - - std::set::const_iterator j = i->second.begin(); - std::set::const_iterator end = i->second.end(); - - while (j != end) { - setDirty((*j)); - ++j; - } } const std::set &PropertySheet::getDeps(const std::string &name) const @@ -1260,23 +1228,39 @@ void PropertySheet::recomputeDependencies(CellAddress key) removeDependencies(key); addDependencies(key); - rebuildDocDepList(); + signaller.tryInvoke(); } -void PropertySheet::rebuildDocDepList() +void PropertySheet::hasSetValue() { - AtomicPropertyChange signaller(*this); - - docDeps.clear(); - BuildDocDepsExpressionVisitor v(*this, docDeps); - - std::map::iterator i = data.begin(); - - /* Resolve all cells */ - while (i != data.end()) { - i->second->visit(v); - ++i; + if(!updateCount || + !owner || !owner->getNameInDocument() || owner->isRestoring() || + this!=&owner->cells || + testFlag(LinkDetached)) + { + PropertyExpressionContainer::hasSetValue(); + return; } + + updateCount = 0; + + std::set deps; + std::vector labels; + unregisterElementReference(); + UpdateElementReferenceExpressionVisitor 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() @@ -1288,16 +1272,196 @@ PyObject *PropertySheet::getPyObject() return Py::new_reference_to(PythonObject); } -void PropertySheet::resolveAll() -{ - std::map::iterator i = data.begin(); - - /* Resolve all cells */ - AtomicPropertyChange signaller(*this); - while (i != data.end()) { - recomputeDependencies(i->first); - setDirty(i->first); - ++i; - } - touch(); +void PropertySheet::setPyObject(PyObject *obj) { + if(!obj || !PyObject_TypeCheck(obj, &PropertySheetPy::Type)) + throw Base::TypeError("Invalid type"); + if(obj != PythonObject.ptr()) + Paste(*static_cast(obj)->getPropertySheetPtr()); +} + +void PropertySheet::afterRestore() +{ + Base::FlagToggler 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 flag(restoring); + unregisterElementReference(); + UpdateElementReferenceExpressionVisitor v(*this); + for(auto &d : data) { + auto expr = d.second->expression.get(); + if(expr) + expr->visit(v); + } +} + +bool PropertySheet::adjustLink(const std::set &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 docObj : expr->getDepObjects()) { + if (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 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(getContainer()); + if(owner) + owner->onUpdateElementReference(this); + } +} + +bool PropertySheet::referenceChanged() const { + return false; +} + +Property *PropertySheet::CopyOnImportExternal( + const std::map &nameMap) const +{ + std::map > 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 0; + std::unique_ptr 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 > 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 0; + std::unique_ptr 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 > 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 0; + std::unique_ptr copy(new PropertySheet(*this)); + for(auto &change : changed) + copy->data[change.first]->setExpression(std::move(change.second)); + return copy.release(); +} + +std::map PropertySheet::getExpressions() const { + std::map 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 &&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(); } diff --git a/src/Mod/Spreadsheet/App/PropertySheet.h b/src/Mod/Spreadsheet/App/PropertySheet.h index e91795bb93..18c84e7ac7 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.h +++ b/src/Mod/Spreadsheet/App/PropertySheet.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include "Cell.h" @@ -37,7 +37,8 @@ class Sheet; class PropertySheet; class SheetObserver; -class PropertySheet : public App::Property, private App::AtomicPropertyChangeInterface { +class SpreadsheetExport PropertySheet : public App::PropertyExpressionContainer + , private App::AtomicPropertyChangeInterface { TYPESYSTEM_HEADER(); public: @@ -45,6 +46,24 @@ public: ~PropertySheet(); + virtual std::map getExpressions() const override; + virtual void setExpressions(std::map &&exprs) override; + virtual void onRelabeledDocument(const App::Document &doc) override; + + virtual void updateElementReference( + App::DocumentObject *feature,bool reverse=false,bool notify=false) override; + virtual bool referenceChanged() const override; + virtual bool adjustLink(const std::set &inList) override; + virtual Property *CopyOnImportExternal(const std::map &nameMap) const override; + virtual Property *CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const override; + virtual Property *CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const override; + virtual void breakLink(App::DocumentObject *obj, bool clear) override; + + virtual void afterRestore() override; + virtual void onContainerRestored() override; + virtual Property *Copy(void) const; virtual void Paste(const Property &from); @@ -53,6 +72,10 @@ public: virtual void Restore(Base::XMLReader & reader); + void copyCells(Base::Writer &writer, const std::vector &ranges) const; + + void pasteCells(Base::XMLReader &reader, const App::CellAddress &addr); + Cell *createCell(App::CellAddress address); void setValue() { } @@ -95,6 +118,8 @@ public: void setDirty(App::CellAddress address); + void setDirty(); + void clearDirty(App::CellAddress key) { dirty.erase(key); } void clearDirty() { dirty.clear(); purgeTouched(); } @@ -103,6 +128,8 @@ public: void moveCell(App::CellAddress currPos, App::CellAddress newPos, std::map &renames); + void pasteCells(const std::map &cells, int rowOffset, int colOffset); + void insertRows(int row, int count); void removeRows(int row, int count); @@ -127,26 +154,31 @@ public: const std::set &getDeps(App::CellAddress pos) const; - const std::set & getDocDeps() const { return docDeps; } - void recomputeDependencies(App::CellAddress key); PyObject *getPyObject(void); - - void resolveAll(); + void setPyObject(PyObject *); void invalidateDependants(const App::DocumentObject *docObj); void renamedDocumentObject(const App::DocumentObject *docObj); - - void renamedDocument(const App::Document *doc); - void renameObjectIdentifiers(const std::map &paths); void deletedDocumentObject(const App::DocumentObject *docObj); void documentSet(); + std::string getRow(int offset=0) const; + + std::string getColumn(int offset=0) const; + +protected: + virtual void hasSetValue() override; + virtual void hasSetChildValue(App::Property &prop) override; + virtual void onBreakLink(App::DocumentObject *obj) override; + virtual void onAddDep(App::DocumentObject *obj) override; + virtual void onRemoveDep(App::DocumentObject *obj) override; + private: PropertySheet(const PropertySheet & other); @@ -191,11 +223,8 @@ private: void removeDependencies(App::CellAddress key); - void recomputeDependants(const App::Property * prop); - - void recomputeDependants(const App::DocumentObject * docObj); - - void rebuildDocDepList(); + void slotChangedObject(const App::DocumentObject &obj, const App::Property &prop); + void recomputeDependants(const App::DocumentObject *obj, const char *propName); /*! Cell dependencies, i.e when a change occurs to property given in key, the set of addresses needs to be recomputed. @@ -213,15 +242,6 @@ private: /*! DocumentObject this cell depends on */ std::map > cellToDocumentObjectMap; - /*! Other document objects the sheet depends on */ - std::set docDeps; - - /*! Name of document objects, used for renaming */ - std::map documentObjectName; - - /*! Name of documents, used for renaming */ - std::map documentName; - /*! Mapping of cell position to alias property */ std::map aliasProp; @@ -230,6 +250,11 @@ private: /*! The associated python object */ Py::Object PythonObject; + + std::map depConnections; + + int updateCount; + bool restoring = false; }; } diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index 5ff7720b05..d7deb552c5 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ #endif +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include "Sheet.h" #include "SheetObserver.h" #include "Utils.h" @@ -53,6 +55,8 @@ #include #include +FC_LOG_LEVEL_INIT("Spreadsheet",true,true); + using namespace Base; using namespace App; using namespace Spreadsheet; @@ -78,19 +82,13 @@ typedef Traits::edge_descriptor Edge; Sheet::Sheet() : DocumentObject() - , props(this) + , props(PropertyContainer::dynamicProps) , cells(this) { - ADD_PROPERTY_TYPE(docDeps, (0), "Spreadsheet", (PropertyType)(Prop_Transient|Prop_ReadOnly|Prop_Hidden), "Dependencies"); - ADD_PROPERTY_TYPE(cells, (), "Spreadsheet", (PropertyType)(Prop_ReadOnly|Prop_Hidden), "Cell contents"); - ADD_PROPERTY_TYPE(columnWidths, (), "Spreadsheet", (PropertyType)(Prop_ReadOnly|Prop_Hidden), "Column widths"); + 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"); - - docDeps.setSize(0); - docDeps.setScope(LinkScope::Global); - - onRenamedDocumentConnection = GetApplication().signalRenameDocument.connect(boost::bind(&Spreadsheet::Sheet::onRenamedDocument, this, _1)); - onRelabledDocumentConnection = GetApplication().signalRelabelDocument.connect(boost::bind(&Spreadsheet::Sheet::onRelabledDocument, this, _1)); } /** @@ -123,7 +121,6 @@ void Sheet::clearAll() columnWidths.clear(); rowHeights.clear(); removedAliases.clear(); - docDeps.setValues(std::vector()); for (ObserverMap::iterator i = observers.begin(); i != observers.end(); ++i) delete i->second; @@ -175,12 +172,14 @@ bool Sheet::importFromFile(const std::string &filename, char delimiter, char quo } } catch (...) { + signaller.tryInvoke(); return false; } ++row; } file.close(); + signaller.tryInvoke(); return true; } else @@ -365,16 +364,7 @@ void Sheet::setCell(CellAddress address, const char * value) return; } - // Update expression, delete old first if necessary - Cell * cell = getNewCell(address); - - if (cell->getExpression()) { - setContent(address, 0); - } setContent(address, value); - - // Recompute dependencies - touch(); } /** @@ -421,14 +411,15 @@ Property * Sheet::getProperty(const char * addr) const * */ -void Sheet::getCellAddress(const Property *prop, CellAddress & address) +bool Sheet::getCellAddress(const Property *prop, CellAddress & address) { std::map::const_iterator i = propAddress.find(prop); - if (i != propAddress.end()) + if (i != propAddress.end()) { address = i->second; - else - throw Base::TypeError("Property is not a cell"); + return true; + } + return false; } /** @@ -488,7 +479,7 @@ void Sheet::onSettingDocument() Property * Sheet::setFloatProperty(CellAddress key, double value) { - Property * prop = props.getPropertyByName(key.toString().c_str()); + Property * prop = props.getDynamicPropertyByName(key.toString().c_str()); PropertyFloat * floatProp; if (!prop || prop->getTypeId() != PropertyFloat::getClassTypeId()) { @@ -496,7 +487,7 @@ Property * Sheet::setFloatProperty(CellAddress key, double value) this->removeDynamicProperty(key.toString().c_str()); propAddress.erase(prop); } - floatProp = freecad_dynamic_cast(props.addDynamicProperty("App::PropertyFloat", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_Transient)); + floatProp = freecad_dynamic_cast(addDynamicProperty("App::PropertyFloat", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_NoPersist)); } else floatProp = static_cast(prop); @@ -519,7 +510,7 @@ Property * Sheet::setFloatProperty(CellAddress key, double value) Property * Sheet::setQuantityProperty(CellAddress key, double value, const Base::Unit & unit) { - Property * prop = props.getPropertyByName(key.toString().c_str()); + Property * prop = props.getDynamicPropertyByName(key.toString().c_str()); PropertySpreadsheetQuantity * quantityProp; if (!prop || prop->getTypeId() != PropertySpreadsheetQuantity::getClassTypeId()) { @@ -527,7 +518,7 @@ Property * Sheet::setQuantityProperty(CellAddress key, double value, const Base: this->removeDynamicProperty(key.toString().c_str()); propAddress.erase(prop); } - Property * p = props.addDynamicProperty("Spreadsheet::PropertySpreadsheetQuantity", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_Transient); + Property * p = addDynamicProperty("Spreadsheet::PropertySpreadsheetQuantity", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_NoPersist); quantityProp = freecad_dynamic_cast(p); } else @@ -553,7 +544,7 @@ Property * Sheet::setQuantityProperty(CellAddress key, double value, const Base: Property * Sheet::setStringProperty(CellAddress key, const std::string & value) { - Property * prop = props.getPropertyByName(key.toString().c_str()); + Property * prop = props.getDynamicPropertyByName(key.toString().c_str()); PropertyString * stringProp = freecad_dynamic_cast(prop); if (!stringProp) { @@ -561,7 +552,7 @@ Property * Sheet::setStringProperty(CellAddress key, const std::string & value) this->removeDynamicProperty(key.toString().c_str()); propAddress.erase(prop); } - stringProp = freecad_dynamic_cast(props.addDynamicProperty("App::PropertyString", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_Transient)); + stringProp = freecad_dynamic_cast(addDynamicProperty("App::PropertyString", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Hidden | Prop_NoPersist)); } propAddress[stringProp] = key; @@ -597,13 +588,31 @@ void Sheet::updateAlias(CellAddress key) } } - if (!aliasProp) - aliasProp = props.addDynamicProperty(prop->getTypeId().getName(), alias.c_str(), 0, 0, Prop_ReadOnly | Prop_Transient); + if (!aliasProp) { + aliasProp = addDynamicProperty(prop->getTypeId().getName(), alias.c_str(), 0, 0, Prop_ReadOnly | Prop_NoPersist); + aliasProp->setStatus(App::Property::Hidden,true); + } aliasProp->Paste(*prop); } } +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; +}; + /** * Update the Property given by \a key. This will also eventually trigger recomputations of cells depending on \a key. * @@ -616,11 +625,12 @@ void Sheet::updateProperty(CellAddress key) Cell * cell = getCell(key); if (cell != 0) { - Expression * output; + std::unique_ptr output; const Expression * input = cell->getExpression(); if (input) { - output = input->eval(); + CurrentAddressLock lock(currentRow,currentCol,key); + output = cells.eval(input); } else { std::string s; @@ -641,8 +651,6 @@ void Sheet::updateProperty(CellAddress key) } else setStringProperty(key, freecad_dynamic_cast(output)->getText().c_str()); - - delete output; } else clear(key); @@ -661,6 +669,12 @@ void Sheet::updateProperty(CellAddress key) Property *Sheet::getPropertyByName(const char* name) const { + std::string _name; + CellAddress addr; + if(addr.parseAbsoluteAddress(name)) { + _name = addr.toString(true); + name = _name.c_str(); + } Property * prop = getProperty(name); if (prop) @@ -669,20 +683,10 @@ Property *Sheet::getPropertyByName(const char* name) const return DocumentObject::getPropertyByName(name); } -/** - * @brief Get name of a property, given a pointer to it. - * @param prop Pointer to property. - * @return Pointer to string. - */ - -const char *Sheet::getPropertyName(const Property *prop) const -{ - const char * name = props.getPropertyName(prop); - - if (name) - return name; - else - return PropertyContainer::getPropertyName(prop); +void Sheet::touchCells(Range range) { + do { + cells.setDirty(*range); + }while(range.next()); } /** @@ -693,18 +697,20 @@ const char *Sheet::getPropertyName(const Property *prop) const void Sheet::recomputeCell(CellAddress p) { Cell * cell = cells.getValue(p); - std::string docName = getDocument()->Label.getValue(); - std::string docObjName = std::string(getNameInDocument()); - std::string name = docName + "#" + docObjName + "." + p.toString(); try { - if (cell) { - cell->clearException(); - cell->clearResolveException(); + if (cell && cell->hasException()) { + std::string content; + cell->getStringContent(content); + cell->setContent(content.c_str()); } + updateProperty(p); - cells.clearDirty(p); - cellErrors.erase(p); + + if(!cell || !cell->hasException()) { + cells.clearDirty(p); + cellErrors.erase(p); + } } catch (const Base::Exception & e) { QString msg = QString::fromUtf8("ERR: %1").arg(QString::fromUtf8(e.what())); @@ -712,9 +718,15 @@ void Sheet::recomputeCell(CellAddress p) setStringProperty(p, Base::Tools::toStdString(msg)); if (cell) cell->setException(e.what()); + else + e.ReportException(); // Mark as erroneous cellErrors.insert(p); + cellUpdated(p); + + if(e.isDerivedFrom(Base::AbortException::getClassTypeId())) + throw; } updateAlias(p); @@ -742,78 +754,117 @@ DocumentObjectExecReturn *Sheet::execute(void) dirtyCells.insert(*i); } - // Push dirty cells onto queue - for (std::set::const_iterator i = dirtyCells.begin(); i != dirtyCells.end(); ++i) { - // Create queue and a graph structure to compute order of evaluation - std::deque workQueue; - DependencyList graph; - std::map VertexList; - std::map VertexIndexList; + DependencyList graph; + std::map VertexList; + std::map VertexIndexList; + std::deque workQueue(dirtyCells.begin(),dirtyCells.end()); + while(workQueue.size()) { + CellAddress currPos = workQueue.front(); + workQueue.pop_front(); - workQueue.push_back(*i); + // Insert into map of CellPos -> Index, if it doesn't exist already + auto res = VertexList.emplace(currPos,Vertex()); + if(res.second) { + res.first->second = add_vertex(graph); + VertexIndexList[res.first->second] = currPos; + } - while (workQueue.size() > 0) { - CellAddress currPos = workQueue.front(); - std::set s; - - // Get other cells that depends on the current cell (currPos) - providesTo(currPos, s); - workQueue.pop_front(); - - // Insert into map of CellPos -> Index, if it doesn't exist already - if (VertexList.find(currPos) == VertexList.end()) { - VertexList[currPos] = add_vertex(graph); - VertexIndexList[VertexList[currPos]] = currPos; + // Process cells that depend on the current cell + 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 to graph to signal dependency + add_edge(res.first->second, resDep.first->second, graph); + } + } + // Compute cells + std::list make_order; + // Sort graph topologically to find evaluation order + try { + boost::topological_sort(graph, std::front_inserter(make_order)); + // Recompute cells + FC_LOG("recomputing " << getFullName()); + for(auto &pos : make_order) { + const auto &addr = VertexIndexList[pos]; + FC_LOG(addr.toString()); + recomputeCell(addr); + } + } catch (std::exception&) { + for(auto &v : VertexList) { + Cell * cell = cells.getValue(v.first); + // Mark as erroneous + cellErrors.insert(v.first); + if (cell) + cell->setException("Pending computation due to cyclic dependency"); + updateProperty(v.first); + updateAlias(v.first); + } + + // Try to be more user friendly by finding individual loops + while(dirtyCells.size()) { + + std::deque workQueue; + DependencyList graph; + std::map VertexList; + std::map VertexIndexList; + + CellAddress currentAddr = *dirtyCells.begin(); + workQueue.push_back(currentAddr); + dirtyCells.erase(dirtyCells.begin()); + + while (workQueue.size() > 0) { + CellAddress currPos = workQueue.front(); + workQueue.pop_front(); - // Process cells that depend on the current cell - std::set::const_iterator i = s.begin(); - while (i != s.end()) { // Insert into map of CellPos -> Index, if it doesn't exist already - if (VertexList.find(*i) == VertexList.end()) { - VertexList[*i] = add_vertex(graph); - VertexIndexList[VertexList[*i]] = *i; - workQueue.push_back(*i); + auto res = VertexList.emplace(currPos,Vertex()); + if(res.second) { + res.first->second = add_vertex(graph); + VertexIndexList[res.first->second] = currPos; + } + + // Process cells that depend on the current cell + 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 to graph to signal dependency + add_edge(res.first->second, resDep.first->second, graph); + } + } + + std::list make_order; + try { + boost::topological_sort(graph, std::front_inserter(make_order)); + } catch (std::exception&) { + // Cycle detected; flag all with errors + std::ostringstream ss; + ss << "Cyclic dependency" << std::endl; + int count = 0; + for(auto &v : VertexList) { + if(count==20) + 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()); } - // Add edge to graph to signal dependency - add_edge(VertexList[currPos], VertexList[*i], graph); - ++i; } } - - // Compute cells - std::list make_order; - - // Sort graph topologically to find evaluation order - try { - boost::topological_sort(graph, std::front_inserter(make_order)); - - // Recompute cells - std::list::const_iterator i = make_order.begin(); - while (i != make_order.end()) { - recomputeCell(VertexIndexList[*i]); - ++i; - } - } - catch (std::exception&) { - // Cycle detected; flag all with errors - - std::map::const_iterator i = VertexList.begin(); - while (i != VertexList.end()) { - Cell * cell = cells.getValue(i->first); - - // Mark as erroneous - cellErrors.insert(i->first); - - if (cell) - cell->setException("Circular dependency."); - updateProperty(i->first); - updateAlias(i->first); - - ++i; - } - } - } // Signal update of column widths @@ -832,16 +883,6 @@ DocumentObjectExecReturn *Sheet::execute(void) rowHeights.clearDirty(); columnWidths.clearDirty(); - std::set ds(cells.getDocDeps()); - - // Make sure we don't reference ourselves - ds.erase(this); - - std::vector dv(ds.begin(), ds.end()); - docDeps.setValues(dv); - - purgeTouched(); - if (cellErrors.size() == 0) return DocumentObject::StdReturn; else @@ -855,12 +896,9 @@ DocumentObjectExecReturn *Sheet::execute(void) short Sheet::mustExecute(void) const { - if (cellErrors.size() > 0 || cells.isTouched() || columnWidths.isTouched() || rowHeights.isTouched()) + if (cellErrors.size() > 0 || cells.isDirty()) return 1; - else if (cells.getDocDeps().size() == 0) - return 0; - else - return -1; + return DocumentObject::mustExecute(); } @@ -888,15 +926,6 @@ void Sheet::clear(CellAddress address, bool /*all*/) cells.clear(address); - // Update dependencies - std::set ds(cells.getDocDeps()); - - // Make sure we don't reference ourselves - ds.erase(this); - - std::vector dv(ds.begin(), ds.end()); - docDeps.setValues(dv); - propAddress.erase(prop); this->removeDynamicProperty(addr.c_str()); } @@ -993,6 +1022,48 @@ std::vector Sheet::getUsedCells() const return usedCells; } +void Sheet::updateColumnsOrRows(bool horizontal, int section, int count) +{ + auto &hiddenProp = horizontal?hiddenColumns:hiddenRows; + const auto &hidden = hiddenProp.getValues(); + auto it = hidden.lower_bound(section); + if(it!=hidden.end()) { + std::set newHidden(hidden.begin(),it); + if(count>0) { + for(;it!=hidden.end();++it) + newHidden.insert(*it + count); + } else { + it = hidden.lower_bound(section-count); + if(it!=hidden.end()) { + for(;it!=hidden.end();++it) + newHidden.insert(*it+count); + } + } + hiddenProp.setValues(newHidden); + } + + const auto &sizes = horizontal?columnWidths.getValues():rowHeights.getValues(); + auto iter = sizes.lower_bound(section); + if(iter!=sizes.end()) { + std::map 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); + } + } +} + /** * Insert \a count columns at before column \a col in the spreadsheet. * @@ -1003,8 +1074,8 @@ std::vector Sheet::getUsedCells() const void Sheet::insertColumns(int col, int count) { - cells.insertColumns(col, count); + updateColumnsOrRows(true,col,count); } /** @@ -1018,6 +1089,7 @@ void Sheet::insertColumns(int col, int count) void Sheet::removeColumns(int col, int count) { cells.removeColumns(col, count); + updateColumnsOrRows(true,col,-count); } /** @@ -1031,6 +1103,7 @@ void Sheet::removeColumns(int col, int count) void Sheet::insertRows(int row, int count) { cells.insertRows(row, count); + updateColumnsOrRows(false,row,count); } /** @@ -1044,6 +1117,7 @@ void Sheet::insertRows(int row, int count) void Sheet::removeRows(int row, int count) { cells.removeRows(row, count); + updateColumnsOrRows(false,row,-count); } /** @@ -1202,17 +1276,6 @@ void Sheet::setSpans(CellAddress address, int rows, int columns) cells.setSpans(address, rows, columns); } -/** - * @brief Called when a document object is renamed. - * @param docObj Renamed document object. - */ - -void Sheet::renamedDocumentObject(const DocumentObject * docObj) -{ - cells.renamedDocumentObject(docObj); - cells.touch(); -} - /** * @brief Called when alias \a alias at \a address is removed. * @param address Address of alias. @@ -1243,13 +1306,11 @@ std::set Sheet::dependsOn(CellAddress address) const void Sheet::providesTo(CellAddress address, std::set & result) const { - const char * docName = getDocument()->Label.getValue(); - const char * docObjName = getNameInDocument(); - std::string fullName = std::string(docName) + "#" + std::string(docObjName) + "." + address.toString(); - std::set tmpResult = cells.getDeps(fullName); + std::string fullName = getFullName() + "."; + std::set tmpResult = cells.getDeps(fullName + address.toString()); for (std::set::const_iterator i = tmpResult.begin(); i != tmpResult.end(); ++i) - result.insert(std::string(docName) + "#" + std::string(docObjName) + "." + i->toString()); + result.insert(fullName + i->toString()); } /** @@ -1258,38 +1319,18 @@ void Sheet::providesTo(CellAddress address, std::set & result) cons * @param result Set of links. */ -void Sheet::providesTo(CellAddress address, std::set & result) const +std::set Sheet::providesTo(CellAddress address) const { - const char * docName = getDocument()->Label.getValue(); - const char * docObjName = getNameInDocument(); - std::string fullName = std::string(docName) + "#" + std::string(docObjName) + "." + address.toString(); - result = cells.getDeps(fullName); + return cells.getDeps(getFullName()+"."+address.toString()); } void Sheet::onDocumentRestored() { - cells.resolveAll(); - execute(); -} - -/** - * @brief Slot called when a document is relabelled. - * @param document Relabelled document. - */ - -void Sheet::onRelabledDocument(const Document &document) -{ - cells.renamedDocument(&document); - cells.purgeTouched(); -} - -/** - * @brief Unimplemented. - * @param document - */ - -void Sheet::onRenamedDocument(const Document & /*document*/) -{ + auto ret = execute(); + if(ret!=DocumentObject::StdReturn) { + FC_ERR("Failed to restore " << getFullName() << ": " << ret->Why); + delete ret; + } } /** @@ -1299,6 +1340,11 @@ void Sheet::onRenamedDocument(const Document & /*document*/) void Sheet::observeDocument(Document * document) { + // observer is no longer required as PropertySheet is now derived from + // PropertyLinkBase and will handle all the link related behavior +#if 1 + (void)document; +#else ObserverMap::const_iterator it = observers.find(document->getName()); if (it != observers.end()) { @@ -1311,6 +1357,7 @@ void Sheet::observeDocument(Document * document) observers[document->getName()] = observer; } +#endif } void Sheet::renameObjectIdentifiers(const std::map &paths) @@ -1319,6 +1366,45 @@ void Sheet::renameObjectIdentifiers(const std::mapCellAddress::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(!isRestoring() && getDocument() && !getDocument()->isPerformingTransaction()) { + if(prop == &PythonMode) + cells.setDirty(); + } + App::DocumentObject::onChanged(prop); +} + +/////////////////////////////////////////////////////////////////////////////// TYPESYSTEM_SOURCE(Spreadsheet::PropertySpreadsheetQuantity, App::PropertyQuantity); diff --git a/src/Mod/Spreadsheet/App/Sheet.h b/src/Mod/Spreadsheet/App/Sheet.h index 1f2e88ea4d..bb8de29dbd 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -154,20 +154,26 @@ public: PyObject *getPyObject(); - App::Property *getPropertyByName(const char *name) const; + PropertySheet *getCells() { return &cells; } - const char* getPropertyName(const App::Property* prop) const; + App::Property *getPropertyByName(const char *name) const; virtual short mustExecute(void) const; App::DocumentObjectExecReturn *execute(void); - void getCellAddress(const App::Property *prop, App::CellAddress &address); + bool getCellAddress(const App::Property *prop, App::CellAddress &address); std::map getColumnWidths() const; std::map getRowHeights() const; + std::string getRow(int offset=0) const; + + std::string getColumn(int offset=0) const; + + void touchCells(App::Range range); + // Signals boost::signals2::signal cellUpdated; @@ -178,70 +184,20 @@ public: boost::signals2::signal rowHeightChanged; - /** @name Access properties */ - //@{ - App::Property* addDynamicProperty( - const char* type, const char* name=0, - const char* group=0, const char* doc=0, - short attr=0, bool ro=false, bool hidden=false) { - return props.addDynamicProperty(type, name, group, doc, attr, ro, hidden); - } - virtual bool removeDynamicProperty(const char* name) { - App::DocumentObject::onAboutToRemoveProperty(name); - return props.removeDynamicProperty(name); - } - std::vector getDynamicPropertyNames() const { - return props.getDynamicPropertyNames(); - } - App::Property *getDynamicPropertyByName(const char* name) const { - return props.getDynamicPropertyByName(name); - } - virtual void addDynamicProperties(const App::PropertyContainer* cont) { - return props.addDynamicProperties(cont); - } - /// get all properties of the class (including properties of the parent) - virtual void getPropertyList(std::vector &List) const { - props.getPropertyList(List); - } - /// get all properties of the class (including parent) - void getPropertyMap(std::map &Map) const { - props.getPropertyMap(Map); - } - - short getPropertyType(const App::Property *prop) const { - return props.getPropertyType(prop); - } - - /// get the group of a property - const char* getPropertyGroup(const App::Property* prop) const { - return props.getPropertyGroup(prop); - } - - /// get the documentation of a property - const char* getPropertyDocumentation(const App::Property* prop) const { - return props.getPropertyDocumentation(prop); - } - - /// get the name of a property - virtual const char* getName(const App::Property* prop) const { - return props.getPropertyName(prop); - } - //@} - void observeDocument(App::Document *document); virtual void renameObjectIdentifiers(const std::map & paths); protected: - void providesTo(App::CellAddress address, std::set & result) const; + virtual void onChanged(const App::Property *prop); + + void updateColumnsOrRows(bool horizontal, int section, int count) ; + + std::set providesTo(App::CellAddress address) const; void onDocumentRestored(); - void onRelabledDocument(const App::Document & document); - - void onRenamedDocument(const App::Document & document); - void recomputeCell(App::CellAddress p); App::Property *getProperty(App::CellAddress key) const; @@ -258,8 +214,6 @@ protected: App::Property *setQuantityProperty(App::CellAddress key, double value, const Base::Unit &unit); - void renamedDocumentObject(const App::DocumentObject * docObj); - void aliasRemoved(App::CellAddress address, const std::string &alias); void removeAliases(); @@ -267,7 +221,7 @@ protected: virtual void onSettingDocument(); /* Properties for used cells */ - App::DynamicProperty props; + App::DynamicProperty &props; /* Mapping of properties to cell position */ std::map propAddress; @@ -289,15 +243,12 @@ protected: /* Row heights */ PropertyRowHeights rowHeights; - /* Dependencies to other documents */ - App::PropertyLinkList docDeps; - /* Document observers to track changes to external properties */ typedef std::map ObserverMap; ObserverMap observers; - boost::signals2::scoped_connection onRelabledDocumentConnection; - boost::signals2::scoped_connection onRenamedDocumentConnection; + int currentRow = -1; + int currentCol = -1; friend class SheetObserver;