diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index 4797bbaff200..4cc3f7c6318e 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 -{ - return expression; +const App::Expression *Cell::getExpression(bool withFormat) const +{ + 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 0f23c8ce734c..b7f088a48bf1 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 @@ class SpreadsheetExport Cell { ~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 @@ class SpreadsheetExport Cell { void clearDirty(); + void setDirty(); + void setResolveException(const std::string &e); void clearResolveException(); @@ -105,9 +108,12 @@ class SpreadsheetExport Cell { 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 @@ class SpreadsheetExport Cell { 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 8ed557f445fb..3c358fc64529 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; - - // Get dependencies from expression - expression->getDeps(expressionDeps); + for(auto &dep : expression->getDeps()) { - 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(); + App::DocumentObject *docObj = dep.first; + App::Document *doc = docObj->getDocument(); - 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(); + std::string docObjName = docObj->getFullName(); - if (!prop) - cell->setResolveException("Unresolved dependency"); - else { - App::DocumentObject * docObject = freecad_dynamic_cast(prop->getContainer()); + owner->observeDocument(doc); - documentObjectName[docObject] = docObject->Label.getValue(); - documentName[docObject->getDocument()] = docObject->getDocument()->Label.getValue(); - } + documentObjectToCellMap[docObjName].insert(key); + cellToDocumentObjectMap[key].insert(docObjName); + ++updateCount; - // Observe document to trach changes to the property - if (doc) - owner->observeDocument(doc); + 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); + // 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()); + // 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(); + 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); + // Insert into maps + propertyNameToCellMap[propName].insert(key); + cellToPropertyNameMap[key].insert(propName); + } } } - - documentObjectToCellMap[docObjName].insert(key); - cellToDocumentObjectMap[key].insert(docObjName); - - ++i; } } @@ -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); - - assert(name != 0); + // 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); + } - if (name) { - const char * docName = owner->getDocument()->Label.getValue(); - const char * nameInDoc = owner->getNameInDocument(); + 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); + } + } +} - 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); +void PropertySheet::breakLink(App::DocumentObject *obj, bool clear) { + AtomicPropertyChange signaller(*this,false); + PropertyExpressionContainer::breakLink(obj,clear); +} - if (i != propertyNameToCellMap.end()) { - std::set::const_iterator j = i->second.begin(); - std::set::const_iterator end = i->second.end(); +void PropertySheet::onBreakLink(App::DocumentObject *obj) { + invalidateDependants(obj); +} - 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; - } - } - } - } - } - } +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); + if(!updateCount || + !owner || !owner->getNameInDocument() || owner->isRestoring() || + this!=&owner->cells || + testFlag(LinkDetached)) + { + PropertyExpressionContainer::hasSetValue(); + return; + } - docDeps.clear(); - BuildDocDepsExpressionVisitor v(*this, docDeps); + 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)); - std::map::iterator i = data.begin(); + updateDeps(std::move(deps)); - /* Resolve all cells */ - while (i != data.end()) { - i->second->visit(v); - ++i; - } + PropertyExpressionContainer::hasSetValue(); } PyObject *PropertySheet::getPyObject() @@ -1288,16 +1272,196 @@ PyObject *PropertySheet::getPyObject() return Py::new_reference_to(PythonObject); } -void PropertySheet::resolveAll() +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() { - std::map::iterator i = data.begin(); + Base::FlagToggler flag(restoring); + AtomicPropertyChange signaller(*this); - /* Resolve all cells */ + 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); - while (i != data.end()) { - recomputeDependencies(i->first); - setDirty(i->first); - ++i; + 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)); } - touch(); + signaller.tryInvoke(); } diff --git a/src/Mod/Spreadsheet/App/PropertySheet.h b/src/Mod/Spreadsheet/App/PropertySheet.h index e91795bb93c6..18c84e7ac7e4 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 @@ class PropertySheet : public App::Property, private App::AtomicPropertyChangeInt ~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 @@ class PropertySheet : public App::Property, private App::AtomicPropertyChangeInt 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 @@ class PropertySheet : public App::Property, private App::AtomicPropertyChangeInt void setDirty(App::CellAddress address); + void setDirty(); + void clearDirty(App::CellAddress key) { dirty.erase(key); } void clearDirty() { dirty.clear(); purgeTouched(); } @@ -103,6 +128,8 @@ class PropertySheet : public App::Property, private App::AtomicPropertyChangeInt 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 @@ class PropertySheet : public App::Property, private App::AtomicPropertyChangeInt 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 @@ class PropertySheet : public App::Property, private App::AtomicPropertyChangeInt 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 @@ class PropertySheet : public App::Property, private App::AtomicPropertyChangeInt /*! 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 @@ class PropertySheet : public App::Property, private App::AtomicPropertyChangeInt /*! 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 5ff7720b05c5..d7deb552c5dc 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; - - workQueue.push_back(*i); - - 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(); + 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(); + + // 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; + } - // 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 - 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); - } - // Add edge to graph to signal dependency - add_edge(VertexList[currPos], VertexList[*i], graph); - ++i; + // 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); } - // Compute cells - std::list make_order; + // Try to be more user friendly by finding individual loops + while(dirtyCells.size()) { - // Sort graph topologically to find evaluation order - try { - boost::topological_sort(graph, std::front_inserter(make_order)); + std::deque workQueue; + DependencyList graph; + std::map VertexList; + std::map VertexIndexList; - // 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 + CellAddress currentAddr = *dirtyCells.begin(); + workQueue.push_back(currentAddr); + dirtyCells.erase(dirtyCells.begin()); - std::map::const_iterator i = VertexList.begin(); - while (i != VertexList.end()) { - Cell * cell = cells.getValue(i->first); + while (workQueue.size() > 0) { + CellAddress currPos = workQueue.front(); + workQueue.pop_front(); - // Mark as erroneous - cellErrors.insert(i->first); + // 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; + } - if (cell) - cell->setException("Circular dependency."); - updateProperty(i->first); - updateAlias(i->first); + // 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); + } + } - ++i; + 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()); + } } } - } // 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 1f2e88ea4d07..bb8de29dbd36 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -154,20 +154,26 @@ class SpreadsheetExport Sheet : public App::DocumentObject 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,69 +184,19 @@ class SpreadsheetExport Sheet : public App::DocumentObject 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 onDocumentRestored(); + void updateColumnsOrRows(bool horizontal, int section, int count) ; - void onRelabledDocument(const App::Document & document); + std::set providesTo(App::CellAddress address) const; - void onRenamedDocument(const App::Document & document); + void onDocumentRestored(); void recomputeCell(App::CellAddress p); @@ -258,8 +214,6 @@ class SpreadsheetExport Sheet : public App::DocumentObject 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 @@ class SpreadsheetExport Sheet : public App::DocumentObject 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 @@ class SpreadsheetExport Sheet : public App::DocumentObject /* 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;