- 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.
This commit is contained in:
Eivind Kvedalen 2015-03-04 22:14:35 +01:00 committed by wmayer
parent 1b7c0e2a51
commit 3743008cda
20 changed files with 382 additions and 178 deletions

View File

@ -31,6 +31,7 @@ void SpreadsheetExport initSpreadsheet() {
(void) Py_InitModule("Spreadsheet", Spreadsheet_methods); /* mod name, table ptr */ (void) Py_InitModule("Spreadsheet", Spreadsheet_methods); /* mod name, table ptr */
Base::Console().Log("Loading Spreadsheet module... done\n"); Base::Console().Log("Loading Spreadsheet module... done\n");
Spreadsheet::PropertySpreadsheetQuantity::init();
Spreadsheet::PropertyColumnWidths::init(); Spreadsheet::PropertyColumnWidths::init();
Spreadsheet::PropertyRowHeights::init(); Spreadsheet::PropertyRowHeights::init();
Spreadsheet::PropertySheet::init(); Spreadsheet::PropertySheet::init();

View File

@ -395,8 +395,20 @@ void Cell::setAlias(const std::string &n)
if (alias != n) { if (alias != n) {
PropertySheet::Signaller signaller(*owner); PropertySheet::Signaller signaller(*owner);
owner->revAliasProp.erase(alias);
alias = n; alias = n;
// Update owner
if (alias != "") {
owner->aliasProp[address] = n;
owner->revAliasProp[n] = address;
}
else
owner->aliasProp.erase(address);
setUsed(ALIAS_SET, !alias.empty()); setUsed(ALIAS_SET, !alias.empty());
} }
} }
@ -590,7 +602,7 @@ void Cell::save(Base::Writer &writer) const
writer.Stream() << writer.ind() << "<Cell "; writer.Stream() << writer.ind() << "<Cell ";
writer.Stream() << "address=\"" << addressToString(address) << "\" "; writer.Stream() << "address=\"" << address.toString() << "\" ";
if (isUsed(EXPRESSION_SET)) { if (isUsed(EXPRESSION_SET)) {
std::string content; std::string content;
@ -663,7 +675,7 @@ bool Cell::isUsed() const
void Cell::visit(ExpressionVisitor &v) void Cell::visit(ExpressionVisitor &v)
{ {
if (expression) if (expression)
v.visit(expression); expression->visit(v);
} }
/** /**

View File

@ -116,6 +116,8 @@ public:
void visit(ExpressionVisitor & v); void visit(ExpressionVisitor & v);
CellAddress getAddress() const { return address; }
/* Alignment */ /* Alignment */
static const int ALIGNMENT_LEFT; static const int ALIGNMENT_LEFT;
static const int ALIGNMENT_HCENTER; static const int ALIGNMENT_HCENTER;

View File

@ -176,7 +176,7 @@ std::string Path::getPythonAccessor() const
const Property * prop = getProperty(); const Property * prop = getProperty();
if (!prop) if (!prop)
throw Exception("Property not found"); throw Exception(std::string("Property '") + getPropertyName() + std::string("' not found."));
const DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(prop->getContainer()); const DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(prop->getContainer());
@ -1309,6 +1309,16 @@ Document * Path::getDocument() const
return doc; 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 Property *Path::getProperty() const
{ {
const App::Document * doc = getDocument(); const App::Document * doc = getDocument();
@ -1346,7 +1356,7 @@ const Property * VariableExpression::getProperty() const
if (prop) if (prop)
return prop; return prop;
else 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(); return copy();
} }
void RangeExpression::setRange(const Range &r)
{
range = r;
}
} }
/** /**

View File

@ -158,6 +158,8 @@ public:
App::Document *getDocument() const; App::Document *getDocument() const;
const App::DocumentObject *getDocumentObject() const;
protected: protected:
const App::DocumentObject *getDocumentObject(const App::Document *doc, const std::string &name) const; const App::DocumentObject *getDocumentObject(const App::Document *doc, const std::string &name) const;
@ -367,6 +369,8 @@ public:
Range getRange() const { return range; } Range getRange() const { return range; }
void setRange(const Range & r);
protected: protected:
Range range; Range range;
}; };

View File

@ -155,3 +155,14 @@ PyObject *PropertyColumnWidths::getPyObject()
} }
return Py::new_reference_to(PythonObject); return Py::new_reference_to(PythonObject);
} }
void PropertyColumnWidths::clear()
{
std::map<int, int>::const_iterator i = begin();
while (i != end()) {
dirty.insert(i->first);
++i;
}
std::map<int,int>::clear();
}

View File

@ -35,8 +35,6 @@ class SpreadsheetExport PropertyColumnWidths : public App::Property, std::map<in
public: public:
PropertyColumnWidths(); PropertyColumnWidths();
PropertyColumnWidths(const PropertyColumnWidths & other);
void setValue() { } void setValue() { }
void setValue(int col, int width); void setValue(int col, int width);
@ -66,11 +64,15 @@ public:
PyObject *getPyObject(void); PyObject *getPyObject(void);
void clear();
static const int defaultWidth; static const int defaultWidth;
static const int defaultHeaderWidth; static const int defaultHeaderWidth;
private: private:
PropertyColumnWidths(const PropertyColumnWidths & other);
std::set<int> dirty; std::set<int> dirty;
Py::Object PythonObject; Py::Object PythonObject;

View File

@ -148,3 +148,14 @@ PyObject *PropertyRowHeights::getPyObject()
} }
return Py::new_reference_to(PythonObject); return Py::new_reference_to(PythonObject);
} }
void PropertyRowHeights::clear()
{
std::map<int, int>::const_iterator i = begin();
while (i != end()) {
dirty.insert(i->first);
++i;
}
std::map<int,int>::clear();
}

View File

@ -35,8 +35,6 @@ class SpreadsheetExport PropertyRowHeights : public App::Property, std::map<int,
public: public:
PropertyRowHeights(); PropertyRowHeights();
PropertyRowHeights(const PropertyRowHeights & other);
void setValue() { } void setValue() { }
void setValue(int row, int height); void setValue(int row, int height);
@ -68,8 +66,12 @@ public:
static const int defaultHeight; static const int defaultHeight;
void clear();
private: private:
PropertyRowHeights(const PropertyRowHeights & other);
std::set<int> dirty; std::set<int> dirty;
Py::Object PythonObject; Py::Object PythonObject;

View File

@ -149,6 +149,8 @@ void PropertySheet::clear()
propertyNameToCellMap.clear(); propertyNameToCellMap.clear();
documentObjectToCellMap.clear(); documentObjectToCellMap.clear();
docDeps.clear(); docDeps.clear();
aliasProp.clear();
revAliasProp.clear();
} }
Cell *PropertySheet::getValue(CellAddress key) 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) void PropertySheet::setAlias(CellAddress address, const std::string &alias)
{ {
assert(nonNullCellAt(address) != 0); Cell * cell = nonNullCellAt(address);
nonNullCellAt(address)->setAlias(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::map<std::string, std::set< CellAddress > >::const_iterator j = propertyNameToCellMap.find(fullName);
if (j != propertyNameToCellMap.end()) {
std::set< CellAddress >::const_iterator k = j->second.begin();
while (k != j->second.end()) {
setDirty(*k);
++k;
}
}
std::string oldAlias;
if (cell->getAlias(oldAlias))
owner->aliasRemoved(address, oldAlias);
cell->setAlias(alias);
} }
void PropertySheet::setComputedUnit(CellAddress address, const Base::Unit &unit) void PropertySheet::setComputedUnit(CellAddress address, const Base::Unit &unit)
@ -464,6 +488,13 @@ void PropertySheet::clear(CellAddress address)
// Mark as dirty // Mark as dirty
dirty.insert(i->first); dirty.insert(i->first);
// Remove alias if it exists
std::map<CellAddress, std::string>::iterator j = aliasProp.find(address);
if (j != aliasProp.end()) {
revAliasProp.erase(j->second);
aliasProp.erase(j);
}
// Erase from internal struct // Erase from internal struct
data.erase(i); data.erase(i);
@ -524,13 +555,15 @@ public:
bool changed() const { return mChanged; } bool changed() const { return mChanged; }
void visit(Expression * node) { void visit(Expression * node) {
VariableExpression *expr = freecad_dynamic_cast<VariableExpression>(node); VariableExpression *varExpr = freecad_dynamic_cast<VariableExpression>(node);
RangeExpression *rangeExpr = freecad_dynamic_cast<RangeExpression>(node);
if (expr) {
if (varExpr) {
static const boost::regex e("(\\${0,1})([A-Za-z]+)(\\${0,1})([0-9]+)"); static const boost::regex e("(\\${0,1})([A-Za-z]+)(\\${0,1})([0-9]+)");
boost::cmatch cm; 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<const char *> colstr = cm[2]; const boost::sub_match<const char *> colstr = cm[2];
const boost::sub_match<const char *> rowstr = cm[4]; const boost::sub_match<const char *> rowstr = cm[4];
int thisRow, thisCol; int thisRow, thisCol;
@ -541,11 +574,26 @@ public:
if (thisRow >= mRow || thisCol >= mCol) { if (thisRow >= mRow || thisCol >= mCol) {
thisRow += mRowCount; thisRow += mRowCount;
thisCol += mColCount; thisCol += mColCount;
expr->setName(columnName(thisCol) + rowName(thisRow)); varExpr->setName(columnName(thisCol) + rowName(thisRow));
mChanged = true; 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: private:
int mRow; int mRow;
@ -832,13 +880,13 @@ void PropertySheet::addDependencies(CellAddress key)
std::set<Path>::const_iterator i = expressionDeps.begin(); std::set<Path>::const_iterator i = expressionDeps.begin();
while (i != expressionDeps.end()) { while (i != expressionDeps.end()) {
const Property * prop = (*i).getProperty(); const Property * prop = i->getProperty();
DocumentObject * docObj = prop ? freecad_dynamic_cast<DocumentObject>(prop->getContainer()) : 0; const DocumentObject * docObj = i->getDocumentObject();
Document * doc = docObj ? docObj->getDocument() : 0; Document * doc = i->getDocument();
std::string docName = doc ? doc->Label.getValue() : (*i).getDocumentName().getString(); std::string docName = doc ? doc->Label.getValue() : i->getDocumentName().getString();
std::string docObjName = docName + "#" + (docObj ? docObj->getNameInDocument() : (*i).getDocumentObjectName().getString()); std::string docObjName = docName + "#" + (docObj ? docObj->getNameInDocument() : i->getDocumentObjectName().getString());
std::string propName = docObjName + "." + (*i).getPropertyName(); std::string propName = docObjName + "." + i->getPropertyName();
if (!prop) if (!prop)
cell->setResolveException("Unresolved dependency"); cell->setResolveException("Unresolved dependency");
@ -857,6 +905,19 @@ void PropertySheet::addDependencies(CellAddress key)
propertyNameToCellMap[propName].insert(key); propertyNameToCellMap[propName].insert(key);
cellToPropertyNameMap[key].insert(propName); cellToPropertyNameMap[key].insert(propName);
// Also an alias?
if (docObj == owner) {
std::map<std::string, CellAddress>::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); documentObjectToCellMap[docObjName].insert(key);
cellToDocumentObjectMap[key].insert(docObjName); cellToDocumentObjectMap[key].insert(docObjName);
@ -874,50 +935,47 @@ void PropertySheet::addDependencies(CellAddress key)
void PropertySheet::removeDependencies(CellAddress key) void PropertySheet::removeDependencies(CellAddress key)
{ {
std::map<CellAddress, std::set< std::string > >::iterator i1 = cellToPropertyNameMap.find(key);
std::set< std::string >::iterator j;
/* Remove from Property <-> Key maps */ /* Remove from Property <-> Key maps */
if (i1 == cellToPropertyNameMap.end()) std::map<CellAddress, std::set< std::string > >::iterator i1 = cellToPropertyNameMap.find(key);
return;
j = i1->second.begin(); if (i1 != cellToPropertyNameMap.end()) {
std::set< std::string >::const_iterator j = i1->second.begin();
while (j != i1->second.end()) { while (j != i1->second.end()) {
std::map<std::string, std::set< CellAddress > >::iterator k = propertyNameToCellMap.find(*j); std::map<std::string, std::set< CellAddress > >::iterator k = propertyNameToCellMap.find(*j);
assert(k != propertyNameToCellMap.end()); assert(k != propertyNameToCellMap.end());
k->second.erase(key); k->second.erase(key);
++j; ++j;
}
cellToPropertyNameMap.erase(i1);
} }
cellToPropertyNameMap.erase(i1);
/* Remove from DocumentObject <-> Key maps */ /* Remove from DocumentObject <-> Key maps */
std::map<CellAddress, std::set< std::string > >::iterator i2 = cellToDocumentObjectMap.find(key); std::map<CellAddress, std::set< std::string > >::iterator i2 = cellToDocumentObjectMap.find(key);
if (i2 == cellToDocumentObjectMap.end()) if (i2 != cellToDocumentObjectMap.end()) {
return; std::set< std::string >::const_iterator j = i2->second.begin();
j = i2->second.begin(); while (j != i2->second.end()) {
std::map<std::string, std::set< CellAddress > >::iterator k = documentObjectToCellMap.find(*j);
while (j != i2->second.end()) { assert(k != documentObjectToCellMap.end());
std::map<std::string, std::set< CellAddress > >::iterator k = documentObjectToCellMap.find(*j);
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) ++j;
documentObjectToCellMap.erase(*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) void PropertySheet::recomputeDependants(const DocumentObject *docObj)
{ {
const char * docName = docObj->getDocument()->Label.getValue(); const char * docName = docObj->getDocument()->Label.getValue();

View File

@ -43,8 +43,6 @@ public:
PropertySheet(Sheet * _owner = 0); PropertySheet(Sheet * _owner = 0);
PropertySheet(const PropertySheet & other);
~PropertySheet(); ~PropertySheet();
virtual Property *Copy(void) const; virtual Property *Copy(void) const;
@ -145,8 +143,12 @@ public:
void renamedDocument(const App::Document *doc); void renamedDocument(const App::Document *doc);
void documentSet();
private: private:
PropertySheet(const PropertySheet & other);
friend class Signaller; friend class Signaller;
friend class SheetObserver; friend class SheetObserver;
@ -211,6 +213,12 @@ private:
/* Name of documents, used for renaming */ /* Name of documents, used for renaming */
std::map<const App::Document*, std::string> documentName; std::map<const App::Document*, std::string> documentName;
/* Mapping of cell position to alias property */
std::map<CellAddress, std::string> aliasProp;
/* Mapping of alias property to cell position */
std::map<std::string, CellAddress> revAliasProp;
int signalCounter; int signalCounter;
Py::Object PythonObject; Py::Object PythonObject;

View File

@ -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() bool Range::next()
{ {
if (row_curr < row_end) { if (row_curr < row_end) {

View File

@ -45,6 +45,8 @@ public:
Range(int _row_begin, int _col_begin, int _row_end, int _col_end); Range(int _row_begin, int _col_begin, int _row_end, int _col_end);
Range(const CellAddress & from, const CellAddress & to);
bool next(); bool next();
/** Current row */ /** Current row */
@ -60,17 +62,17 @@ public:
inline CellAddress to() const { return CellAddress(row_end, col_end); } inline CellAddress to() const { return CellAddress(row_end, col_end); }
/** Start of range as a string */ /** 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 */ /** 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 */ /** 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 */ /** The raneg as a string */
inline std::string rangeString() const { 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); } CellAddress operator*() const { return CellAddress(row_curr, col_curr); }

View File

@ -110,9 +110,15 @@ void Sheet::clearAll()
props.removeDynamicProperty((*i).c_str()); props.removeDynamicProperty((*i).c_str());
propAddress.clear(); propAddress.clear();
cellErrors.clear();
columnWidths.clear();
rowHeights.clear();
removedAliases.clear();
docDeps.setValues(std::vector<DocumentObject*>());
for (ObserverMap::iterator i = observers.begin(); i != observers.end(); ++i) for (ObserverMap::iterator i = observers.begin(); i != observers.end(); ++i)
delete i->second; delete i->second;
observers.clear();
} }
/** /**
@ -315,19 +321,6 @@ Cell *Sheet::getNewCell(CellAddress address)
return cell; 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. * Set cell given by \a address to \a contents.
* *
@ -400,7 +393,7 @@ PyObject *Sheet::getPyObject(void)
Property * Sheet::getProperty(CellAddress key) const 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 Property * Sheet::getProperty(const char * addr) const
@ -442,6 +435,22 @@ void Sheet::setPosition(CellAddress address)
currColumn.purgeTouched(); currColumn.purgeTouched();
} }
void Sheet::removeAliases()
{
std::map<CellAddress, std::string>::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. * 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. * 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 * Sheet::setFloatProperty(CellAddress key, double value)
{ {
Property * prop = props.getPropertyByName(toAddress(key).c_str()); Property * prop = props.getPropertyByName(key.toString().c_str());
PropertyFloat * floatProp; PropertyFloat * floatProp;
if (!prop || prop->getTypeId() != PropertyFloat::getClassTypeId()) { if (!prop || prop->getTypeId() != PropertyFloat::getClassTypeId()) {
if (prop) { if (prop) {
props.removeDynamicProperty(toAddress(key).c_str()); props.removeDynamicProperty(key.toString().c_str());
propAddress.erase(prop); propAddress.erase(prop);
std::map<CellAddress, App::Property*>::iterator i = aliasProp.find(key);
if (i != aliasProp.end()) {
props.removeDynamicProperty(props.getPropertyName(i->second));
aliasProp.erase(i);
}
} }
floatProp = freecad_dynamic_cast<PropertyFloat>(props.addDynamicProperty("App::PropertyFloat", toAddress(key).c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true)); floatProp = freecad_dynamic_cast<PropertyFloat>(props.addDynamicProperty("App::PropertyFloat", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true));
floatProp->StatusBits.set(3); floatProp->StatusBits.set(3);
} }
else else
@ -491,26 +494,20 @@ Property * Sheet::setFloatProperty(CellAddress key, double value)
Property * Sheet::setQuantityProperty(CellAddress key, double value, const Base::Unit & unit) Property * Sheet::setQuantityProperty(CellAddress key, double value, const Base::Unit & unit)
{ {
Property * prop = props.getPropertyByName(toAddress(key).c_str()); Property * prop = props.getPropertyByName(key.toString().c_str());
PropertyQuantity * quantityProp; PropertySpreadsheetQuantity * quantityProp;
if (!prop || prop->getTypeId() != PropertyQuantity::getClassTypeId()) { if (!prop || prop->getTypeId() != PropertySpreadsheetQuantity::getClassTypeId()) {
if (prop) { if (prop) {
props.removeDynamicProperty(toAddress(key).c_str()); props.removeDynamicProperty(key.toString().c_str());
propAddress.erase(prop); propAddress.erase(prop);
std::map<CellAddress, App::Property*>::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); Property * p = props.addDynamicProperty("Spreadsheet::PropertySpreadsheetQuantity", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true);
quantityProp = freecad_dynamic_cast<PropertyQuantity>(p); quantityProp = freecad_dynamic_cast<PropertySpreadsheetQuantity>(p);
quantityProp->StatusBits.set(3); quantityProp->StatusBits.set(3);
} }
else else
quantityProp = static_cast<PropertyQuantity*>(prop); quantityProp = static_cast<PropertySpreadsheetQuantity*>(prop);
propAddress[quantityProp] = key; propAddress[quantityProp] = key;
quantityProp->setValue(value); 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 * 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<PropertyString>(prop); PropertyString * stringProp = freecad_dynamic_cast<PropertyString>(prop);
if (!stringProp) { if (!stringProp) {
if (prop) { if (prop) {
props.removeDynamicProperty(toAddress(key).c_str()); props.removeDynamicProperty(key.toString().c_str());
propAddress.erase(prop); propAddress.erase(prop);
std::map<CellAddress, App::Property*>::iterator i = aliasProp.find(key);
if (i != aliasProp.end()) {
props.removeDynamicProperty(props.getPropertyName(i->second));
aliasProp.erase(i);
}
} }
stringProp = freecad_dynamic_cast<PropertyString>(props.addDynamicProperty("App::PropertyString", toAddress(key).c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true)); stringProp = freecad_dynamic_cast<PropertyString>(props.addDynamicProperty("App::PropertyString", key.toString().c_str(), 0, 0, Prop_ReadOnly | Prop_Transient, true, true));
stringProp->StatusBits.set(3); stringProp->StatusBits.set(3);
} }
@ -556,6 +547,35 @@ Property * Sheet::setStringProperty(CellAddress key, const std::string & value)
return stringProp; 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. * 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) void Sheet::updateProperty(CellAddress key)
{ {
const Property * prop; const Property * prop;
const char * aliasType = 0;
Cell * cell = getCell(key); Cell * cell = getCell(key);
std::string alias;
if (cell != 0) { if (cell != 0) {
Expression * output; Expression * output;
@ -590,40 +608,19 @@ void Sheet::updateProperty(CellAddress key)
/* Eval returns either NumberExpression or StringExpression objects */ /* Eval returns either NumberExpression or StringExpression objects */
if (freecad_dynamic_cast<NumberExpression>(output)) { if (freecad_dynamic_cast<NumberExpression>(output)) {
NumberExpression * number = static_cast<NumberExpression*>(output); NumberExpression * number = static_cast<NumberExpression*>(output);
if (number->getUnit().isEmpty()) { if (number->getUnit().isEmpty())
prop = setFloatProperty(key, number->getValue()); prop = setFloatProperty(key, number->getValue());
aliasType = "App::PropertyFloat"; else
}
else {
prop = setQuantityProperty(key, number->getValue(), number->getUnit()); prop = setQuantityProperty(key, number->getValue(), number->getUnit());
aliasType = "App::PropertyQuantity";
}
} }
else { else
prop = setStringProperty(key, freecad_dynamic_cast<StringExpression>(output)->getText().c_str()); prop = setStringProperty(key, freecad_dynamic_cast<StringExpression>(output)->getText().c_str());
aliasType = "App::PropertyString";
}
delete output; delete output;
} }
else else
clear(key); 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<CellAddress, App::Property*>::iterator i = aliasProp.find(key);
if (i != aliasProp.end()) {
props.removeDynamicProperty(props.getPropertyName(i->second));
aliasProp.erase(i);
}
}
cellUpdated(key); cellUpdated(key);
} }
@ -661,7 +658,7 @@ void Sheet::recomputeCell(CellAddress p)
Cell * cell = cells.getValue(p); Cell * cell = cells.getValue(p);
std::string docName = getDocument()->Label.getValue(); std::string docName = getDocument()->Label.getValue();
std::string docObjName = std::string(getNameInDocument()); std::string docObjName = std::string(getNameInDocument());
std::string name = docName + "#" + docObjName + "." + toAddress(p); std::string name = docName + "#" + docObjName + "." + p.toString();
try { try {
if (cell) { if (cell) {
@ -683,6 +680,8 @@ void Sheet::recomputeCell(CellAddress p)
cellErrors.insert(p); cellErrors.insert(p);
} }
updateAlias(p);
if (!cell || cell->spansChanged()) if (!cell || cell->spansChanged())
cellSpanChanged(p); cellSpanChanged(p);
} }
@ -694,6 +693,9 @@ void Sheet::recomputeCell(CellAddress p)
App::DocumentObjectExecReturn *Sheet::execute(void) App::DocumentObjectExecReturn *Sheet::execute(void)
{ {
// Remove all aliases first
removeAliases();
// Get dirty cells that we have to recompute // Get dirty cells that we have to recompute
std::set<CellAddress> dirtyCells = cells.getDirty(); std::set<CellAddress> dirtyCells = cells.getDirty();
@ -769,6 +771,8 @@ App::DocumentObjectExecReturn *Sheet::execute(void)
if (cell) if (cell)
cell->setException("Circular dependency."); cell->setException("Circular dependency.");
updateProperty(i->first); updateProperty(i->first);
updateAlias(i->first);
++i; ++i;
} }
} }
@ -797,6 +801,10 @@ App::DocumentObjectExecReturn *Sheet::execute(void)
currColumn.purgeTouched(); currColumn.purgeTouched();
std::set<App::DocumentObject*> ds(cells.getDocDeps()); std::set<App::DocumentObject*> ds(cells.getDocDeps());
// Make sure we don't reference ourselves
ds.erase(this);
std::vector<App::DocumentObject*> dv(ds.begin(), ds.end()); std::vector<App::DocumentObject*> dv(ds.begin(), ds.end());
docDeps.setValues(dv); docDeps.setValues(dv);
@ -837,9 +845,15 @@ short Sheet::mustExecute(void) const
void Sheet::clear(CellAddress address, bool all) 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()); 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); cells.clear(address);
propAddress.erase(prop); propAddress.erase(prop);
@ -911,7 +925,7 @@ std::vector<std::string> Sheet::getUsedCells() const
std::set<CellAddress> usedSet = cells.getUsedCells(); std::set<CellAddress> usedSet = cells.getUsedCells();
for (std::set<CellAddress>::const_iterator i = usedSet.begin(); i != usedSet.end(); ++i) for (std::set<CellAddress>::const_iterator i = usedSet.begin(); i != usedSet.end(); ++i)
usedCells.push_back(toAddress(*i)); usedCells.push_back(i->toString());
return usedCells; return usedCells;
} }
@ -1031,6 +1045,11 @@ void Sheet::renamedDocumentObject(const App::DocumentObject * docObj)
cells.touch(); cells.touch();
} }
void Sheet::aliasRemoved(CellAddress address, const std::string & alias)
{
removedAliases[address] = alias;
}
std::set<std::string> Sheet::dependsOn(CellAddress address) const std::set<std::string> Sheet::dependsOn(CellAddress address) const
{ {
return cells.getDeps(address); return cells.getDeps(address);
@ -1040,18 +1059,18 @@ void Sheet::providesTo(CellAddress address, std::set<std::string> & result) cons
{ {
const char * docName = getDocument()->Label.getValue(); const char * docName = getDocument()->Label.getValue();
const char * docObjName = getNameInDocument(); 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<CellAddress> tmpResult = cells.getDeps(fullName); std::set<CellAddress> tmpResult = cells.getDeps(fullName);
for (std::set<CellAddress>::const_iterator i = tmpResult.begin(); i != tmpResult.end(); ++i) for (std::set<CellAddress>::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<CellAddress> & result) const void Sheet::providesTo(CellAddress address, std::set<CellAddress> & result) const
{ {
const char * docName = getDocument()->Label.getValue(); const char * docName = getDocument()->Label.getValue();
const char * docObjName = getNameInDocument(); 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); result = cells.getDeps(fullName);
} }
@ -1086,3 +1105,23 @@ void Sheet::observeDocument(Document * document)
observers[document->getName()] = observer; 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<const PropertySpreadsheetQuantity*>(&from)->_dValue;
_Unit = static_cast<const PropertySpreadsheetQuantity*>(&from)->_Unit;
hasSetValue();
}

View File

@ -52,10 +52,25 @@ class Expression;
class Range; class Range;
class SheetObserver; 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 class SpreadsheetExport Sheet : public App::DocumentObject
{ {
PROPERTY_HEADER(Sheet::Sheet); PROPERTY_HEADER(Spreadsheet::Sheet);
public: public:
@ -220,6 +235,8 @@ protected:
App::Property *getProperty(const char * addr) const; App::Property *getProperty(const char * addr) const;
void updateAlias(CellAddress key);
void updateProperty(CellAddress key); void updateProperty(CellAddress key);
App::Property *setStringProperty(CellAddress key, const std::string & value) ; App::Property *setStringProperty(CellAddress key, const std::string & value) ;
@ -228,20 +245,24 @@ protected:
App::Property *setQuantityProperty(CellAddress key, double value, const Base::Unit &unit); App::Property *setQuantityProperty(CellAddress key, double value, const Base::Unit &unit);
static std::string toAddress(CellAddress key);
void moveCell(CellAddress currPos, CellAddress newPos); void moveCell(CellAddress currPos, CellAddress newPos);
void renamedDocumentObject(const App::DocumentObject * docObj); void renamedDocumentObject(const App::DocumentObject * docObj);
void aliasRemoved(CellAddress address, const std::string &alias);
void removeAliases();
virtual void onSettingDocument();
/* Properties for used cells */ /* Properties for used cells */
App::DynamicProperty props; App::DynamicProperty props;
/* Mapping of properties to cell position */ /* Mapping of properties to cell position */
std::map<const App::Property*, CellAddress > propAddress; std::map<const App::Property*, CellAddress > propAddress;
/* Mapping of cell position to alias property */ /* Removed (unprocessed) aliases */
std::map<CellAddress, App::Property*> aliasProp; std::map<CellAddress, std::string> removedAliases;
/* Set of cells with errors */ /* Set of cells with errors */
std::set<CellAddress> cellErrors; std::set<CellAddress> cellErrors;

View File

@ -161,33 +161,6 @@ Spreadsheet::CellAddress Spreadsheet::stringToAddress(const char * strAddress)
throw Base::Exception("Invalid cell specifier."); 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<std::pair<int, int> > & cells, std::map<std::pair<int, int>, std::pair<int, int> > & rectangles) void Spreadsheet::createRectangles(std::set<std::pair<int, int> > & cells, std::map<std::pair<int, int>, std::pair<int, int> > & rectangles)
{ {
while (cells.size() != 0) { while (cells.size() != 0) {
@ -342,3 +315,28 @@ std::string Spreadsheet::unquote(const std::string & input)
return output; 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();
}

View File

@ -42,12 +42,9 @@ SpreadsheetExport void createRectangles(std::set<std::pair<int, int> > & cells,
SpreadsheetExport std::string quote(const std::string &input); SpreadsheetExport std::string quote(const std::string &input);
SpreadsheetExport std::string unquote(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(int row = -1, int col = -1) : _row(row), _col(col) { }
CellAddress(unsigned int _value) : value(_value) { }
CellAddress(int row = -1, int col = -1) : value(((row & 0xffff) << 16) | (col & 0xffff)) { }
CellAddress(const char * address) { CellAddress(const char * address) {
*this = stringToAddress(address); *this = stringToAddress(address);
@ -57,18 +54,20 @@ struct CellAddress {
*this = stringToAddress(address.c_str()); *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); } inline bool isValid() { return (row() >=0 && row() < MAX_ROWS && col() >= 0 && col() < MAX_COLUMNS); }
std::string toString() const;
// Static members // Static members
static const int MAX_ROWS; static const int MAX_ROWS;
@ -76,7 +75,11 @@ struct CellAddress {
static const int MAX_COLUMNS; static const int MAX_COLUMNS;
protected: protected:
unsigned int value;
inline unsigned int asInt() const { return ((_row << 16) | _col); }
short _row;
short _col;
}; };
template<typename T> T * freecad_dynamic_cast(Base::BaseClass * t) template<typename T> T * freecad_dynamic_cast(Base::BaseClass * t)

View File

@ -125,7 +125,7 @@ void CmdSpreadsheetSplitCell::activated(int iMsg)
QModelIndex current = sheetView->currentIndex(); QModelIndex current = sheetView->currentIndex();
if (current.isValid()) { 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::openCommand("Split cell");
Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.splitCell('%s')", sheet->getNameInDocument(), Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.splitCell('%s')", sheet->getNameInDocument(),
address.c_str()); address.c_str());

View File

@ -147,10 +147,10 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const
if (role == Qt::ToolTipRole) { if (role == Qt::ToolTipRole) {
QString v; QString v;
std::set<std::string> deps = sheet->dependsOn(row, col); std::set<std::string> deps = sheet->dependsOn(CellAddress(row, col));
std::set<std::string> provides; std::set<std::string> provides;
sheet->providesTo(row, col, provides); sheet->providesTo(CellAddress(row, col), provides);
if (deps.size() > 0) { if (deps.size() > 0) {
v += QString::fromUtf8("Depends on:"); v += QString::fromUtf8("Depends on:");
@ -196,7 +196,7 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const
} }
// Get display value as computed property // 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()); Property * prop = sheet->getPropertyByName(address.c_str());
if (prop == 0) if (prop == 0)
@ -394,8 +394,8 @@ bool SheetModel::setData(const QModelIndex & index, const QVariant & value, int
CellAddress address(index.row(), index.column()); CellAddress address(index.row(), index.column());
try { try {
std::string strAddress = addressToString(address); std::string strAddress = address.toString();
std::string next_address = addressToString(CellAddress(address.row() + 1, address.col())); std::string next_address = CellAddress(address.row() + 1, address.col()).toString();
QString str = value.toString(); QString str = value.toString();
std::string content; std::string content;
Cell * cell = sheet->getCell(address); Cell * cell = sheet->getCell(address);

View File

@ -158,7 +158,7 @@ bool ViewProviderSheet::onDelete(const std::vector<std::string> &)
if (selection.size() > 0) { if (selection.size() > 0) {
Gui::Command::openCommand("Clear cell(s)"); Gui::Command::openCommand("Clear cell(s)");
for (QModelIndexList::const_iterator it = selection.begin(); it != selection.end(); ++it) { 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(), Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.clear('%s')", sheet->getNameInDocument(),
address.c_str()); address.c_str());
} }