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 55e2c918a9
commit 0c2c334f87
23 changed files with 928 additions and 66 deletions

View File

@@ -54,22 +54,21 @@ using namespace Gui;
class ExpressionCompleterModel: public QAbstractItemModel {
public:
ExpressionCompleterModel(QObject *parent, const App::DocumentObject *obj, bool noProperty)
ExpressionCompleterModel(QObject *parent, bool noProperty)
:QAbstractItemModel(parent), noProperty(noProperty)
{
setDocumentObject(obj);
}
void setNoProperty(bool enabled) {
noProperty = enabled;
}
void setDocumentObject(const App::DocumentObject *obj) {
void setDocumentObject(const App::DocumentObject *obj, bool checkInList) {
beginResetModel();
if(obj) {
currentDoc = obj->getDocument()->getName();
currentObj = obj->getNameInDocument();
if(!noProperty)
if(!noProperty && checkInList)
inList = obj->getInListEx(true);
} else {
currentDoc.clear();
@@ -340,9 +339,10 @@ private:
* @param parent Parent object owning the completer.
*/
ExpressionCompleter::ExpressionCompleter(const App::DocumentObject * currentDocObj,
QObject *parent, bool noProperty)
: QCompleter(parent), currentObj(currentDocObj), noProperty(noProperty)
ExpressionCompleter::ExpressionCompleter(const App::DocumentObject * currentDocObj,
QObject *parent, bool noProperty, bool checkInList)
: QCompleter(parent), currentObj(currentDocObj)
, noProperty(noProperty), checkInList(checkInList)
{
setCaseSensitivity(Qt::CaseInsensitive);
}
@@ -351,18 +351,21 @@ void ExpressionCompleter::init() {
if(model())
return;
setModel(new ExpressionCompleterModel(this,currentObj.getObject(),noProperty));
auto m = new ExpressionCompleterModel(this,noProperty);
m->setDocumentObject(currentObj.getObject(),checkInList);
setModel(m);
}
void ExpressionCompleter::setDocumentObject(const App::DocumentObject *obj) {
void ExpressionCompleter::setDocumentObject(const App::DocumentObject *obj, bool _checkInList) {
if(!obj || !obj->getNameInDocument())
currentObj = App::DocumentObjectT();
else
currentObj = obj;
setCompletionPrefix(QString());
checkInList = _checkInList;
auto m = model();
if(m)
static_cast<ExpressionCompleterModel*>(m)->setDocumentObject(obj);
static_cast<ExpressionCompleterModel*>(m)->setDocumentObject(obj, checkInList);
}
void ExpressionCompleter::setNoProperty(bool enabled) {
@@ -372,10 +375,6 @@ void ExpressionCompleter::setNoProperty(bool enabled) {
static_cast<ExpressionCompleterModel*>(m)->setNoProperty(enabled);
}
void ExpressionCompleter::setRequireLeadingEqualSign(bool enabled) {
requireLeadingEqualSign = enabled;
}
QString ExpressionCompleter::pathFromIndex ( const QModelIndex & index ) const
{
auto m = model();
@@ -459,12 +458,6 @@ void ExpressionCompleter::slotUpdate(const QString & prefix, int pos)
// Compute start; if prefix starts with =, start parsing from offset 1.
int start = (prefix.size() > 0 && prefix.at(0) == QChar::fromLatin1('=')) ? 1 : 0;
if (requireLeadingEqualSign && start != 1) {
if (auto p = popup())
p->setVisible(false);
return;
}
std::string expression = Base::Tools::toStdString(prefix.mid(start));
// Tokenize prefix
@@ -564,28 +557,33 @@ void ExpressionCompleter::slotUpdate(const QString & prefix, int pos)
}
}
ExpressionLineEdit::ExpressionLineEdit(QWidget *parent, bool noProperty, bool requireLeadingEqualSign)
ExpressionLineEdit::ExpressionLineEdit(QWidget *parent, bool noProperty, char checkPrefix, bool checkInList)
: QLineEdit(parent)
, completer(nullptr)
, block(true)
, noProperty(noProperty)
, exactMatch(false)
, requireLeadingEqualSign(requireLeadingEqualSign)
, checkInList(checkInList)
, checkPrefix(checkPrefix)
{
connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(slotTextChanged(const QString&)));
}
void ExpressionLineEdit::setDocumentObject(const App::DocumentObject * currentDocObj)
void ExpressionLineEdit::setPrefix(char prefix) {
checkPrefix = prefix;
}
void ExpressionLineEdit::setDocumentObject(const App::DocumentObject * currentDocObj, bool _checkInList)
{
checkInList = _checkInList;
if (completer) {
completer->setDocumentObject(currentDocObj);
completer->setDocumentObject(currentDocObj, checkInList);
return;
}
if (currentDocObj != 0) {
completer = new ExpressionCompleter(currentDocObj, this, noProperty);
completer = new ExpressionCompleter(currentDocObj, this, noProperty, checkInList);
completer->setWidget(this);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setRequireLeadingEqualSign(requireLeadingEqualSign);
if (!exactMatch)
completer->setFilterMode(Qt::MatchContains);
connect(completer, SIGNAL(activated(QString)), this, SLOT(slotCompleteText(QString)));
@@ -621,6 +619,8 @@ void ExpressionLineEdit::hideCompleter()
void ExpressionLineEdit::slotTextChanged(const QString & text)
{
if (!block) {
if(!text.size() || (checkPrefix && text[0]!=QLatin1Char(checkPrefix)))
return;
Q_EMIT textChanged2(text,cursorPosition());
}
}