From 3743008cda8caec3b55452d1cf9b5a5c79032652 Mon Sep 17 00:00:00 2001 From: Eivind Kvedalen Date: Wed, 4 Mar 2015 22:14:35 +0100 Subject: [PATCH] - Self-reference bug - Refactoring/clean-up of code - Dependency tracking of aliased cells - Various resolution errors - Rewriting of ranges when columns/rows are inserted/removed - References to aliases keep their units. --- src/Mod/Spreadsheet/App/AppSpreadsheet.cpp | 1 + src/Mod/Spreadsheet/App/Cell.cpp | 16 +- src/Mod/Spreadsheet/App/Cell.h | 2 + src/Mod/Spreadsheet/App/Expression.cpp | 19 +- src/Mod/Spreadsheet/App/Expression.h | 4 + .../Spreadsheet/App/PropertyColumnWidths.cpp | 11 + .../Spreadsheet/App/PropertyColumnWidths.h | 6 +- .../Spreadsheet/App/PropertyRowHeights.cpp | 11 + src/Mod/Spreadsheet/App/PropertyRowHeights.h | 6 +- src/Mod/Spreadsheet/App/PropertySheet.cpp | 137 +++++++++---- src/Mod/Spreadsheet/App/PropertySheet.h | 12 +- src/Mod/Spreadsheet/App/Range.cpp | 10 + src/Mod/Spreadsheet/App/Range.h | 10 +- src/Mod/Spreadsheet/App/Sheet.cpp | 193 +++++++++++------- src/Mod/Spreadsheet/App/Sheet.h | 31 ++- src/Mod/Spreadsheet/App/Utils.cpp | 52 +++-- src/Mod/Spreadsheet/App/Utils.h | 25 ++- src/Mod/Spreadsheet/Gui/Command.cpp | 2 +- src/Mod/Spreadsheet/Gui/SheetModel.cpp | 10 +- .../Gui/ViewProviderSpreadsheet.cpp | 2 +- 20 files changed, 382 insertions(+), 178 deletions(-) diff --git a/src/Mod/Spreadsheet/App/AppSpreadsheet.cpp b/src/Mod/Spreadsheet/App/AppSpreadsheet.cpp index ff6dc853ddcb..f12e36726979 100644 --- a/src/Mod/Spreadsheet/App/AppSpreadsheet.cpp +++ b/src/Mod/Spreadsheet/App/AppSpreadsheet.cpp @@ -31,6 +31,7 @@ void SpreadsheetExport initSpreadsheet() { (void) Py_InitModule("Spreadsheet", Spreadsheet_methods); /* mod name, table ptr */ Base::Console().Log("Loading Spreadsheet module... done\n"); + Spreadsheet::PropertySpreadsheetQuantity::init(); Spreadsheet::PropertyColumnWidths::init(); Spreadsheet::PropertyRowHeights::init(); Spreadsheet::PropertySheet::init(); diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index d2545684ddf8..0e9a888c5cac 100644 --- a/src/Mod/Spreadsheet/App/Cell.cpp +++ b/src/Mod/Spreadsheet/App/Cell.cpp @@ -395,8 +395,20 @@ void Cell::setAlias(const std::string &n) if (alias != n) { PropertySheet::Signaller signaller(*owner); + owner->revAliasProp.erase(alias); + alias = n; + + // Update owner + if (alias != "") { + owner->aliasProp[address] = n; + owner->revAliasProp[n] = address; + } + else + owner->aliasProp.erase(address); + setUsed(ALIAS_SET, !alias.empty()); + } } @@ -590,7 +602,7 @@ void Cell::save(Base::Writer &writer) const writer.Stream() << writer.ind() << "visit(v); } /** diff --git a/src/Mod/Spreadsheet/App/Cell.h b/src/Mod/Spreadsheet/App/Cell.h index fd82e85b0082..1ce6eeefb320 100644 --- a/src/Mod/Spreadsheet/App/Cell.h +++ b/src/Mod/Spreadsheet/App/Cell.h @@ -116,6 +116,8 @@ class SpreadsheetExport Cell { void visit(ExpressionVisitor & v); + CellAddress getAddress() const { return address; } + /* Alignment */ static const int ALIGNMENT_LEFT; static const int ALIGNMENT_HCENTER; diff --git a/src/Mod/Spreadsheet/App/Expression.cpp b/src/Mod/Spreadsheet/App/Expression.cpp index e00ea18abd41..45c47eddc488 100644 --- a/src/Mod/Spreadsheet/App/Expression.cpp +++ b/src/Mod/Spreadsheet/App/Expression.cpp @@ -176,7 +176,7 @@ std::string Path::getPythonAccessor() const const Property * prop = getProperty(); if (!prop) - throw Exception("Property not found"); + throw Exception(std::string("Property '") + getPropertyName() + std::string("' not found.")); const DocumentObject * docObj = freecad_dynamic_cast(prop->getContainer()); @@ -1309,6 +1309,16 @@ Document * Path::getDocument() const return doc; } +const DocumentObject *Path::getDocumentObject() const +{ + const App::Document * doc = getDocument(); + + if (!doc) + return 0; + + return getDocumentObject(doc, documentObjectName); +} + const Property *Path::getProperty() const { const App::Document * doc = getDocument(); @@ -1346,7 +1356,7 @@ const Property * VariableExpression::getProperty() const if (prop) return prop; else - throw Base::Exception("Property not found."); + throw Base::Exception(std::string("Property '") + var.getPropertyName() + std::string("' not found.")); } /** @@ -1785,6 +1795,11 @@ Expression *RangeExpression::simplify() const return copy(); } +void RangeExpression::setRange(const Range &r) +{ + range = r; +} + } /** diff --git a/src/Mod/Spreadsheet/App/Expression.h b/src/Mod/Spreadsheet/App/Expression.h index b9e12b9d762b..3dfc65695a7b 100644 --- a/src/Mod/Spreadsheet/App/Expression.h +++ b/src/Mod/Spreadsheet/App/Expression.h @@ -158,6 +158,8 @@ class SpreadsheetExport Path { App::Document *getDocument() const; + const App::DocumentObject *getDocumentObject() const; + protected: const App::DocumentObject *getDocumentObject(const App::Document *doc, const std::string &name) const; @@ -367,6 +369,8 @@ class SpreadsheetExport RangeExpression : public Expression { Range getRange() const { return range; } + void setRange(const Range & r); + protected: Range range; }; diff --git a/src/Mod/Spreadsheet/App/PropertyColumnWidths.cpp b/src/Mod/Spreadsheet/App/PropertyColumnWidths.cpp index b064df99035a..cba0ecd20fe2 100644 --- a/src/Mod/Spreadsheet/App/PropertyColumnWidths.cpp +++ b/src/Mod/Spreadsheet/App/PropertyColumnWidths.cpp @@ -155,3 +155,14 @@ PyObject *PropertyColumnWidths::getPyObject() } return Py::new_reference_to(PythonObject); } + +void PropertyColumnWidths::clear() +{ + std::map::const_iterator i = begin(); + + while (i != end()) { + dirty.insert(i->first); + ++i; + } + std::map::clear(); +} diff --git a/src/Mod/Spreadsheet/App/PropertyColumnWidths.h b/src/Mod/Spreadsheet/App/PropertyColumnWidths.h index 9adfe17c0026..d7a282c371aa 100644 --- a/src/Mod/Spreadsheet/App/PropertyColumnWidths.h +++ b/src/Mod/Spreadsheet/App/PropertyColumnWidths.h @@ -35,8 +35,6 @@ class SpreadsheetExport PropertyColumnWidths : public App::Property, std::map dirty; Py::Object PythonObject; diff --git a/src/Mod/Spreadsheet/App/PropertyRowHeights.cpp b/src/Mod/Spreadsheet/App/PropertyRowHeights.cpp index 68a0737e24e7..9b5aa53cffef 100644 --- a/src/Mod/Spreadsheet/App/PropertyRowHeights.cpp +++ b/src/Mod/Spreadsheet/App/PropertyRowHeights.cpp @@ -148,3 +148,14 @@ PyObject *PropertyRowHeights::getPyObject() } return Py::new_reference_to(PythonObject); } + +void PropertyRowHeights::clear() +{ + std::map::const_iterator i = begin(); + + while (i != end()) { + dirty.insert(i->first); + ++i; + } + std::map::clear(); +} diff --git a/src/Mod/Spreadsheet/App/PropertyRowHeights.h b/src/Mod/Spreadsheet/App/PropertyRowHeights.h index 3042c16b204b..b44d5d2fce42 100644 --- a/src/Mod/Spreadsheet/App/PropertyRowHeights.h +++ b/src/Mod/Spreadsheet/App/PropertyRowHeights.h @@ -35,8 +35,6 @@ class SpreadsheetExport PropertyRowHeights : public App::Property, std::map dirty; Py::Object PythonObject; diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 0b95dd49a9dd..3e0425c56247 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -149,6 +149,8 @@ void PropertySheet::clear() propertyNameToCellMap.clear(); documentObjectToCellMap.clear(); docDeps.clear(); + aliasProp.clear(); + revAliasProp.clear(); } Cell *PropertySheet::getValue(CellAddress key) @@ -429,8 +431,30 @@ void PropertySheet::setDisplayUnit(CellAddress address, const std::string &unit) void PropertySheet::setAlias(CellAddress address, const std::string &alias) { - assert(nonNullCellAt(address) != 0); - nonNullCellAt(address)->setAlias(alias); + Cell * cell = nonNullCellAt(address); + 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::map >::const_iterator j = propertyNameToCellMap.find(fullName); + if (j != propertyNameToCellMap.end()) { + std::set< CellAddress >::const_iterator k = j->second.begin(); + + while (k != j->second.end()) { + setDirty(*k); + ++k; + } + } + + std::string oldAlias; + + if (cell->getAlias(oldAlias)) + owner->aliasRemoved(address, oldAlias); + + cell->setAlias(alias); } void PropertySheet::setComputedUnit(CellAddress address, const Base::Unit &unit) @@ -464,6 +488,13 @@ void PropertySheet::clear(CellAddress address) // Mark as dirty dirty.insert(i->first); + // Remove alias if it exists + std::map::iterator j = aliasProp.find(address); + if (j != aliasProp.end()) { + revAliasProp.erase(j->second); + aliasProp.erase(j); + } + // Erase from internal struct data.erase(i); @@ -524,13 +555,15 @@ class RewriteExpressionVisitor : public ExpressionVisitor { bool changed() const { return mChanged; } void visit(Expression * node) { - VariableExpression *expr = freecad_dynamic_cast(node); + VariableExpression *varExpr = freecad_dynamic_cast(node); + RangeExpression *rangeExpr = freecad_dynamic_cast(node); - if (expr) { + + if (varExpr) { static const boost::regex e("(\\${0,1})([A-Za-z]+)(\\${0,1})([0-9]+)"); boost::cmatch cm; - if (boost::regex_match(expr->name().c_str(), cm, e)) { + if (boost::regex_match(varExpr->name().c_str(), cm, e)) { const boost::sub_match colstr = cm[2]; const boost::sub_match rowstr = cm[4]; int thisRow, thisCol; @@ -541,11 +574,26 @@ class RewriteExpressionVisitor : public ExpressionVisitor { if (thisRow >= mRow || thisCol >= mCol) { thisRow += mRowCount; thisCol += mColCount; - expr->setName(columnName(thisCol) + rowName(thisRow)); + varExpr->setName(columnName(thisCol) + rowName(thisRow)); mChanged = true; } } } + else if (rangeExpr) { + Range r = rangeExpr->getRange(); + CellAddress from(r.from()); + CellAddress to(r.to()); + + if (from.row() >= mRow || from.col() >= mCol) { + from = CellAddress(from.row() + mRowCount, from.col() + mColCount); + mChanged = true; + } + if (to.row() >= mRow || to.col() >= mCol) { + to = CellAddress(to.row() + mRowCount, to.col() + mColCount); + mChanged = true; + } + rangeExpr->setRange(Range(from, to)); + } } private: int mRow; @@ -832,13 +880,13 @@ void PropertySheet::addDependencies(CellAddress key) std::set::const_iterator i = expressionDeps.begin(); while (i != expressionDeps.end()) { - const Property * prop = (*i).getProperty(); - DocumentObject * docObj = prop ? freecad_dynamic_cast(prop->getContainer()) : 0; - Document * doc = docObj ? docObj->getDocument() : 0; + const Property * prop = i->getProperty(); + const DocumentObject * docObj = i->getDocumentObject(); + Document * doc = i->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 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"); @@ -857,6 +905,19 @@ void PropertySheet::addDependencies(CellAddress key) 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); + } + } + documentObjectToCellMap[docObjName].insert(key); cellToDocumentObjectMap[key].insert(docObjName); @@ -874,50 +935,47 @@ void PropertySheet::addDependencies(CellAddress key) void PropertySheet::removeDependencies(CellAddress key) { - std::map >::iterator i1 = cellToPropertyNameMap.find(key); - std::set< std::string >::iterator j; - /* Remove from Property <-> Key maps */ - if (i1 == cellToPropertyNameMap.end()) - return; + std::map >::iterator i1 = cellToPropertyNameMap.find(key); - j = i1->second.begin(); + if (i1 != cellToPropertyNameMap.end()) { + std::set< std::string >::const_iterator j = i1->second.begin(); - while (j != i1->second.end()) { - std::map >::iterator k = propertyNameToCellMap.find(*j); + while (j != i1->second.end()) { + std::map >::iterator k = propertyNameToCellMap.find(*j); - assert(k != propertyNameToCellMap.end()); + assert(k != propertyNameToCellMap.end()); - k->second.erase(key); - ++j; - } + k->second.erase(key); + ++j; + } - cellToPropertyNameMap.erase(i1); + cellToPropertyNameMap.erase(i1); + } /* Remove from DocumentObject <-> Key maps */ std::map >::iterator i2 = cellToDocumentObjectMap.find(key); - if (i2 == cellToDocumentObjectMap.end()) - return; + if (i2 != cellToDocumentObjectMap.end()) { + std::set< std::string >::const_iterator j = i2->second.begin(); - j = i2->second.begin(); + while (j != i2->second.end()) { + std::map >::iterator k = documentObjectToCellMap.find(*j); - while (j != i2->second.end()) { - std::map >::iterator k = documentObjectToCellMap.find(*j); + assert(k != documentObjectToCellMap.end()); - assert(k != documentObjectToCellMap.end()); + k->second.erase(key); - k->second.erase(key); + if (k->second.size() == 0) + documentObjectToCellMap.erase(*j); - if (k->second.size() == 0) - documentObjectToCellMap.erase(*j); + ++j; + } - ++j; + cellToDocumentObjectMap.erase(i2); } - - cellToDocumentObjectMap.erase(i2); } /** @@ -1028,6 +1086,11 @@ void PropertySheet::renamedDocument(const Document * doc) } } +void PropertySheet::documentSet() +{ + documentName[owner->getDocument()] = owner->getDocument()->Label.getValue(); +} + void PropertySheet::recomputeDependants(const DocumentObject *docObj) { const char * docName = docObj->getDocument()->Label.getValue(); diff --git a/src/Mod/Spreadsheet/App/PropertySheet.h b/src/Mod/Spreadsheet/App/PropertySheet.h index 7717ab60f9f7..c718d549d7d6 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.h +++ b/src/Mod/Spreadsheet/App/PropertySheet.h @@ -43,8 +43,6 @@ class PropertySheet : public App::Property { PropertySheet(Sheet * _owner = 0); - PropertySheet(const PropertySheet & other); - ~PropertySheet(); virtual Property *Copy(void) const; @@ -145,8 +143,12 @@ class PropertySheet : public App::Property { void renamedDocument(const App::Document *doc); + void documentSet(); + private: + PropertySheet(const PropertySheet & other); + friend class Signaller; friend class SheetObserver; @@ -211,6 +213,12 @@ class PropertySheet : public App::Property { /* Name of documents, used for renaming */ std::map documentName; + /* Mapping of cell position to alias property */ + std::map aliasProp; + + /* Mapping of alias property to cell position */ + std::map revAliasProp; + int signalCounter; Py::Object PythonObject; diff --git a/src/Mod/Spreadsheet/App/Range.cpp b/src/Mod/Spreadsheet/App/Range.cpp index c76d1cf38413..673b27707597 100644 --- a/src/Mod/Spreadsheet/App/Range.cpp +++ b/src/Mod/Spreadsheet/App/Range.cpp @@ -66,6 +66,16 @@ Range::Range(int _row_begin, int _col_begin, int _row_end, int _col_end) { } +Range::Range(const CellAddress &from, const CellAddress &to) + : row_curr(from.row()) + , col_curr(from.col()) + , row_begin(from.row()) + , col_begin(from.col()) + , row_end(to.row()) + , col_end(to.col()) +{ +} + bool Range::next() { if (row_curr < row_end) { diff --git a/src/Mod/Spreadsheet/App/Range.h b/src/Mod/Spreadsheet/App/Range.h index 2c94d15b02e2..5264790afabe 100644 --- a/src/Mod/Spreadsheet/App/Range.h +++ b/src/Mod/Spreadsheet/App/Range.h @@ -45,6 +45,8 @@ class SpreadsheetExport Range { Range(int _row_begin, int _col_begin, int _row_end, int _col_end); + Range(const CellAddress & from, const CellAddress & to); + bool next(); /** Current row */ @@ -60,17 +62,17 @@ class SpreadsheetExport Range { inline CellAddress to() const { return CellAddress(row_end, col_end); } /** Start of range as a string */ - inline std::string fromCellString() const { return addressToString(CellAddress(row_begin, col_begin)); } + inline std::string fromCellString() const { return CellAddress(row_begin, col_begin).toString(); } /** End of range as a string */ - inline std::string toCellString() const { return addressToString(CellAddress(row_end, col_end)); } + inline std::string toCellString() const { return CellAddress(row_end, col_end).toString(); } /** Current cell as a string */ - inline std::string address() const { return addressToString(CellAddress(row_curr, col_curr)); } + inline std::string address() const { return CellAddress(row_curr, col_curr).toString(); } /** The raneg as a string */ inline std::string rangeString() const { - return addressToString(CellAddress(row_begin, col_begin)) + ":" + addressToString(CellAddress(row_end, col_end)); + return CellAddress(row_begin, col_begin).toString() + ":" + CellAddress(row_end, col_end).toString(); } CellAddress operator*() const { return CellAddress(row_curr, col_curr); } diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index 60607fcc6681..d2a44dd0954a 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -110,9 +110,15 @@ void Sheet::clearAll() props.removeDynamicProperty((*i).c_str()); propAddress.clear(); + cellErrors.clear(); + columnWidths.clear(); + rowHeights.clear(); + removedAliases.clear(); + docDeps.setValues(std::vector()); for (ObserverMap::iterator i = observers.begin(); i != observers.end(); ++i) delete i->second; + observers.clear(); } /** @@ -315,19 +321,6 @@ Cell *Sheet::getNewCell(CellAddress address) return cell; } -/** - * Convert given \a key address to string. - * - * @param key Address of cell. - * - * @returns Address given as a string. - */ - -std::string Sheet::toAddress(CellAddress key) -{ - return Spreadsheet::addressToString(key); -} - /** * Set cell given by \a address to \a contents. * @@ -400,7 +393,7 @@ PyObject *Sheet::getPyObject(void) Property * Sheet::getProperty(CellAddress key) const { - return props.getDynamicPropertyByName(toAddress(key).c_str()); + return props.getDynamicPropertyByName(key.toString().c_str()); } Property * Sheet::getProperty(const char * addr) const @@ -442,6 +435,22 @@ void Sheet::setPosition(CellAddress address) currColumn.purgeTouched(); } +void Sheet::removeAliases() +{ + std::map::iterator i = removedAliases.begin(); + + while (i != removedAliases.end()) { + props.removeDynamicProperty(i->second.c_str()); + ++i; + } + removedAliases.clear(); +} + +void Sheet::onSettingDocument() +{ + cells.documentSet(); +} + /** * Set the property for cell \key to a PropertyFloat with the value \a value. * If the Property exists, but of wrong type, the previous Property is destroyed and recreated as the correct type. @@ -453,21 +462,15 @@ void Sheet::setPosition(CellAddress address) Property * Sheet::setFloatProperty(CellAddress key, double value) { - Property * prop = props.getPropertyByName(toAddress(key).c_str()); + Property * prop = props.getPropertyByName(key.toString().c_str()); PropertyFloat * floatProp; if (!prop || prop->getTypeId() != PropertyFloat::getClassTypeId()) { if (prop) { - props.removeDynamicProperty(toAddress(key).c_str()); + props.removeDynamicProperty(key.toString().c_str()); propAddress.erase(prop); - - std::map::iterator i = aliasProp.find(key); - if (i != aliasProp.end()) { - props.removeDynamicProperty(props.getPropertyName(i->second)); - aliasProp.erase(i); - } } - floatProp = freecad_dynamic_cast(props.addDynamicProperty("App::PropertyFloat", toAddress(key).c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true)); + floatProp = freecad_dynamic_cast(props.addDynamicProperty("App::PropertyFloat", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true)); floatProp->StatusBits.set(3); } else @@ -491,26 +494,20 @@ Property * Sheet::setFloatProperty(CellAddress key, double value) Property * Sheet::setQuantityProperty(CellAddress key, double value, const Base::Unit & unit) { - Property * prop = props.getPropertyByName(toAddress(key).c_str()); - PropertyQuantity * quantityProp; + Property * prop = props.getPropertyByName(key.toString().c_str()); + PropertySpreadsheetQuantity * quantityProp; - if (!prop || prop->getTypeId() != PropertyQuantity::getClassTypeId()) { + if (!prop || prop->getTypeId() != PropertySpreadsheetQuantity::getClassTypeId()) { if (prop) { - props.removeDynamicProperty(toAddress(key).c_str()); + props.removeDynamicProperty(key.toString().c_str()); propAddress.erase(prop); - - std::map::iterator i = aliasProp.find(key); - if (i != aliasProp.end()) { - props.removeDynamicProperty(props.getPropertyName(i->second)); - aliasProp.erase(i); - } } - Property * p = props.addDynamicProperty("App::PropertyQuantity", toAddress(key).c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true); - quantityProp = freecad_dynamic_cast(p); + Property * p = props.addDynamicProperty("Spreadsheet::PropertySpreadsheetQuantity", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true); + quantityProp = freecad_dynamic_cast(p); quantityProp->StatusBits.set(3); } else - quantityProp = static_cast(prop); + quantityProp = static_cast(prop); propAddress[quantityProp] = key; quantityProp->setValue(value); @@ -532,21 +529,15 @@ Property * Sheet::setQuantityProperty(CellAddress key, double value, const Base: Property * Sheet::setStringProperty(CellAddress key, const std::string & value) { - Property * prop = props.getPropertyByName(toAddress(key).c_str()); + Property * prop = props.getPropertyByName(key.toString().c_str()); PropertyString * stringProp = freecad_dynamic_cast(prop); if (!stringProp) { if (prop) { - props.removeDynamicProperty(toAddress(key).c_str()); + props.removeDynamicProperty(key.toString().c_str()); propAddress.erase(prop); - - std::map::iterator i = aliasProp.find(key); - if (i != aliasProp.end()) { - props.removeDynamicProperty(props.getPropertyName(i->second)); - aliasProp.erase(i); - } } - stringProp = freecad_dynamic_cast(props.addDynamicProperty("App::PropertyString", toAddress(key).c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true)); + stringProp = freecad_dynamic_cast(props.addDynamicProperty("App::PropertyString", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true)); stringProp->StatusBits.set(3); } @@ -556,6 +547,35 @@ Property * Sheet::setStringProperty(CellAddress key, const std::string & value) return stringProp; } +void Sheet::updateAlias(CellAddress key) +{ + std::string alias; + Property * prop = props.getDynamicPropertyByName(key.toString().c_str()); + + if (!prop) + return; + + Cell * cell = getCell(key); + + if (cell && cell->getAlias(alias)) { + App::Property * aliasProp = props.getDynamicPropertyByName(alias.c_str()); + + /* Update or create alias? */ + if (aliasProp) { + // Type of alias and property must always be the same + if (aliasProp->getTypeId() != prop->getTypeId()) { + props.removeDynamicProperty(alias.c_str()); + aliasProp = 0; + } + } + + if (!aliasProp) + aliasProp = props.addDynamicProperty(prop->getTypeId().getName(), alias.c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true); + + aliasProp->Paste(*prop); + } +} + /** * Update the Propery given by \a key. This will also eventually trigger recomputations of cells depending on \a key. * @@ -566,10 +586,8 @@ Property * Sheet::setStringProperty(CellAddress key, const std::string & value) void Sheet::updateProperty(CellAddress key) { const Property * prop; - const char * aliasType = 0; Cell * cell = getCell(key); - std::string alias; if (cell != 0) { Expression * output; @@ -590,40 +608,19 @@ void Sheet::updateProperty(CellAddress key) /* Eval returns either NumberExpression or StringExpression objects */ if (freecad_dynamic_cast(output)) { NumberExpression * number = static_cast(output); - if (number->getUnit().isEmpty()) { + if (number->getUnit().isEmpty()) prop = setFloatProperty(key, number->getValue()); - aliasType = "App::PropertyFloat"; - } - else { + else prop = setQuantityProperty(key, number->getValue(), number->getUnit()); - aliasType = "App::PropertyQuantity"; - } } - else { + else prop = setStringProperty(key, freecad_dynamic_cast(output)->getText().c_str()); - aliasType = "App::PropertyString"; - } delete output; } else clear(key); - if (cell && cell->getAlias(alias)) { - /* Update or create alias? */ - if (aliasProp.find(key) == aliasProp.end()) - aliasProp[key] = props.addDynamicProperty(aliasType, alias.c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true); - aliasProp[key]->Paste(*prop); - } - else { - /* Remove alias? */ - std::map::iterator i = aliasProp.find(key); - if (i != aliasProp.end()) { - props.removeDynamicProperty(props.getPropertyName(i->second)); - aliasProp.erase(i); - } - } - cellUpdated(key); } @@ -661,7 +658,7 @@ 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 + "." + toAddress(p); + std::string name = docName + "#" + docObjName + "." + p.toString(); try { if (cell) { @@ -683,6 +680,8 @@ void Sheet::recomputeCell(CellAddress p) cellErrors.insert(p); } + updateAlias(p); + if (!cell || cell->spansChanged()) cellSpanChanged(p); } @@ -694,6 +693,9 @@ void Sheet::recomputeCell(CellAddress p) App::DocumentObjectExecReturn *Sheet::execute(void) { + // Remove all aliases first + removeAliases(); + // Get dirty cells that we have to recompute std::set dirtyCells = cells.getDirty(); @@ -769,6 +771,8 @@ App::DocumentObjectExecReturn *Sheet::execute(void) if (cell) cell->setException("Circular dependency."); updateProperty(i->first); + updateAlias(i->first); + ++i; } } @@ -797,6 +801,10 @@ App::DocumentObjectExecReturn *Sheet::execute(void) currColumn.purgeTouched(); 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); @@ -837,9 +845,15 @@ short Sheet::mustExecute(void) const void Sheet::clear(CellAddress address, bool all) { - std::string addr = toAddress(address); + Cell * cell = getCell(address); + std::string addr = address.toString(); Property * prop = props.getDynamicPropertyByName(addr.c_str()); + // Remove alias, if defined + std::string aliasStr; + if (cell && cell->getAlias(aliasStr)) + props.removeDynamicProperty(aliasStr.c_str()); + cells.clear(address); propAddress.erase(prop); @@ -911,7 +925,7 @@ std::vector Sheet::getUsedCells() const std::set usedSet = cells.getUsedCells(); for (std::set::const_iterator i = usedSet.begin(); i != usedSet.end(); ++i) - usedCells.push_back(toAddress(*i)); + usedCells.push_back(i->toString()); return usedCells; } @@ -1031,6 +1045,11 @@ void Sheet::renamedDocumentObject(const App::DocumentObject * docObj) cells.touch(); } +void Sheet::aliasRemoved(CellAddress address, const std::string & alias) +{ + removedAliases[address] = alias; +} + std::set Sheet::dependsOn(CellAddress address) const { return cells.getDeps(address); @@ -1040,18 +1059,18 @@ void Sheet::providesTo(CellAddress address, std::set & result) cons { const char * docName = getDocument()->Label.getValue(); const char * docObjName = getNameInDocument(); - std::string fullName = std::string(docName) + "#" + std::string(docObjName) + "." + toAddress(address); + std::string fullName = std::string(docName) + "#" + std::string(docObjName) + "." + address.toString(); std::set tmpResult = cells.getDeps(fullName); for (std::set::const_iterator i = tmpResult.begin(); i != tmpResult.end(); ++i) - result.insert(std::string(docName) + "#" + std::string(docObjName) + "." + toAddress(*i)); + result.insert(std::string(docName) + "#" + std::string(docObjName) + "." + i->toString()); } 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) + "." + toAddress(address); + std::string fullName = std::string(docName) + "#" + std::string(docObjName) + "." + address.toString(); result = cells.getDeps(fullName); } @@ -1086,3 +1105,23 @@ void Sheet::observeDocument(Document * document) observers[document->getName()] = observer; } } + +TYPESYSTEM_SOURCE(Spreadsheet::PropertySpreadsheetQuantity, App::PropertyQuantity); + +Property *PropertySpreadsheetQuantity::Copy() const +{ + PropertySpreadsheetQuantity * obj = new PropertySpreadsheetQuantity(); + + obj->_dValue = _dValue; + obj->_Unit = _Unit; + + return obj; +} + +void PropertySpreadsheetQuantity::Paste(const Property &from) +{ + aboutToSetValue(); + _dValue = static_cast(&from)->_dValue; + _Unit = static_cast(&from)->_Unit; + hasSetValue(); +} diff --git a/src/Mod/Spreadsheet/App/Sheet.h b/src/Mod/Spreadsheet/App/Sheet.h index 0bde46c55a49..a7a109556b38 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -52,10 +52,25 @@ class Expression; class Range; class SheetObserver; +/** Spreadsheet quantity property + * This is a property for quantities, and unlike its ancestor implements + * Copy() and Paste() methods. It is used by the spreadsheet to + * create aliases in a generic way. + */ +class SpreadsheetExport PropertySpreadsheetQuantity : public App::PropertyQuantity +{ + TYPESYSTEM_HEADER(); +public: + PropertySpreadsheetQuantity(void){} + virtual ~PropertySpreadsheetQuantity(){} + + virtual Property *Copy(void) const; + virtual void Paste(const Property &from); +}; class SpreadsheetExport Sheet : public App::DocumentObject { - PROPERTY_HEADER(Sheet::Sheet); + PROPERTY_HEADER(Spreadsheet::Sheet); public: @@ -220,6 +235,8 @@ class SpreadsheetExport Sheet : public App::DocumentObject App::Property *getProperty(const char * addr) const; + void updateAlias(CellAddress key); + void updateProperty(CellAddress key); App::Property *setStringProperty(CellAddress key, const std::string & value) ; @@ -228,20 +245,24 @@ class SpreadsheetExport Sheet : public App::DocumentObject App::Property *setQuantityProperty(CellAddress key, double value, const Base::Unit &unit); - static std::string toAddress(CellAddress key); - void moveCell(CellAddress currPos, CellAddress newPos); void renamedDocumentObject(const App::DocumentObject * docObj); + void aliasRemoved(CellAddress address, const std::string &alias); + + void removeAliases(); + + virtual void onSettingDocument(); + /* Properties for used cells */ App::DynamicProperty props; /* Mapping of properties to cell position */ std::map propAddress; - /* Mapping of cell position to alias property */ - std::map aliasProp; + /* Removed (unprocessed) aliases */ + std::map removedAliases; /* Set of cells with errors */ std::set cellErrors; diff --git a/src/Mod/Spreadsheet/App/Utils.cpp b/src/Mod/Spreadsheet/App/Utils.cpp index 25ee3b89e2b1..8eef12e5cb51 100644 --- a/src/Mod/Spreadsheet/App/Utils.cpp +++ b/src/Mod/Spreadsheet/App/Utils.cpp @@ -161,33 +161,6 @@ Spreadsheet::CellAddress Spreadsheet::stringToAddress(const char * strAddress) throw Base::Exception("Invalid cell specifier."); } -/** - * Convert given \a row and \a col into a string address. - * - * @param row Row address. - * @param col Column address. - * - * @returns Address given as a string. - */ - -std::string Spreadsheet::addressToString(const CellAddress &address) -{ - std::stringstream s; - - if (address.col() < 26) - s << (char)('A' + address.col()); - else { - int col = address.col() - 26; - - s << (char)('A' + (col / 26)); - s << (char)('A' + (col % 26)); - } - - s << (address.row() + 1); - - return s.str(); -} - void Spreadsheet::createRectangles(std::set > & cells, std::map, std::pair > & rectangles) { while (cells.size() != 0) { @@ -342,3 +315,28 @@ std::string Spreadsheet::unquote(const std::string & input) return output; } + + +/** + * Convert given \a cell address into its string representation. + * + * @returns Address given as a string. + */ + +std::string Spreadsheet::CellAddress::toString() const +{ + std::stringstream s; + + if (col() < 26) + s << (char)('A' + col()); + else { + int colnum = col() - 26; + + s << (char)('A' + (colnum / 26)); + s << (char)('A' + (colnum % 26)); + } + + s << (row() + 1); + + return s.str(); +} diff --git a/src/Mod/Spreadsheet/App/Utils.h b/src/Mod/Spreadsheet/App/Utils.h index 2d760926d10b..411f8b54ead2 100644 --- a/src/Mod/Spreadsheet/App/Utils.h +++ b/src/Mod/Spreadsheet/App/Utils.h @@ -42,12 +42,9 @@ SpreadsheetExport void createRectangles(std::set > & cells, SpreadsheetExport std::string quote(const std::string &input); SpreadsheetExport std::string unquote(const std::string & input); -SpreadsheetExport std::string addressToString(const CellAddress & address); +struct SpreadsheetExport CellAddress { -struct CellAddress { - CellAddress(unsigned int _value) : value(_value) { } - - CellAddress(int row = -1, int col = -1) : value(((row & 0xffff) << 16) | (col & 0xffff)) { } + CellAddress(int row = -1, int col = -1) : _row(row), _col(col) { } CellAddress(const char * address) { *this = stringToAddress(address); @@ -57,18 +54,20 @@ struct CellAddress { *this = stringToAddress(address.c_str()); } - inline int row() const { return (value >> 16) & 0xffff; } + inline int row() const { return _row; } - inline int col() const { return value & 0xffff; } + inline int col() const { return _col; } - inline bool operator<(const CellAddress & other) const { return value < other.value; } + inline bool operator<(const CellAddress & other) const { return asInt() < other.asInt(); } - inline bool operator==(const CellAddress & other) const { return value == other.value; } + inline bool operator==(const CellAddress & other) const { return asInt() == other.asInt(); } - inline bool operator!=(const CellAddress & other) const { return value != other.value; } + inline bool operator!=(const CellAddress & other) const { return asInt() != other.asInt(); } inline bool isValid() { return (row() >=0 && row() < MAX_ROWS && col() >= 0 && col() < MAX_COLUMNS); } + std::string toString() const; + // Static members static const int MAX_ROWS; @@ -76,7 +75,11 @@ struct CellAddress { static const int MAX_COLUMNS; protected: - unsigned int value; + + inline unsigned int asInt() const { return ((_row << 16) | _col); } + + short _row; + short _col; }; template T * freecad_dynamic_cast(Base::BaseClass * t) diff --git a/src/Mod/Spreadsheet/Gui/Command.cpp b/src/Mod/Spreadsheet/Gui/Command.cpp index 96eee18492ee..3c5a9d798798 100644 --- a/src/Mod/Spreadsheet/Gui/Command.cpp +++ b/src/Mod/Spreadsheet/Gui/Command.cpp @@ -125,7 +125,7 @@ void CmdSpreadsheetSplitCell::activated(int iMsg) QModelIndex current = sheetView->currentIndex(); if (current.isValid()) { - std::string address = addressToString(CellAddress(current.row(), current.column())); + std::string address = CellAddress(current.row(), current.column()).toString(); Gui::Command::openCommand("Split cell"); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.splitCell('%s')", sheet->getNameInDocument(), address.c_str()); diff --git a/src/Mod/Spreadsheet/Gui/SheetModel.cpp b/src/Mod/Spreadsheet/Gui/SheetModel.cpp index f3a85abd1f75..00e38a5baca3 100644 --- a/src/Mod/Spreadsheet/Gui/SheetModel.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetModel.cpp @@ -147,10 +147,10 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const if (role == Qt::ToolTipRole) { QString v; - std::set deps = sheet->dependsOn(row, col); + std::set deps = sheet->dependsOn(CellAddress(row, col)); std::set provides; - sheet->providesTo(row, col, provides); + sheet->providesTo(CellAddress(row, col), provides); if (deps.size() > 0) { v += QString::fromUtf8("Depends on:"); @@ -196,7 +196,7 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const } // Get display value as computed property - std::string address = addressToString(CellAddress(row, col)); + std::string address = CellAddress(row, col).toString(); Property * prop = sheet->getPropertyByName(address.c_str()); if (prop == 0) @@ -394,8 +394,8 @@ bool SheetModel::setData(const QModelIndex & index, const QVariant & value, int CellAddress address(index.row(), index.column()); try { - std::string strAddress = addressToString(address); - std::string next_address = addressToString(CellAddress(address.row() + 1, address.col())); + std::string strAddress = address.toString(); + std::string next_address = CellAddress(address.row() + 1, address.col()).toString(); QString str = value.toString(); std::string content; Cell * cell = sheet->getCell(address); diff --git a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.cpp b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.cpp index 5ca86f1fed96..93546830a831 100644 --- a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.cpp +++ b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheet.cpp @@ -158,7 +158,7 @@ bool ViewProviderSheet::onDelete(const std::vector &) if (selection.size() > 0) { Gui::Command::openCommand("Clear cell(s)"); for (QModelIndexList::const_iterator it = selection.begin(); it != selection.end(); ++it) { - std::string address = Spreadsheet::addressToString(CellAddress((*it).row(), (*it).column())); + std::string address = CellAddress((*it).row(), (*it).column()).toString(); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.clear('%s')", sheet->getNameInDocument(), address.c_str()); }