Cell binding allows one to bind a range of cells of one sheet to another
range of cells of an arbitary sheet, including any empty cells in the
range.
The binding is implemented with PropertyExpressionEngine and
PropertySheet::setPathValue(), which binds a special path of
PropertySheet, such as
.cells.Bind.A1.D1
to an expression, such as
tuple(.cells, <<A2>>, <<A5>>)
The A1 and D1 in the example above specifies the binding start and end
cell address. And <<A2>> and <<A5>> are the range of cells to bind to.
Note that you can use any expression that evalutes to string for the
binding destination, e.g. <<A%d>> % B1, which uses the value inside B1
to construct the binding destination. The '.cells' in the tuple shown
above is an example to bind cells of the same PropertySheet. It can be
change to to reference to any other spreadsheet, even those outside the
current document, e.g. Document#Spreadsheet001.cells
1883 lines
58 KiB
C++
1883 lines
58 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2015 Eivind Kvedalen <eivind@kvedalen.name> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "PreCompiled.h"
|
|
|
|
#ifndef _PreComp_
|
|
#endif
|
|
|
|
#include <boost/range/adaptor/map.hpp>
|
|
#include <boost/range/algorithm/copy.hpp>
|
|
#include <boost/assign.hpp>
|
|
#include <boost_bind_bind.hpp>
|
|
#include <boost/regex.hpp>
|
|
#include <Base/Console.h>
|
|
#include <App/Document.h>
|
|
#include <App/DocumentObject.h>
|
|
#include <App/Property.h>
|
|
#include <Base/Writer.h>
|
|
#include <Base/Reader.h>
|
|
#include <Base/Tools.h>
|
|
#include <Base/PyObjectBase.h>
|
|
#include "PropertySheet.h"
|
|
#include "Sheet.h"
|
|
#include "Utils.h"
|
|
#include <PropertySheetPy.h>
|
|
#include <App/ExpressionVisitors.h>
|
|
#include <App/ExpressionParser.h>
|
|
|
|
FC_LOG_LEVEL_INIT("Spreadsheet", true, true)
|
|
|
|
using namespace App;
|
|
using namespace Base;
|
|
using namespace Spreadsheet;
|
|
namespace bp = boost::placeholders;
|
|
|
|
TYPESYSTEM_SOURCE(Spreadsheet::PropertySheet , App::PropertyExpressionContainer)
|
|
|
|
void PropertySheet::clear()
|
|
{
|
|
std::map<CellAddress, Cell* >::iterator i = data.begin();
|
|
|
|
/* Clear cells */
|
|
while (i != data.end()) {
|
|
delete i->second;
|
|
setDirty(i->first);
|
|
++i;
|
|
}
|
|
|
|
/* Clear from map */
|
|
data.clear();
|
|
|
|
mergedCells.clear();
|
|
|
|
propertyNameToCellMap.clear();
|
|
cellToPropertyNameMap.clear();
|
|
documentObjectToCellMap.clear();
|
|
cellToDocumentObjectMap.clear();
|
|
aliasProp.clear();
|
|
revAliasProp.clear();
|
|
|
|
clearDeps();
|
|
}
|
|
|
|
Cell *PropertySheet::getValue(CellAddress key)
|
|
{
|
|
std::map<CellAddress, Cell*>::const_iterator i = data.find(key);
|
|
|
|
if (i == data.end())
|
|
return 0;
|
|
else
|
|
return i->second;
|
|
}
|
|
|
|
const Cell *PropertySheet::getValue(CellAddress key) const
|
|
{
|
|
std::map<CellAddress, Cell*>::const_iterator i = data.find(key);
|
|
|
|
if (i == data.end())
|
|
return 0;
|
|
else
|
|
return i->second;
|
|
}
|
|
|
|
|
|
const Cell * PropertySheet::getValueFromAlias(const std::string &alias) const
|
|
{
|
|
std::map<std::string, CellAddress>::const_iterator it = revAliasProp.find(alias);
|
|
|
|
if (it != revAliasProp.end())
|
|
return getValue(it->second);
|
|
else
|
|
return 0;
|
|
|
|
}
|
|
|
|
bool PropertySheet::isValidAlias(const std::string &candidate)
|
|
{
|
|
static const boost::regex gen("^[A-Za-z][_A-Za-z0-9]*$");
|
|
boost::cmatch cm;
|
|
|
|
/* Check if it is used before */
|
|
if (getValueFromAlias(candidate) != 0)
|
|
return false;
|
|
|
|
/* Check to make sure it doesn't clash with a predefined unit */
|
|
if (ExpressionParser::isTokenAUnit(candidate))
|
|
return false;
|
|
|
|
/* Check to make sure it doesn't match a cell reference */
|
|
if (boost::regex_match(candidate.c_str(), cm, gen)) {
|
|
static const boost::regex e("\\${0,1}([A-Z]{1,2})\\${0,1}([0-9]{1,5})");
|
|
|
|
if (boost::regex_match(candidate.c_str(), cm, e)) {
|
|
const boost::sub_match<const char *> colstr = cm[1];
|
|
const boost::sub_match<const char *> rowstr = cm[2];
|
|
|
|
// A valid cell address?
|
|
if (App::validRow(rowstr.str()) >= 0 && App::validColumn(colstr.str()) >= 0)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
std::set<CellAddress> PropertySheet::getUsedCells() const
|
|
{
|
|
std::set<CellAddress> usedSet;
|
|
|
|
for (std::map<CellAddress, Cell*>::const_iterator i = data.begin(); i != data.end(); ++i) {
|
|
if (i->second->isUsed())
|
|
usedSet.insert(i->first);
|
|
}
|
|
|
|
return usedSet;
|
|
}
|
|
|
|
void PropertySheet::setDirty(CellAddress address)
|
|
{
|
|
/* Merged cells will automatically force an update of the top left cell
|
|
to be consistent. */
|
|
std::map<CellAddress, CellAddress>::const_iterator i = mergedCells.find(address);
|
|
if (i != mergedCells.end())
|
|
address = i->second;
|
|
|
|
dirty.insert(address);
|
|
}
|
|
|
|
void PropertySheet::setDirty()
|
|
{
|
|
AtomicPropertyChange signaller(*this);
|
|
for(auto &address : 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);
|
|
|
|
data[address] = cell;
|
|
|
|
return cell;
|
|
}
|
|
|
|
PropertySheet::PropertySheet(Sheet *_owner)
|
|
: owner(_owner)
|
|
, updateCount(0)
|
|
{
|
|
}
|
|
|
|
PropertySheet::PropertySheet(const PropertySheet &other)
|
|
: dirty(other.dirty)
|
|
, mergedCells(other.mergedCells)
|
|
, owner(other.owner)
|
|
, propertyNameToCellMap(other.propertyNameToCellMap)
|
|
, cellToPropertyNameMap(other.cellToPropertyNameMap)
|
|
, documentObjectToCellMap(other.documentObjectToCellMap)
|
|
, cellToDocumentObjectMap(other.cellToDocumentObjectMap)
|
|
, aliasProp(other.aliasProp)
|
|
, revAliasProp(other.revAliasProp)
|
|
, updateCount(other.updateCount)
|
|
{
|
|
std::map<CellAddress, Cell* >::const_iterator i = other.data.begin();
|
|
|
|
/* Copy cells */
|
|
while (i != other.data.end()) {
|
|
data[i->first] = new Cell(this, *i->second);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
PropertySheet::~PropertySheet()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
App::Property *PropertySheet::Copy(void) const
|
|
{
|
|
return new PropertySheet(*this);
|
|
}
|
|
|
|
void PropertySheet::Paste(const Property &from)
|
|
{
|
|
const PropertySheet &froms = dynamic_cast<const PropertySheet&>(from);
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
std::map<CellAddress, Cell* >::iterator icurr = data.begin();
|
|
|
|
/* Mark all first */
|
|
while (icurr != data.end()) {
|
|
icurr->second->mark();
|
|
++icurr;
|
|
}
|
|
|
|
std::map<CellAddress, Cell* >::const_iterator ifrom = froms.data.begin();
|
|
while (ifrom != froms.data.end()) {
|
|
std::map<CellAddress, Cell* >::iterator i = data.find(ifrom->first);
|
|
|
|
if (i != data.end()) {
|
|
*(data[ifrom->first]) = *(ifrom->second); // Exists; assign cell directly
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* Remove all that are still marked */
|
|
icurr = data.begin();
|
|
while (icurr != data.end()) {
|
|
Cell * cell = icurr->second;
|
|
|
|
if (cell->isMarked()) {
|
|
std::map<CellAddress, Cell* >::iterator next = icurr;
|
|
|
|
++next;
|
|
clear(icurr->first);
|
|
icurr = next;
|
|
}
|
|
else
|
|
++icurr;
|
|
}
|
|
|
|
mergedCells = froms.mergedCells;
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
void PropertySheet::Save(Base::Writer &writer) const
|
|
{
|
|
// Save cell contents
|
|
int count = 0;
|
|
|
|
std::map<CellAddress, Cell*>::const_iterator ci = data.begin();
|
|
while (ci != data.end()) {
|
|
if (ci->second->isUsed())
|
|
++count;
|
|
++ci;
|
|
}
|
|
|
|
writer.Stream() << writer.ind() << "<Cells Count=\"" << count
|
|
<< "\" xlink=\"1\">" << std::endl;
|
|
|
|
writer.incInd();
|
|
|
|
PropertyExpressionContainer::Save(writer);
|
|
|
|
ci = data.begin();
|
|
while (ci != data.end()) {
|
|
ci->second->save(writer);
|
|
++ci;
|
|
}
|
|
|
|
writer.decInd();
|
|
writer.Stream() << writer.ind() << "</Cells>" << std::endl;
|
|
}
|
|
|
|
void PropertySheet::Restore(Base::XMLReader &reader)
|
|
{
|
|
int Cnt;
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
reader.readElement("Cells");
|
|
Cnt = reader.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") : "";
|
|
|
|
try {
|
|
CellAddress address(strAddress);
|
|
Cell * cell = createCell(address);
|
|
|
|
cell->restore(reader);
|
|
|
|
int rows, cols;
|
|
if (cell->getSpans(rows, cols) && (rows > 1 || cols > 1)) {
|
|
mergeCells(address, CellAddress(address.row() + rows - 1, address.col() + cols - 1));
|
|
}
|
|
}
|
|
catch (const Base::Exception &) {
|
|
// Something is wrong, skip this cell
|
|
}
|
|
catch (...) {
|
|
}
|
|
}
|
|
reader.readEndElement("Cells");
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
void PropertySheet::copyCells(Base::Writer& writer, const std::vector<Range>& ranges) const {
|
|
writer.Stream() << "<?xml version='1.0' encoding='utf-8'?>" << std::endl;
|
|
writer.Stream() << "<Cells count=\"" << ranges.size() << "\">" << std::endl;
|
|
writer.incInd();
|
|
for (auto range : ranges) {
|
|
writer.Stream() << writer.ind() << "<Range from=\"" << range.fromCellString()
|
|
<< "\" to=\"" << range.toCellString() << "\" count=\"" << range.size() << "\">" << std::endl;
|
|
writer.incInd();
|
|
do {
|
|
auto cell = getValue(*range);
|
|
if (cell && cell->isUsed()) {
|
|
cell->save(writer);
|
|
}
|
|
else {
|
|
// The cell is empty, so when it's pasted it needs to clear the existing contents
|
|
writer.Stream() << writer.ind() << "<Cell "
|
|
<< "address=\"" << (*range).toString() << "\" "
|
|
<< "content = \"\" />";
|
|
}
|
|
} while (range.next());
|
|
writer.decInd();
|
|
writer.Stream() << writer.ind() << "</Range>" << std::endl;
|
|
}
|
|
writer.decInd();
|
|
writer.Stream() << "</Cells>" << std::endl;
|
|
}
|
|
|
|
void PropertySheet::pasteCells(XMLReader &reader, Range dstRange) {
|
|
reader.readElement("Cells");
|
|
int rangeCount = reader.getAttributeAsInteger("count");
|
|
if(rangeCount<=0)
|
|
return;
|
|
|
|
int dstRows = dstRange.rowCount();
|
|
int dstCols = dstRange.colCount();
|
|
CellAddress dstFrom = dstRange.from();
|
|
|
|
int roffset,coffset;
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
for(int ri=0; ri < rangeCount; ++ri) {
|
|
reader.readElement("Range");
|
|
CellAddress from(reader.getAttribute("from"));
|
|
CellAddress to(reader.getAttribute("to"));
|
|
int cellCount = reader.getAttributeAsInteger("count");
|
|
|
|
Range range(from,to);
|
|
|
|
CellAddress addr(dstFrom);
|
|
if(!ri) {
|
|
roffset = addr.row() - from.row();
|
|
coffset = addr.col() - from.col();
|
|
}
|
|
|
|
int rcount,ccount;
|
|
if(rangeCount>1) {
|
|
rcount = 1;
|
|
ccount = 1;
|
|
} else {
|
|
rcount = dstRows/range.rowCount();
|
|
if(!rcount)
|
|
rcount = 1;
|
|
ccount = dstCols/range.colCount();
|
|
if(!ccount)
|
|
ccount = 1;
|
|
}
|
|
for(int ci=0; ci < cellCount; ++ci) {
|
|
reader.readElement("Cell");
|
|
CellAddress src(reader.getAttribute("address"));
|
|
|
|
if(ci)
|
|
range.next();
|
|
|
|
while(src!=*range) {
|
|
for(int r=0; r < rcount; ++r) {
|
|
for(int c=0; c < ccount; ++c) {
|
|
CellAddress dst(range.row()+roffset+r*range.rowCount(),
|
|
range.column()+coffset+c*range.colCount());
|
|
if(!dst.isValid())
|
|
continue;
|
|
owner->clear(dst);
|
|
owner->cellUpdated(dst);
|
|
}
|
|
}
|
|
range.next();
|
|
}
|
|
|
|
CellAddress newCellAddr;
|
|
for(int r=0; r < rcount; ++r) {
|
|
for(int c=0; c < ccount; ++c) {
|
|
CellAddress dst(src.row()+roffset+r*range.rowCount(),
|
|
src.col()+coffset+c*range.colCount());
|
|
if(!dst.isValid())
|
|
continue;
|
|
|
|
auto cell = owner->getNewCell(dst);
|
|
cell->setSpans(-1,-1);
|
|
|
|
int roffset_cur, coffset_cur;
|
|
if(!newCellAddr.isValid()) {
|
|
roffset_cur = roffset;
|
|
coffset_cur = coffset;
|
|
newCellAddr = dst;
|
|
cell->restore(reader,true);
|
|
} else {
|
|
roffset_cur = r*range.rowCount();
|
|
coffset_cur = c*range.colCount();
|
|
auto newCell = owner->getCell(newCellAddr);
|
|
const Expression *expr;
|
|
if(!newCell || !(expr=newCell->getExpression(true))) {
|
|
FC_THROWM(Base::RuntimeError, "Failed to copy cell "
|
|
<< getFullName() << '.' << dst.toString()
|
|
<< " from " << newCellAddr.toString());
|
|
}
|
|
cell->setExpression(ExpressionPtr(expr->copy()));
|
|
}
|
|
|
|
int rows, cols;
|
|
if (cell->getSpans(rows, cols) && (rows > 1 || cols > 1))
|
|
mergeCells(dst, CellAddress(dst.row() + rows - 1, dst.col() + cols - 1));
|
|
|
|
if(roffset_cur || coffset_cur) {
|
|
OffsetCellsExpressionVisitor<PropertySheet> visitor(*this, roffset_cur, coffset_cur);
|
|
cell->visit(visitor);
|
|
if(visitor.changed())
|
|
recomputeDependencies(dst);
|
|
}
|
|
dirty.insert(dst);
|
|
owner->cellUpdated(dst);
|
|
}
|
|
}
|
|
}
|
|
if(!cellCount || range.next()) {
|
|
do {
|
|
for(int r=0; r < rcount; ++r) {
|
|
for(int c=0; c < ccount; ++c) {
|
|
CellAddress dst(range.row()+roffset+r*range.rowCount(),
|
|
range.column()+coffset+c*range.colCount());
|
|
if(!dst.isValid())
|
|
continue;
|
|
owner->clear(dst);
|
|
owner->cellUpdated(dst);
|
|
}
|
|
}
|
|
}while(range.next());
|
|
}
|
|
}
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
|
|
Cell * PropertySheet::cellAt(CellAddress address)
|
|
{
|
|
std::map<CellAddress, CellAddress>::const_iterator j = mergedCells.find(address);
|
|
|
|
// address actually inside a merged cell
|
|
if (j != mergedCells.end()) {
|
|
std::map<CellAddress, Cell*>::const_iterator i = data.find(j->second);
|
|
assert(i != data.end());
|
|
|
|
return i->second;
|
|
}
|
|
|
|
std::map<CellAddress, Cell*>::const_iterator i = data.find(address);
|
|
|
|
if (i == data.end())
|
|
return 0;
|
|
else
|
|
return i->second;
|
|
}
|
|
|
|
const Cell * PropertySheet::cellAt(CellAddress address) const
|
|
{
|
|
std::map<CellAddress, CellAddress>::const_iterator j = mergedCells.find(address);
|
|
|
|
// address actually inside a merged cell
|
|
if (j != mergedCells.end()) {
|
|
std::map<CellAddress, Cell*>::const_iterator i = data.find(j->second);
|
|
assert(i != data.end());
|
|
|
|
return i->second;
|
|
}
|
|
|
|
std::map<CellAddress, Cell*>::const_iterator i = data.find(address);
|
|
|
|
if (i == data.end())
|
|
return 0;
|
|
else
|
|
return i->second;
|
|
}
|
|
|
|
Cell * PropertySheet::nonNullCellAt(CellAddress address)
|
|
{
|
|
std::map<CellAddress, CellAddress>::const_iterator j = mergedCells.find(address);
|
|
|
|
if (j != mergedCells.end()) {
|
|
std::map<CellAddress, Cell*>::const_iterator i = data.find(j->second);
|
|
|
|
if (i == data.end())
|
|
return createCell(address);
|
|
else
|
|
return i->second;
|
|
}
|
|
|
|
std::map<CellAddress, Cell*>::const_iterator i = data.find(address);
|
|
|
|
if (i == data.end())
|
|
return createCell(address);
|
|
else
|
|
return i->second;
|
|
}
|
|
|
|
void PropertySheet::setContent(CellAddress address, const char *value)
|
|
{
|
|
Cell * cell = nonNullCellAt(address);
|
|
assert(cell != 0);
|
|
cell->setContent(value);
|
|
}
|
|
|
|
void PropertySheet::setAlignment(CellAddress address, int _alignment)
|
|
{
|
|
Cell * cell = nonNullCellAt(address);
|
|
assert(cell != 0);
|
|
if (cell->address != address) return; //Reject alignment change for merged cell except top-left one
|
|
cell->setAlignment(_alignment);
|
|
}
|
|
|
|
void PropertySheet::setStyle(CellAddress address, const std::set<std::string> &_style)
|
|
{
|
|
Cell * cell = nonNullCellAt(address);
|
|
assert(cell != 0);
|
|
cell->setStyle(_style);
|
|
}
|
|
|
|
void PropertySheet::setForeground(CellAddress address, const App::Color &color)
|
|
{
|
|
Cell * cell = nonNullCellAt(address);
|
|
assert(cell != 0);
|
|
cell->setForeground(color);
|
|
}
|
|
|
|
void PropertySheet::setBackground(CellAddress address, const App::Color &color)
|
|
{
|
|
Cell * cell = nonNullCellAt(address);
|
|
assert(cell != 0);
|
|
cell->setBackground(color);
|
|
}
|
|
|
|
void PropertySheet::setDisplayUnit(CellAddress address, const std::string &unit)
|
|
{
|
|
Cell * cell = nonNullCellAt(address);
|
|
assert(cell != 0);
|
|
cell->setDisplayUnit(unit);
|
|
}
|
|
|
|
|
|
void PropertySheet::setAlias(CellAddress address, const std::string &alias)
|
|
{
|
|
if (alias.size() > 0 && !isValidAlias(alias))
|
|
throw Base::ValueError("Invalid alias");
|
|
|
|
const Cell * aliasedCell = getValueFromAlias(alias);
|
|
Cell * cell = nonNullCellAt(address);
|
|
assert(cell != 0);
|
|
|
|
if (aliasedCell != 0 && cell != aliasedCell)
|
|
throw Base::ValueError("Alias already defined.");
|
|
|
|
/* Mark cells depending on this cell dirty; they need to be resolved when an alias changes or disappears */
|
|
std::string fullName = owner->getFullName() + "." + address.toString();
|
|
|
|
std::map<std::string, std::set< CellAddress > >::const_iterator j = propertyNameToCellMap.find(fullName);
|
|
if (j != propertyNameToCellMap.end()) {
|
|
std::set< CellAddress >::const_iterator k = j->second.begin();
|
|
|
|
while (k != j->second.end()) {
|
|
setDirty(*k);
|
|
++k;
|
|
}
|
|
}
|
|
|
|
std::string oldAlias;
|
|
|
|
if (cell->getAlias(oldAlias))
|
|
owner->aliasRemoved(address, oldAlias);
|
|
|
|
cell->setAlias(alias);
|
|
|
|
if (oldAlias.size() > 0 && alias.size() > 0) {
|
|
std::map<App::ObjectIdentifier, App::ObjectIdentifier> m;
|
|
|
|
App::ObjectIdentifier key(owner, oldAlias);
|
|
App::ObjectIdentifier value(owner, alias);
|
|
|
|
m[key] = value;
|
|
|
|
owner->getDocument()->renameObjectIdentifiers(m);
|
|
}
|
|
|
|
}
|
|
|
|
void PropertySheet::setComputedUnit(CellAddress address, const Base::Unit &unit)
|
|
{
|
|
Cell * cell = nonNullCellAt(address);
|
|
assert(cell != 0);
|
|
cell->setComputedUnit(unit);
|
|
}
|
|
|
|
void PropertySheet::setSpans(CellAddress address, int rows, int columns)
|
|
{
|
|
Cell * cell = nonNullCellAt(address);
|
|
assert(cell != 0);
|
|
cell->setSpans(rows, columns);
|
|
}
|
|
|
|
void PropertySheet::clearAlias(CellAddress address)
|
|
{
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
void PropertySheet::clear(CellAddress address, bool toClearAlias)
|
|
{
|
|
std::map<CellAddress, Cell*>::iterator i = data.find(address);
|
|
|
|
if (i == data.end())
|
|
return;
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
// Spit cell to clean up mergeCells map; all data is in first cell anyway
|
|
splitCell(address);
|
|
|
|
// Delete Cell object
|
|
removeDependencies(address);
|
|
delete i->second;
|
|
|
|
// Mark as dirty
|
|
dirty.insert(i->first);
|
|
|
|
if (toClearAlias)
|
|
clearAlias(address);
|
|
|
|
// Erase from internal struct
|
|
data.erase(i);
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
void PropertySheet::moveAlias(CellAddress currPos, CellAddress newPos)
|
|
{
|
|
std::map<CellAddress, std::string>::iterator j = aliasProp.find(currPos);
|
|
if (j != aliasProp.end()) {
|
|
aliasProp[newPos] = j->second;
|
|
revAliasProp[j->second] = newPos;
|
|
aliasProp.erase(currPos);
|
|
}
|
|
}
|
|
|
|
void PropertySheet::moveCell(CellAddress currPos, CellAddress newPos, std::map<App::ObjectIdentifier, App::ObjectIdentifier> & renames)
|
|
{
|
|
std::map<CellAddress, Cell*>::const_iterator i = data.find(currPos);
|
|
std::map<CellAddress, Cell*>::const_iterator j = data.find(newPos);
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
if (j != data.end()) {
|
|
// do not clear alias because we have moved them already
|
|
clear(newPos, false);
|
|
}
|
|
|
|
if (i != data.end()) {
|
|
Cell * cell = i->second;
|
|
int rows, columns;
|
|
|
|
// Get merged cell data
|
|
cell->getSpans(rows, columns);
|
|
|
|
// Remove merged cell data
|
|
splitCell(currPos);
|
|
|
|
// Remove from old
|
|
removeDependencies(currPos);
|
|
data.erase(currPos);
|
|
setDirty(currPos);
|
|
|
|
// Insert into new spot
|
|
cell->moveAbsolute(newPos);
|
|
data[newPos] = cell;
|
|
|
|
if (rows > 1 || columns > 1) {
|
|
CellAddress toPos(newPos.row() + rows - 1, newPos.col() + columns - 1);
|
|
|
|
mergeCells(newPos, toPos);
|
|
}
|
|
else
|
|
cell->setSpans(-1, -1);
|
|
|
|
addDependencies(newPos);
|
|
|
|
setDirty(newPos);
|
|
|
|
renames[ObjectIdentifier(owner, currPos.toString())] = ObjectIdentifier(owner, newPos.toString());
|
|
}
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
void PropertySheet::insertRows(int row, int count)
|
|
{
|
|
std::vector<CellAddress> keys;
|
|
std::map<App::ObjectIdentifier, App::ObjectIdentifier> renames;
|
|
|
|
/* Copy all keys from cells map */
|
|
boost::copy( data | boost::adaptors::map_keys, std::back_inserter(keys));
|
|
|
|
/* Sort them */
|
|
std::sort(keys.begin(), keys.end(), boost::bind(&PropertySheet::rowSortFunc, this, bp::_1, bp::_2));
|
|
|
|
MoveCellsExpressionVisitor<PropertySheet> visitor(*this,
|
|
CellAddress(row, CellAddress::MAX_COLUMNS), count, 0);
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
// move all the aliases first so dependencies can be calculated correctly
|
|
for (std::vector<CellAddress>::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) {
|
|
if (i->row() >= row)
|
|
moveAlias(*i, CellAddress(i->row() + count, i->col()));
|
|
}
|
|
|
|
for (std::vector<CellAddress>::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) {
|
|
std::map<CellAddress, Cell*>::iterator j = data.find(*i);
|
|
|
|
assert(j != data.end());
|
|
|
|
Cell * cell = j->second;
|
|
|
|
// Visit each cell to make changes to expressions if necessary
|
|
visitor.reset();
|
|
cell->visit(visitor);
|
|
if (visitor.changed()) {
|
|
setDirty(*i);
|
|
recomputeDependencies(*i);
|
|
}
|
|
|
|
if (i->row() >= row)
|
|
moveCell(*i, CellAddress(i->row() + count, i->col()), renames);
|
|
}
|
|
|
|
const App::DocumentObject * docObj = static_cast<const App::DocumentObject*>(getContainer());
|
|
owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject * obj) { return obj != docObj; });
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
/**
|
|
* Sort function to sort two cell positions according to their row position.
|
|
*
|
|
*/
|
|
|
|
bool PropertySheet::rowSortFunc(const CellAddress & a, const CellAddress & b) {
|
|
if (a.row() < b.row())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
std::vector<CellAddress> PropertySheet::getRows(int row, int count) const
|
|
{
|
|
std::vector<CellAddress> keys;
|
|
|
|
for (const auto &i : data) {
|
|
auto key = i.first;
|
|
if (key.row() >= row && key.row() < row + count)
|
|
keys.push_back(key);
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
void PropertySheet::removeRows(int row, int count)
|
|
{
|
|
std::vector<CellAddress> keys;
|
|
std::map<App::ObjectIdentifier, App::ObjectIdentifier> renames;
|
|
|
|
/* Copy all keys from cells map */
|
|
boost::copy(data | boost::adaptors::map_keys, std::back_inserter(keys));
|
|
|
|
/* Sort them */
|
|
std::sort(keys.begin(), keys.end(), boost::bind(&PropertySheet::rowSortFunc, this, bp::_1, bp::_2));
|
|
|
|
MoveCellsExpressionVisitor<PropertySheet> visitor(*this,
|
|
CellAddress(row + count - 1, CellAddress::MAX_COLUMNS), -count, 0);
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
// move all the aliases first so dependencies can be calculated correctly
|
|
for (std::vector<CellAddress>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
|
|
if (i->row() >= row && i->row() < row + count)
|
|
clearAlias(*i);
|
|
else if (i->row() >= row + count)
|
|
moveAlias(*i, CellAddress(i->row() - count, i->col()));
|
|
}
|
|
|
|
for (std::vector<CellAddress>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
|
|
std::map<CellAddress, Cell*>::iterator j = data.find(*i);
|
|
|
|
assert(j != data.end());
|
|
|
|
Cell * cell = j->second;
|
|
|
|
// Visit each cell to make changes to expressions if necessary
|
|
visitor.reset();
|
|
cell->visit(visitor);
|
|
if (visitor.changed()) {
|
|
setDirty(*i);
|
|
recomputeDependencies(*i);
|
|
}
|
|
|
|
if (i->row() >= row && i->row() < row + count)
|
|
clear(*i, false); // aliases were cleared earlier
|
|
else if (i->row() >= row + count)
|
|
moveCell(*i, CellAddress(i->row() - count, i->col()), renames);
|
|
}
|
|
|
|
const App::DocumentObject * docObj = static_cast<const App::DocumentObject*>(getContainer());
|
|
owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject * obj) { return obj != docObj; });
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
void PropertySheet::insertColumns(int col, int count)
|
|
{
|
|
std::vector<CellAddress> keys;
|
|
std::map<App::ObjectIdentifier, App::ObjectIdentifier> renames;
|
|
|
|
/* Copy all keys from cells map */
|
|
boost::copy(data | boost::adaptors::map_keys, std::back_inserter(keys));
|
|
|
|
/* Sort them */
|
|
std::sort(keys.begin(), keys.end());
|
|
|
|
MoveCellsExpressionVisitor<PropertySheet> visitor(*this,
|
|
CellAddress(CellAddress::MAX_ROWS, col), 0, count);
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
// move all the aliases first so dependencies can be calculated correctly
|
|
for (std::vector<CellAddress>::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) {
|
|
if (i->col() >= col)
|
|
moveAlias(*i, CellAddress(i->row(), i->col() + count));
|
|
}
|
|
|
|
for (std::vector<CellAddress>::const_reverse_iterator i = keys.rbegin(); i != keys.rend(); ++i) {
|
|
std::map<CellAddress, Cell*>::iterator j = data.find(*i);
|
|
|
|
assert(j != data.end());
|
|
|
|
Cell * cell = j->second;
|
|
|
|
// Visit each cell to make changes to expressions if necessary
|
|
visitor.reset();
|
|
cell->visit(visitor);
|
|
if (visitor.changed()) {
|
|
setDirty(*i);
|
|
recomputeDependencies(*i);
|
|
}
|
|
|
|
if (i->col() >= col)
|
|
moveCell(*i, CellAddress(i->row(), i->col() + count), renames);
|
|
}
|
|
|
|
const App::DocumentObject * docObj = static_cast<const App::DocumentObject*>(getContainer());
|
|
owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject * obj) { return obj != docObj; });
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
/**
|
|
* Sort function to sort two cell positions according to their column position.
|
|
*
|
|
*/
|
|
|
|
bool PropertySheet::colSortFunc(const CellAddress & a, const CellAddress & b) {
|
|
if (a.col() < b.col())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
std::vector<CellAddress> PropertySheet::getColumns(int column, int count) const
|
|
{
|
|
std::vector<CellAddress> keys;
|
|
|
|
for (const auto &i : data) {
|
|
auto key = i.first;
|
|
if (key.col() >= column && key.col() < column + count)
|
|
keys.push_back(key);
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
void PropertySheet::removeColumns(int col, int count)
|
|
{
|
|
std::vector<CellAddress> keys;
|
|
std::map<App::ObjectIdentifier, App::ObjectIdentifier> renames;
|
|
|
|
/* Copy all keys from cells map */
|
|
boost::copy(data | boost::adaptors::map_keys, std::back_inserter(keys));
|
|
|
|
/* Sort them */
|
|
std::sort(keys.begin(), keys.end(), boost::bind(&PropertySheet::colSortFunc, this, bp::_1, bp::_2));
|
|
|
|
MoveCellsExpressionVisitor<PropertySheet> visitor(*this,
|
|
CellAddress(CellAddress::MAX_ROWS, col + count - 1), 0, -count);
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
// move all the aliases first so dependencies can be calculated correctly
|
|
for (std::vector<CellAddress>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
|
|
if (i->col() >= col && i->col() < col + count)
|
|
clearAlias(*i);
|
|
else if (i->col() >= col + count)
|
|
moveAlias(*i, CellAddress(i->row(), i->col() - count));
|
|
}
|
|
|
|
for (std::vector<CellAddress>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
|
|
std::map<CellAddress, Cell*>::iterator j = data.find(*i);
|
|
|
|
assert(j != data.end());
|
|
|
|
Cell * cell = j->second;
|
|
|
|
// Visit each cell to make changes to expressions if necessary
|
|
visitor.reset();
|
|
cell->visit(visitor);
|
|
if (visitor.changed()) {
|
|
setDirty(*i);
|
|
recomputeDependencies(*i);
|
|
}
|
|
|
|
if (i->col() >= col && i->col() < col + count)
|
|
clear(*i, false); // aliases were cleared earlier
|
|
else if (i->col() >= col + count)
|
|
moveCell(*i, CellAddress(i->row(), i->col() - count), renames);
|
|
}
|
|
|
|
const App::DocumentObject * docObj = static_cast<const App::DocumentObject*>(getContainer());
|
|
owner->getDocument()->renameObjectIdentifiers(renames, [docObj](const App::DocumentObject * obj) { return obj != docObj; } );
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
unsigned int PropertySheet::getMemSize() const
|
|
{
|
|
return sizeof(*this);
|
|
}
|
|
|
|
|
|
bool PropertySheet::mergeCells(CellAddress from, CellAddress to)
|
|
{
|
|
// Check that this merge is not overlapping other merged cells
|
|
for (int r = from.row(); r <= to.row(); ++r) {
|
|
for (int c = from.col(); c <= to.col(); ++c) {
|
|
if (mergedCells.find(CellAddress(r, c)) != mergedCells.end())
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
// Clear cells that will be hidden by the merge
|
|
for (int r = from.row(); r <= to.row(); ++r)
|
|
for (int c = from.col(); c <= to.col(); ++c)
|
|
if ( !(r == from.row() && c == from.col()) )
|
|
clear(CellAddress(r, c));
|
|
|
|
// Update internal structure to track merged cells
|
|
for (int r = from.row(); r <= to.row(); ++r)
|
|
for (int c = from.col(); c <= to.col(); ++c) {
|
|
mergedCells[CellAddress(r, c)] = from;
|
|
setDirty(CellAddress(r, c));
|
|
}
|
|
|
|
setSpans(from, to.row() - from.row() + 1, to.col() - from.col() + 1);
|
|
signaller.tryInvoke();
|
|
|
|
return true;
|
|
}
|
|
|
|
void PropertySheet::splitCell(CellAddress address)
|
|
{
|
|
int rows, cols;
|
|
std::map<CellAddress, CellAddress>::const_iterator i = mergedCells.find(address);
|
|
|
|
if (i == mergedCells.end())
|
|
return;
|
|
|
|
CellAddress anchor = i->second;
|
|
AtomicPropertyChange signaller(*this);
|
|
cellAt(anchor)->getSpans(rows, cols);
|
|
|
|
for (int r = anchor.row(); r <= anchor.row() + rows; ++r)
|
|
for (int c = anchor.col(); c <= anchor.col() + cols; ++c) {
|
|
setDirty(CellAddress(r, c));
|
|
mergedCells.erase(CellAddress(r, c));
|
|
}
|
|
|
|
setSpans(anchor, -1, -1);
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
void PropertySheet::getSpans(CellAddress address, int & rows, int & cols) const
|
|
{
|
|
std::map<CellAddress, CellAddress>::const_iterator i = mergedCells.find(address);
|
|
|
|
if (i != mergedCells.end()) {
|
|
CellAddress anchor = i->second;
|
|
|
|
if (anchor == address)
|
|
cellAt(anchor)->getSpans(rows, cols);
|
|
else
|
|
rows = cols = 1;
|
|
}
|
|
else {
|
|
rows = cols = 1;
|
|
}
|
|
}
|
|
|
|
App::CellAddress Spreadsheet::PropertySheet::getAnchor(App::CellAddress address) const
|
|
{
|
|
if (auto anchor = mergedCells.find(address); anchor != mergedCells.end())
|
|
return anchor->second;
|
|
else
|
|
return address;
|
|
}
|
|
|
|
bool PropertySheet::isMergedCell(CellAddress address) const
|
|
{
|
|
return mergedCells.find(address) != mergedCells.end();
|
|
}
|
|
|
|
bool PropertySheet::isHidden(CellAddress address) const
|
|
{
|
|
std::map<CellAddress, CellAddress>::const_iterator i = mergedCells.find(address);
|
|
|
|
return i != mergedCells.end() && i->second != address;
|
|
}
|
|
|
|
/**
|
|
* Update dependencies of \a expression for cell at \a key.
|
|
*
|
|
* @param expression Expression to extract dependencies from
|
|
* @param key Address of cell containing the expression.
|
|
*/
|
|
|
|
void PropertySheet::addDependencies(CellAddress key)
|
|
{
|
|
Cell * cell = getValue(key);
|
|
|
|
if (!cell)
|
|
return;
|
|
|
|
cell->clearResolveException();
|
|
|
|
const Expression * expression = cell->getExpression();
|
|
|
|
if (expression == 0)
|
|
return;
|
|
|
|
for(auto &var : expression->getIdentifiers()) {
|
|
for(auto &dep : var.first.getDep(true)) {
|
|
App::DocumentObject *docObj = dep.first;
|
|
App::Document *doc = docObj->getDocument();
|
|
|
|
std::string docObjName = docObj->getFullName();
|
|
|
|
owner->observeDocument(doc);
|
|
|
|
documentObjectToCellMap[docObjName].insert(key);
|
|
cellToDocumentObjectMap[key].insert(docObjName);
|
|
++updateCount;
|
|
|
|
for(auto &name : dep.second) {
|
|
std::string propName = docObjName + "." + name;
|
|
FC_LOG("dep " << key.toString() << " -> " << name);
|
|
|
|
// Insert into maps
|
|
propertyNameToCellMap[propName].insert(key);
|
|
cellToPropertyNameMap[key].insert(propName);
|
|
|
|
// Also an alias?
|
|
if (docObj==owner && name.size()) {
|
|
auto j = revAliasProp.find(name);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove dependencies given by \a expression for cell at \a key.
|
|
*
|
|
* @param expression Expression to extract dependencies from
|
|
* @param key Address of cell containing the expression
|
|
*
|
|
*/
|
|
|
|
void PropertySheet::removeDependencies(CellAddress key)
|
|
{
|
|
/* Remove from Property <-> Key maps */
|
|
|
|
std::map<CellAddress, std::set< std::string > >::iterator i1 = cellToPropertyNameMap.find(key);
|
|
|
|
if (i1 != cellToPropertyNameMap.end()) {
|
|
std::set< std::string >::const_iterator j = i1->second.begin();
|
|
|
|
while (j != i1->second.end()) {
|
|
std::map<std::string, std::set< CellAddress > >::iterator k = propertyNameToCellMap.find(*j);
|
|
|
|
//assert(k != propertyNameToCellMap.end());
|
|
if (k != propertyNameToCellMap.end())
|
|
k->second.erase(key);
|
|
++j;
|
|
}
|
|
|
|
cellToPropertyNameMap.erase(i1);
|
|
}
|
|
|
|
/* Remove from DocumentObject <-> Key maps */
|
|
|
|
std::map<CellAddress, std::set< std::string > >::iterator i2 = cellToDocumentObjectMap.find(key);
|
|
|
|
if (i2 != cellToDocumentObjectMap.end()) {
|
|
std::set< std::string >::const_iterator j = i2->second.begin();
|
|
|
|
while (j != i2->second.end()) {
|
|
std::map<std::string, std::set< CellAddress > >::iterator k = documentObjectToCellMap.find(*j);
|
|
|
|
//assert(k != documentObjectToCellMap.end());
|
|
if (k != documentObjectToCellMap.end()) {
|
|
k->second.erase(key);
|
|
|
|
if (k->second.size() == 0)
|
|
documentObjectToCellMap.erase(*j);
|
|
}
|
|
|
|
++j;
|
|
}
|
|
|
|
cellToDocumentObjectMap.erase(i2);
|
|
++updateCount;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recompute any cells that depend on \a prop.
|
|
*
|
|
*/
|
|
|
|
void PropertySheet::recomputeDependants(const App::DocumentObject *owner, const char *propName)
|
|
{
|
|
auto itD = _Deps.find(const_cast<App::DocumentObject*>(owner));
|
|
if(itD!=_Deps.end() && itD->second) {
|
|
// Check for hidden reference. Because a hidden reference is not
|
|
// protected by cyclic dependency checking, we need to take special
|
|
// care to prevent it from misbehave.
|
|
Sheet *sheet = Base::freecad_dynamic_cast<Sheet>(getContainer());
|
|
if(!sheet || sheet->testStatus(App::ObjectStatus::Recompute2)
|
|
|| !owner || owner->testStatus(App::ObjectStatus::Recompute2))
|
|
return;
|
|
}
|
|
|
|
// First, search without actual property name for sub-object/link
|
|
// references, i.e indirect references. The dependencies 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 (propName && *propName) {
|
|
// Now, we check for direct property references
|
|
it = propertyNameToCellMap.find(fullName + propName);
|
|
if (it != propertyNameToCellMap.end()) {
|
|
for(auto &cell : it->second)
|
|
setDirty(cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertySheet::breakLink(App::DocumentObject *obj, bool clear) {
|
|
AtomicPropertyChange signaller(*this,false);
|
|
PropertyExpressionContainer::breakLink(obj,clear);
|
|
}
|
|
|
|
void PropertySheet::onBreakLink(App::DocumentObject *obj) {
|
|
invalidateDependants(obj);
|
|
}
|
|
|
|
void PropertySheet::hasSetChildValue(App::Property &prop) {
|
|
++updateCount;
|
|
PropertyExpressionContainer::hasSetChildValue(prop);
|
|
}
|
|
|
|
void PropertySheet::invalidateDependants(const App::DocumentObject *docObj)
|
|
{
|
|
depConnections.erase(docObj);
|
|
|
|
// Recompute cells that depend on this cell
|
|
auto iter = documentObjectToCellMap.find(docObj->getFullName());
|
|
if (iter == documentObjectToCellMap.end())
|
|
return;
|
|
|
|
// Touch to force recompute
|
|
touch();
|
|
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
for(const auto &address : iter->second) {
|
|
Cell * cell = getValue(address);
|
|
cell->setResolveException("Unresolved dependency");
|
|
setDirty(address);
|
|
}
|
|
}
|
|
|
|
void PropertySheet::slotChangedObject(const App::DocumentObject &obj, const App::Property &prop) {
|
|
recomputeDependants(&obj, prop.getName());
|
|
}
|
|
|
|
void PropertySheet::onAddDep(App::DocumentObject *obj) {
|
|
depConnections[obj] = obj->signalChanged.connect(boost::bind(
|
|
&PropertySheet::slotChangedObject, this, bp::_1, bp::_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;
|
|
|
|
std::map<CellAddress, Cell* >::iterator i = data.begin();
|
|
|
|
while (i != data.end()) {
|
|
RelabelDocumentObjectExpressionVisitor<PropertySheet> v(*this, docObj);
|
|
i->second->visit(v);
|
|
if(v.changed()) {
|
|
v.reset();
|
|
recomputeDependencies(i->first);
|
|
setDirty(i->first);
|
|
}
|
|
++i;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void PropertySheet::onRelabeledDocument(const App::Document &doc)
|
|
{
|
|
RelabelDocumentExpressionVisitor v(doc);
|
|
for(auto &c : data)
|
|
c.second->visit(v);
|
|
}
|
|
|
|
void PropertySheet::renameObjectIdentifiers(const std::map<App::ObjectIdentifier, App::ObjectIdentifier> &paths)
|
|
{
|
|
RenameObjectIdentifierExpressionVisitor<PropertySheet> v(*this, paths, *this);
|
|
for(auto &c : data) {
|
|
c.second->visit(v);
|
|
if(v.changed()) {
|
|
v.reset();
|
|
recomputeDependencies(c.first);
|
|
setDirty(c.first);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertySheet::deletedDocumentObject(const App::DocumentObject *docObj)
|
|
{
|
|
(void)docObj;
|
|
// This function is only used in SheetObserver, which is obsolete.
|
|
//
|
|
// if(docDeps.erase(const_cast<App::DocumentObject*>(docObj))) {
|
|
// const App::DocumentObject * docObj = dynamic_cast<const App::DocumentObject*>(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()
|
|
{
|
|
}
|
|
|
|
const std::set<CellAddress> &PropertySheet::getDeps(const std::string &name) const
|
|
{
|
|
static std::set<CellAddress> empty;
|
|
std::map<std::string, std::set< CellAddress > >::const_iterator i = propertyNameToCellMap.find(name);
|
|
|
|
if (i != propertyNameToCellMap.end())
|
|
return i->second;
|
|
else
|
|
return empty;
|
|
}
|
|
|
|
const std::set<std::string> &PropertySheet::getDeps(CellAddress pos) const
|
|
{
|
|
static std::set<std::string> empty;
|
|
std::map<CellAddress, std::set< std::string > >::const_iterator i = cellToPropertyNameMap.find(pos);
|
|
|
|
if (i != cellToPropertyNameMap.end())
|
|
return i->second;
|
|
else
|
|
return empty;
|
|
}
|
|
|
|
void PropertySheet::recomputeDependencies(CellAddress key)
|
|
{
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
removeDependencies(key);
|
|
addDependencies(key);
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
void PropertySheet::hasSetValue()
|
|
{
|
|
if(!updateCount ||
|
|
!owner || !owner->getNameInDocument() || owner->isRestoring() ||
|
|
this!=&owner->cells ||
|
|
testFlag(LinkDetached))
|
|
{
|
|
PropertyExpressionContainer::hasSetValue();
|
|
return;
|
|
}
|
|
|
|
updateCount = 0;
|
|
|
|
std::map<App::DocumentObject*,bool> deps;
|
|
std::vector<std::string> labels;
|
|
unregisterElementReference();
|
|
UpdateElementReferenceExpressionVisitor<PropertySheet> v(*this);
|
|
for(auto &d : data) {
|
|
auto expr = d.second->expression.get();
|
|
if(expr) {
|
|
expr->getDepObjects(deps,&labels);
|
|
if(!restoring)
|
|
expr->visit(v);
|
|
}
|
|
}
|
|
registerLabelReferences(std::move(labels));
|
|
|
|
updateDeps(std::move(deps));
|
|
|
|
PropertyExpressionContainer::hasSetValue();
|
|
}
|
|
|
|
PyObject *PropertySheet::getPyObject()
|
|
{
|
|
if (PythonObject.is(Py::_None())){
|
|
// ref counter is set to 1
|
|
PythonObject = Py::Object(new PropertySheetPy(this),true);
|
|
}
|
|
return Py::new_reference_to(PythonObject);
|
|
}
|
|
|
|
void PropertySheet::setPyObject(PyObject *obj) {
|
|
if(!obj || !PyObject_TypeCheck(obj, &PropertySheetPy::Type))
|
|
throw Base::TypeError("Invalid type");
|
|
if(obj != PythonObject.ptr())
|
|
Paste(*static_cast<PropertySheetPy*>(obj)->getPropertySheetPtr());
|
|
}
|
|
|
|
PyObject *PropertySheet::getPyValue(PyObject *key) {
|
|
assert(key);
|
|
|
|
PY_TRY {
|
|
std::string addr = Py::Object(key).as_string();
|
|
CellAddress caddr = getCellAddress(addr.c_str(),true);
|
|
if(caddr.isValid()) {
|
|
auto prop = owner->getPropertyByName(caddr.toString().c_str());
|
|
if(prop)
|
|
return prop->getPyObject();
|
|
Py_Return;
|
|
}
|
|
|
|
Range range = getRange(Py::Object(key).as_string().c_str());
|
|
if(!range.from().isValid() || !range.to().isValid())
|
|
return Py::new_reference_to(Py::Tuple());
|
|
|
|
Py::Tuple res(range.size());
|
|
int i = 0;
|
|
do {
|
|
addr = range.address();
|
|
auto prop = owner->getPropertyByName(addr.c_str());
|
|
res.setItem(i++,prop?Py::asObject(prop->getPyObject()):Py::Object());
|
|
} while(range.next());
|
|
|
|
return Py::new_reference_to(res);
|
|
|
|
} PY_CATCH
|
|
}
|
|
|
|
void PropertySheet::afterRestore()
|
|
{
|
|
Base::FlagToggler<bool> flag(restoring);
|
|
AtomicPropertyChange signaller(*this);
|
|
|
|
PropertyExpressionContainer::afterRestore();
|
|
{
|
|
ObjectIdentifier::DocumentMapper mapper(this->_DocMap);
|
|
for(auto &d : data)
|
|
d.second->afterRestore();
|
|
}
|
|
|
|
for(auto &v : _XLinks) {
|
|
auto &xlink = *v.second;
|
|
if(!xlink.checkRestore())
|
|
continue;
|
|
auto iter = documentObjectToCellMap.find(xlink.getValue()->getFullName());
|
|
if(iter == documentObjectToCellMap.end())
|
|
continue;
|
|
touch();
|
|
for(const auto &address : iter->second)
|
|
setDirty(address);
|
|
}
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
void PropertySheet::onContainerRestored() {
|
|
Base::FlagToggler<bool> flag(restoring);
|
|
unregisterElementReference();
|
|
UpdateElementReferenceExpressionVisitor<PropertySheet> v(*this);
|
|
for(auto &d : data) {
|
|
auto expr = d.second->expression.get();
|
|
if(expr)
|
|
expr->visit(v);
|
|
}
|
|
}
|
|
|
|
bool PropertySheet::adjustLink(const std::set<DocumentObject*> &inList) {
|
|
AtomicPropertyChange signaller(*this,false);
|
|
bool changed = false;
|
|
|
|
for(auto &d : data) {
|
|
auto expr = d.second->expression.get();
|
|
if(!expr)
|
|
continue;
|
|
try {
|
|
bool need_adjust = false;
|
|
for(auto &v : expr->getDepObjects()) {
|
|
auto docObj = v.first;
|
|
if (v.second && docObj && docObj!=owner && inList.count(docObj)) {
|
|
need_adjust = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!need_adjust)
|
|
continue;
|
|
|
|
signaller.aboutToChange();
|
|
changed = true;
|
|
|
|
removeDependencies(d.first);
|
|
expr->adjustLinks(inList);
|
|
addDependencies(d.first);
|
|
|
|
}catch(Base::Exception &e) {
|
|
addDependencies(d.first);
|
|
std::ostringstream ss;
|
|
ss << "Failed to adjust link for " << owner->getFullName() << " in expression "
|
|
<< expr->toString() << ": " << e.what();
|
|
throw Base::RuntimeError(ss.str());
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
void PropertySheet::updateElementReference(DocumentObject *feature,bool reverse,bool notify)
|
|
{
|
|
(void)notify;
|
|
if(!feature)
|
|
unregisterElementReference();
|
|
UpdateElementReferenceExpressionVisitor<PropertySheet> visitor(*this,feature,reverse);
|
|
for(auto &d : data) {
|
|
auto expr = d.second->expression.get();
|
|
if(!expr)
|
|
continue;
|
|
expr->visit(visitor);
|
|
}
|
|
if(feature && visitor.changed()) {
|
|
auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
|
|
if(owner)
|
|
owner->onUpdateElementReference(this);
|
|
}
|
|
}
|
|
|
|
bool PropertySheet::referenceChanged() const {
|
|
return false;
|
|
}
|
|
|
|
Property *PropertySheet::CopyOnImportExternal(
|
|
const std::map<std::string,std::string> &nameMap) const
|
|
{
|
|
std::map<CellAddress,std::unique_ptr<Expression> > changed;
|
|
for(auto &d : data) {
|
|
auto e = d.second->expression.get();
|
|
if(!e) continue;
|
|
auto expr = e->importSubNames(nameMap);
|
|
if(!expr)
|
|
continue;
|
|
changed[d.first] = std::move(expr);
|
|
}
|
|
if(changed.empty())
|
|
return 0;
|
|
std::unique_ptr<PropertySheet> copy(new PropertySheet(*this));
|
|
for(auto &change : changed)
|
|
copy->data[change.first]->setExpression(std::move(change.second));
|
|
return copy.release();
|
|
}
|
|
|
|
Property *PropertySheet::CopyOnLabelChange(App::DocumentObject *obj,
|
|
const std::string &ref, const char *newLabel) const
|
|
{
|
|
std::map<CellAddress,std::unique_ptr<Expression> > changed;
|
|
for(auto &d : data) {
|
|
auto e = d.second->expression.get();
|
|
if(!e) continue;
|
|
auto expr = e->updateLabelReference(obj,ref,newLabel);
|
|
if(!expr)
|
|
continue;
|
|
changed[d.first] = std::move(expr);
|
|
}
|
|
if(changed.empty())
|
|
return 0;
|
|
std::unique_ptr<PropertySheet> copy(new PropertySheet(*this));
|
|
for(auto &change : changed)
|
|
copy->data[change.first]->setExpression(std::move(change.second));
|
|
return copy.release();
|
|
}
|
|
|
|
Property *PropertySheet::CopyOnLinkReplace(const App::DocumentObject *parent,
|
|
App::DocumentObject *oldObj, App::DocumentObject *newObj) const
|
|
{
|
|
std::map<CellAddress,std::unique_ptr<Expression> > changed;
|
|
for(auto &d : data) {
|
|
auto e = d.second->expression.get();
|
|
if(!e) continue;
|
|
auto expr = e->replaceObject(parent,oldObj,newObj);
|
|
if(!expr)
|
|
continue;
|
|
changed[d.first] = std::move(expr);
|
|
}
|
|
if(changed.empty())
|
|
return 0;
|
|
std::unique_ptr<PropertySheet> copy(new PropertySheet(*this));
|
|
for(auto &change : changed)
|
|
copy->data[change.first]->setExpression(std::move(change.second));
|
|
return copy.release();
|
|
}
|
|
|
|
std::map<App::ObjectIdentifier, const App::Expression*> PropertySheet::getExpressions() const {
|
|
std::map<App::ObjectIdentifier, const Expression*> res;
|
|
for(auto &d : data) {
|
|
if(d.second->expression) {
|
|
res[ObjectIdentifier(owner,d.first.toString())] = d.second->getExpression(true);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void PropertySheet::setExpressions(
|
|
std::map<App::ObjectIdentifier, App::ExpressionPtr> &&exprs)
|
|
{
|
|
AtomicPropertyChange signaller(*this);
|
|
for(auto &v : exprs) {
|
|
CellAddress addr(v.first.getPropertyName().c_str());
|
|
auto &cell = data[addr];
|
|
if(!cell) {
|
|
if(!v.second)
|
|
continue;
|
|
cell = new Cell(addr,this);
|
|
}
|
|
if(!v.second)
|
|
clear(addr);
|
|
else
|
|
cell->setExpression(std::move(v.second));
|
|
}
|
|
signaller.tryInvoke();
|
|
}
|
|
|
|
App::CellAddress PropertySheet::getCellAddress(const char *addr, bool silent) const {
|
|
assert(addr);
|
|
CellAddress caddr;
|
|
const Cell * cell = getValueFromAlias(addr);
|
|
if(cell)
|
|
return cell->getAddress();
|
|
else
|
|
return stringToAddress(addr,silent);
|
|
}
|
|
|
|
App::Range PropertySheet::getRange(const char *range, bool silent) const {
|
|
assert(range);
|
|
const char *sep = strchr(range,':');
|
|
CellAddress from,to;
|
|
if(!sep)
|
|
from = to = getCellAddress(range,silent);
|
|
else {
|
|
std::string addr(range,sep);
|
|
|
|
auto findCell = [this, &addr](CellAddress caddr, int r, int c) -> CellAddress {
|
|
if(!getValue(caddr))
|
|
return CellAddress();
|
|
if(addr == "-")
|
|
r = 0;
|
|
else
|
|
c = 0;
|
|
for(;;) {
|
|
caddr.setRow(caddr.row()+r);
|
|
caddr.setCol(caddr.col()+c);
|
|
if(!caddr.isValid() || !getValue(caddr))
|
|
break;
|
|
}
|
|
caddr.setRow(caddr.row()-r);
|
|
caddr.setCol(caddr.col()-c);
|
|
return caddr;
|
|
};
|
|
|
|
if(addr == "-" || addr == "|") {
|
|
to = getCellAddress(sep+1,silent);
|
|
return Range(findCell(to,-1,-1), from);
|
|
} else {
|
|
from = getCellAddress(addr.c_str(),silent);
|
|
addr = sep+1;
|
|
if(addr == "-" || addr == "|")
|
|
return Range(from, findCell(from,1,1));
|
|
to = getCellAddress(addr.c_str(),silent);
|
|
}
|
|
}
|
|
|
|
if(!from.isValid() || !to.isValid())
|
|
return App::Range(App::CellAddress(),App::CellAddress());
|
|
return App::Range(from,to);
|
|
}
|
|
|
|
bool PropertySheet::isBindingPath(const ObjectIdentifier &path,
|
|
CellAddress *from, CellAddress *to, bool *href) const
|
|
{
|
|
const auto &comps = path.getComponents();
|
|
if (comps.size()!=4
|
|
|| !comps[2].isSimple()
|
|
|| !comps[3].isSimple()
|
|
|| (comps[1].getName()!="Bind"
|
|
&& comps[1].getName()!="BindHREF"
|
|
&& comps[1].getName()!="BindHiddenRef")
|
|
|| path.getProperty() != this)
|
|
{
|
|
return false;
|
|
}
|
|
if(href)
|
|
*href = (comps[1].getName()=="BindHREF" || comps[1].getName()=="BindHiddenRef");
|
|
if(from)
|
|
*from = CellAddress(comps[2].getName());
|
|
if(to)
|
|
*to = CellAddress(comps[3].getName());
|
|
return true;
|
|
}
|
|
|
|
PropertySheet::BindingType PropertySheet::getBinding(
|
|
const Range &range, ExpressionPtr *pStart, ExpressionPtr *pEnd) const
|
|
{
|
|
if(!owner)
|
|
return BindingNone;
|
|
|
|
for(int href=0;href<2;++href) {
|
|
ObjectIdentifier path(*this);
|
|
path << ObjectIdentifier::SimpleComponent(href?"BindHiddenRef":"Bind");
|
|
path << ObjectIdentifier::SimpleComponent(range.from().toString().c_str());
|
|
path << ObjectIdentifier::SimpleComponent(range.to().toString().c_str());
|
|
auto res = owner->getExpression(path);
|
|
if(res.expression && res.expression->isDerivedFrom(FunctionExpression::getClassTypeId()))
|
|
{
|
|
auto expr = static_cast<FunctionExpression*>(res.expression.get());
|
|
if(href) {
|
|
if((expr->getFunction()!=FunctionExpression::HIDDENREF
|
|
&& expr->getFunction()!=FunctionExpression::HREF)
|
|
|| expr->getArgs().size()!=1
|
|
|| !expr->getArgs().front()->isDerivedFrom(FunctionExpression::getClassTypeId()))
|
|
continue;
|
|
expr = static_cast<FunctionExpression*>(expr->getArgs().front());
|
|
}
|
|
|
|
if(expr->getFunction() == FunctionExpression::TUPLE && expr->getArgs().size()==3) {
|
|
if(pStart)
|
|
pStart->reset(expr->getArgs()[1]->copy());
|
|
if(pEnd)
|
|
pEnd->reset(expr->getArgs()[2]->copy());
|
|
return href?BindingHiddenRef:BindingNormal;
|
|
}
|
|
}
|
|
}
|
|
return BindingNone;
|
|
}
|
|
|
|
void PropertySheet::setPathValue(const ObjectIdentifier &path, const boost::any &value)
|
|
{
|
|
if(!owner)
|
|
FC_THROWM(Base::RuntimeError, "Invalid state");
|
|
|
|
bool href = false;
|
|
CellAddress from,to;
|
|
if(!isBindingPath(path,&from,&to,&href)) {
|
|
FC_THROWM(Base::IndexError, "Invalid binding of '" << path.toString()
|
|
<< "' in " << getFullName());
|
|
}
|
|
|
|
Base::PyGILStateLocker lock;
|
|
Py::Object pyValue = pyObjectFromAny(value);
|
|
|
|
if(pyValue.isSequence()) {
|
|
Py::Sequence seq(pyValue);
|
|
if(seq.size()==3
|
|
&& PyObject_TypeCheck(seq[0].ptr(),&PropertySheetPy::Type)
|
|
&& Py::Object(seq[1].ptr()).isString()
|
|
&& Py::Object(seq[2].ptr()).isString())
|
|
{
|
|
AtomicPropertyChange signaller(*this,false);
|
|
auto other = static_cast<PropertySheetPy*>(seq[0].ptr())->getPropertySheetPtr();
|
|
auto otherOwner = Base::freecad_dynamic_cast<App::DocumentObject>(other->getContainer());
|
|
if(!otherOwner)
|
|
FC_THROWM(Base::RuntimeError, "Invalid binding of '" << other->getFullName()
|
|
<< " in " << getFullName());
|
|
|
|
App::CellAddress targetFrom = other->getCellAddress(
|
|
Py::Object(seq[1].ptr()).as_string().c_str());
|
|
|
|
App::CellAddress targetTo = other->getCellAddress(
|
|
Py::Object(seq[2].ptr()).as_string().c_str());
|
|
|
|
App::Range range(from,to);
|
|
App::Range rangeTarget(targetFrom,targetTo);
|
|
|
|
std::string expr(href?"href(":"");
|
|
if(other != this) {
|
|
if(otherOwner->getDocument() == owner->getDocument())
|
|
expr = otherOwner->getNameInDocument();
|
|
else
|
|
expr = otherOwner->getFullName();
|
|
}
|
|
expr += ".";
|
|
std::size_t exprSize = expr.size();
|
|
|
|
do {
|
|
CellAddress target(*rangeTarget);
|
|
CellAddress source(*range);
|
|
if(other == this && source.row() >= targetFrom.row()
|
|
&& source.row() <= targetTo.row()
|
|
&& source.col() >= targetFrom.col()
|
|
&& source.col() <= targetTo.col())
|
|
continue;
|
|
|
|
Cell *dst = other->getValue(target);
|
|
Cell *src = getValue(source);
|
|
if(!dst) {
|
|
if(src) {
|
|
signaller.aboutToChange();
|
|
owner->clear(source);
|
|
owner->cellUpdated(source);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if(!src) {
|
|
signaller.aboutToChange();
|
|
src = createCell(source);
|
|
}
|
|
|
|
std::string alias;
|
|
if(this!=other && dst->getAlias(alias)) {
|
|
auto *oldCell = getValueFromAlias(alias);
|
|
if(oldCell && oldCell!=dst) {
|
|
signaller.aboutToChange();
|
|
oldCell->setAlias("");
|
|
}
|
|
std::string oldAlias;
|
|
if(!src->getAlias(oldAlias) || oldAlias!=alias) {
|
|
signaller.aboutToChange();
|
|
setAlias(source,alias);
|
|
}
|
|
}
|
|
|
|
expr.resize(exprSize);
|
|
expr += rangeTarget.address();
|
|
if(href)
|
|
expr += ")";
|
|
auto e = App::ExpressionPtr(App::Expression::parse(owner,expr));
|
|
auto e2 = src->getExpression();
|
|
if(!e2 || !e->isSame(*e2,false)) {
|
|
signaller.aboutToChange();
|
|
src->setExpression(std::move(e));
|
|
}
|
|
|
|
} while(range.next() && rangeTarget.next());
|
|
owner->rangeUpdated(range);
|
|
signaller.tryInvoke();
|
|
return;
|
|
}
|
|
}
|
|
|
|
FC_THROWM(Base::TypeError, "Invalid path value '"
|
|
<< "' for " << getFullName());
|
|
}
|
|
|
|
const boost::any PropertySheet::getPathValue(const App::ObjectIdentifier & path) const {
|
|
if(isBindingPath(path))
|
|
return boost::any();
|
|
return path.getValue();
|
|
}
|