Spreadsheet: support cell binding

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
This commit is contained in:
Zheng, Lei
2019-12-26 18:51:41 +08:00
committed by Chris Hennes
parent 3d1b9f5c90
commit 68fca40983
23 changed files with 928 additions and 66 deletions

View File

@@ -1140,12 +1140,13 @@ Expression* Expression::eval() const {
return expressionFromPy(owner,getPyValue());
}
bool Expression::isSame(const Expression &other) const {
bool Expression::isSame(const Expression &other, bool checkComment) const {
if(&other == this)
return true;
if(getTypeId()!=other.getTypeId())
return false;
return comment==other.comment && toString(true,true) == other.toString(true,true);
return (!checkComment || comment==other.comment)
&& toString(true,true) == other.toString(true,true);
}
std::string Expression::toString(bool persistent, bool checkPriority, int indent) const {

View File

@@ -185,7 +185,7 @@ public:
Py::Object getPyValue() const;
bool isSame(const Expression &other) const;
bool isSame(const Expression &other, bool checkComment=true) const;
friend ExpressionVisitor;

View File

@@ -301,6 +301,9 @@ public:
static Py::Object evaluate(const Expression *owner, int type, const std::vector<Expression*> &args);
Function getFunction() const {return f;}
const std::vector<Expression*> &getArgs() const {return args;}
protected:
static Py::Object evalAggregate(const Expression *owner, int type, const std::vector<Expression*> &args);
virtual Py::Object _getPyValue() const override;

View File

@@ -28,7 +28,7 @@
#include <Base/Writer.h>
#include <Base/Reader.h>
#include <Base/Tools.h>
#include "Expression.h"
#include "ExpressionParser.h"
#include "ExpressionVisitors.h"
#include "PropertyExpressionEngine.h"
#include "PropertyStandard.h"
@@ -477,8 +477,13 @@ void PropertyExpressionEngine::setValue(const ObjectIdentifier & path, std::shar
// Check if the current expression equals the new one and do nothing if so to reduce unneeded computations
ExpressionMap::iterator it = expressions.find(usePath);
if(it != expressions.end() && expr == it->second.expression)
if(it != expressions.end()
&& (expr == it->second.expression ||
(expr && it->second.expression
&& expr->isSame(*it->second.expression))))
{
return;
}
if (expr) {
std::string error = validateExpression(usePath, expr);
@@ -488,9 +493,9 @@ void PropertyExpressionEngine::setValue(const ObjectIdentifier & path, std::shar
expressions[usePath] = ExpressionInfo(expr);
expressionChanged(usePath);
signaller.tryInvoke();
} else {
} else if (it != expressions.end()) {
AtomicPropertyChange signaller(*this);
expressions.erase(usePath);
expressions.erase(it);
expressionChanged(usePath);
signaller.tryInvoke();
}

View File

@@ -176,7 +176,8 @@ private:
typedef boost::adjacency_list< boost::listS, boost::vecS, boost::directedS > DiGraph;
typedef std::pair<int, int> Edge;
typedef boost::unordered_map<const App::ObjectIdentifier, ExpressionInfo> ExpressionMap;
// Note: use std::map instead of unordered_map to keep the binding order stable
typedef std::map<const App::ObjectIdentifier, ExpressionInfo> ExpressionMap;
std::vector<App::ObjectIdentifier> computeEvaluationOrder(ExecuteOption option);

View File

@@ -149,7 +149,7 @@ int App::validRow(const std::string &rowstr)
char * end;
int i = strtol(rowstr.c_str(), &end, 10);
if (i <0 || i >= CellAddress::MAX_ROWS || *end)
if (i <=0 || i > CellAddress::MAX_ROWS || *end)
return -1;
return i - 1;
@@ -234,24 +234,28 @@ App::CellAddress App::stringToAddress(const char * strAddress, bool silent)
* @returns Address given as a string.
*/
std::string App::CellAddress::toString(bool noAbsolute) const
std::string App::CellAddress::toString(bool noAbsolute, bool r, bool c) const
{
std::stringstream s;
if(_absCol && !noAbsolute)
s << '$';
if (col() < 26)
s << (char)('A' + col());
else {
int colnum = col() - 26;
if(c) {
if(_absCol && !noAbsolute)
s << '$';
if (col() < 26)
s << (char)('A' + col());
else {
int colnum = col() - 26;
s << (char)('A' + (colnum / 26));
s << (char)('A' + (colnum % 26));
s << (char)('A' + (colnum / 26));
s << (char)('A' + (colnum % 26));
}
}
if(_absRow && !noAbsolute)
s << '$';
s << (row() + 1);
if(r) {
if(_absRow && !noAbsolute)
s << '$';
s << (row() + 1);
}
return s.str();
}

View File

@@ -58,9 +58,9 @@ struct AppExport CellAddress {
inline int col() const { return _col; }
void setRow(int r) { _row = r; }
void setRow(int r, bool clip=false) { _row = (clip && r>=MAX_ROWS) ? MAX_ROWS-1 : r; }
void setCol(int c) { _col = c; }
void setCol(int c, bool clip=false) { _col = (clip && c>=MAX_COLUMNS) ? MAX_COLUMNS-1 : c; }
inline bool operator<(const CellAddress & other) const { return asInt() < other.asInt(); }
@@ -74,7 +74,7 @@ struct AppExport CellAddress {
inline bool isAbsoluteCol() const { return _absCol; }
std::string toString(bool noAbsolute=false) const;
std::string toString(bool noAbsolute=false, bool row=true, bool col=true) const;
// Static members