From 68fca409833ca0c8c3811ad4efddcfcc3e2e4255 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 26 Dec 2019 18:51:41 +0800 Subject: [PATCH] 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, <>, <>) The A1 and D1 in the example above specifies the binding start and end cell address. And <> and <> 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. <> % 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 --- src/App/Expression.cpp | 5 +- src/App/Expression.h | 2 +- src/App/ExpressionParser.h | 3 + src/App/PropertyExpressionEngine.cpp | 13 +- src/App/PropertyExpressionEngine.h | 3 +- src/App/Range.cpp | 30 +- src/App/Range.h | 6 +- src/Gui/ExpressionCompleter.cpp | 52 ++-- src/Gui/ExpressionCompleter.h | 19 +- src/Mod/Spreadsheet/App/PropertySheet.cpp | 261 +++++++++++++++++- src/Mod/Spreadsheet/App/PropertySheet.h | 18 ++ src/Mod/Spreadsheet/App/Sheet.cpp | 53 ++++ src/Mod/Spreadsheet/App/Sheet.h | 16 ++ src/Mod/Spreadsheet/Gui/CMakeLists.txt | 3 + src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp | 202 ++++++++++++++ src/Mod/Spreadsheet/Gui/DlgBindSheet.h | 56 ++++ src/Mod/Spreadsheet/Gui/DlgBindSheet.ui | 169 ++++++++++++ src/Mod/Spreadsheet/Gui/SheetModel.cpp | 10 + src/Mod/Spreadsheet/Gui/SheetModel.h | 2 + src/Mod/Spreadsheet/Gui/SheetTableView.cpp | 19 ++ src/Mod/Spreadsheet/Gui/SheetTableView.h | 2 + .../Spreadsheet/Gui/SpreadsheetDelegate.cpp | 43 ++- src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h | 7 +- 23 files changed, 928 insertions(+), 66 deletions(-) create mode 100644 src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp create mode 100644 src/Mod/Spreadsheet/Gui/DlgBindSheet.h create mode 100644 src/Mod/Spreadsheet/Gui/DlgBindSheet.ui diff --git a/src/App/Expression.cpp b/src/App/Expression.cpp index f00dd4950c..66e698ac9f 100644 --- a/src/App/Expression.cpp +++ b/src/App/Expression.cpp @@ -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 { diff --git a/src/App/Expression.h b/src/App/Expression.h index 8d394db807..926e72c99c 100644 --- a/src/App/Expression.h +++ b/src/App/Expression.h @@ -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; diff --git a/src/App/ExpressionParser.h b/src/App/ExpressionParser.h index 726220c80c..a10c1e8212 100644 --- a/src/App/ExpressionParser.h +++ b/src/App/ExpressionParser.h @@ -301,6 +301,9 @@ public: static Py::Object evaluate(const Expression *owner, int type, const std::vector &args); + Function getFunction() const {return f;} + const std::vector &getArgs() const {return args;} + protected: static Py::Object evalAggregate(const Expression *owner, int type, const std::vector &args); virtual Py::Object _getPyValue() const override; diff --git a/src/App/PropertyExpressionEngine.cpp b/src/App/PropertyExpressionEngine.cpp index 57cdc5cb62..e316c0310d 100644 --- a/src/App/PropertyExpressionEngine.cpp +++ b/src/App/PropertyExpressionEngine.cpp @@ -28,7 +28,7 @@ #include #include #include -#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(); } diff --git a/src/App/PropertyExpressionEngine.h b/src/App/PropertyExpressionEngine.h index 926ff856d3..9a4982d2d2 100644 --- a/src/App/PropertyExpressionEngine.h +++ b/src/App/PropertyExpressionEngine.h @@ -176,7 +176,8 @@ private: typedef boost::adjacency_list< boost::listS, boost::vecS, boost::directedS > DiGraph; typedef std::pair Edge; - typedef boost::unordered_map ExpressionMap; + // Note: use std::map instead of unordered_map to keep the binding order stable + typedef std::map ExpressionMap; std::vector computeEvaluationOrder(ExecuteOption option); diff --git a/src/App/Range.cpp b/src/App/Range.cpp index f7e5445253..0e4f4651af 100644 --- a/src/App/Range.cpp +++ b/src/App/Range.cpp @@ -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(); } diff --git a/src/App/Range.h b/src/App/Range.h index a9182b7466..6fb914c869 100644 --- a/src/App/Range.h +++ b/src/App/Range.h @@ -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 diff --git a/src/Gui/ExpressionCompleter.cpp b/src/Gui/ExpressionCompleter.cpp index 6fbe8e5be1..697ff6aad5 100644 --- a/src/Gui/ExpressionCompleter.cpp +++ b/src/Gui/ExpressionCompleter.cpp @@ -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(m)->setDocumentObject(obj); + static_cast(m)->setDocumentObject(obj, checkInList); } void ExpressionCompleter::setNoProperty(bool enabled) { @@ -372,10 +375,6 @@ void ExpressionCompleter::setNoProperty(bool enabled) { static_cast(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()); } } diff --git a/src/Gui/ExpressionCompleter.h b/src/Gui/ExpressionCompleter.h index 70169bb792..3f76bd49d4 100644 --- a/src/Gui/ExpressionCompleter.h +++ b/src/Gui/ExpressionCompleter.h @@ -50,8 +50,8 @@ class GuiExport ExpressionCompleter : public QCompleter { Q_OBJECT public: - ExpressionCompleter(const App::DocumentObject * currentDocObj, - QObject *parent = nullptr, bool noProperty = false); + ExpressionCompleter(const App::DocumentObject * currentDocObj, + QObject *parent = nullptr, bool noProperty = false, bool checkInList = true); void getPrefixRange(int &start, int &end) const { start = prefixStart; @@ -62,10 +62,9 @@ public: prefixEnd = end; } - void setDocumentObject(const App::DocumentObject*); + void setDocumentObject(const App::DocumentObject*, bool checkInList=true); void setNoProperty(bool enabled=true); - void setRequireLeadingEqualSign(bool enabled); public Q_SLOTS: void slotUpdate(const QString &prefix, int pos); @@ -77,18 +76,19 @@ private: int prefixStart = 0; int prefixEnd = 0; - bool requireLeadingEqualSign = false; App::DocumentObjectT currentObj; bool noProperty; - + bool checkInList; }; class GuiExport ExpressionLineEdit : public QLineEdit { Q_OBJECT public: - ExpressionLineEdit(QWidget *parent = nullptr, bool noProperty = false, bool requireLeadingEqualSign = false); - void setDocumentObject(const App::DocumentObject *currentDocObj); + ExpressionLineEdit(QWidget *parent = 0, bool noProperty=false, + char checkPrefix=0, bool checkInList=true); + void setDocumentObject(const App::DocumentObject *currentDocObj, bool checkInList=true); + void setPrefix(char prefix); bool completerActive() const; void hideCompleter(); void setNoProperty(bool enabled=true); @@ -106,7 +106,8 @@ private: bool block; bool noProperty; bool exactMatch; - bool requireLeadingEqualSign; + bool checkInList; + char checkPrefix; }; class GuiExport ExpressionTextEdit : public QPlainTextEdit { diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index cef6b26087..7c7715beee 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -1216,7 +1216,8 @@ void PropertySheet::recomputeDependants(const App::DocumentObject *owner, const // protected by cyclic dependency checking, we need to take special // care to prevent it from misbehave. Sheet *sheet = Base::freecad_dynamic_cast(getContainer()); - if(!sheet || sheet->testStatus(App::ObjectStatus::Recompute2)) + if(!sheet || sheet->testStatus(App::ObjectStatus::Recompute2) + || !owner || owner->testStatus(App::ObjectStatus::Recompute2)) return; } @@ -1434,6 +1435,36 @@ void PropertySheet::setPyObject(PyObject *obj) { Paste(*static_cast(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 flag(restoring); @@ -1621,3 +1652,231 @@ void PropertySheet::setExpressions( } 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(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(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(seq[0].ptr())->getPropertySheetPtr(); + auto otherOwner = Base::freecad_dynamic_cast(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(); +} diff --git a/src/Mod/Spreadsheet/App/PropertySheet.h b/src/Mod/Spreadsheet/App/PropertySheet.h index a98fee3087..8a181fec0d 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.h +++ b/src/Mod/Spreadsheet/App/PropertySheet.h @@ -163,6 +163,8 @@ public: PyObject *getPyObject(void) override; void setPyObject(PyObject *) override; + PyObject *getPyValue(PyObject *key); + void invalidateDependants(const App::DocumentObject *docObj); void renamedDocumentObject(const App::DocumentObject *docObj); @@ -176,6 +178,22 @@ public: std::string getColumn(int offset=0) const; + virtual void setPathValue(const App::ObjectIdentifier & path, const boost::any & value) override; + virtual const boost::any getPathValue(const App::ObjectIdentifier & path) const override; + + unsigned getBindingBorder(App::CellAddress address) const; + + bool isBindingPath(const App::ObjectIdentifier &path, + App::CellAddress *from=0, App::CellAddress *to=0, bool *href=0) const; + + enum BindingType { + BindingNone, + BindingNormal, + BindingHiddenRef, + }; + BindingType getBinding(const App::Range &range, + App::ExpressionPtr *pStart=0, App::ExpressionPtr *pEnd=0) const; + protected: virtual void hasSetValue() override; virtual void hasSetChildValue(App::Property &prop) override; diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index 61e4c2b17b..9f576d8faa 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -844,6 +844,52 @@ void Sheet::recomputeCell(CellAddress p) cellSpanChanged(p); } +PropertySheet::BindingType Sheet::getCellBinding(Range &range, + ExpressionPtr *pStart, ExpressionPtr *pEnd) const +{ + do { + CellAddress addr = *range; + for(auto &r : boundRanges) { + if(addr.row()>=r.from().row() + && addr.row()<=r.to().row() + && addr.col()>=r.from().col() + && addr.col()<=r.to().col()) + { + auto res = cells.getBinding(r,pStart,pEnd); + if(res != PropertySheet::BindingNone) { + range = r; + return res; + } + } + } + } while(range.next()); + return PropertySheet::BindingNone; +} + +unsigned Sheet::getCellBindingBorder(App::CellAddress address) const { + unsigned flags = 0; + for(auto &range : boundRanges) { + auto from = range.from(); + auto to = range.to(); + if(address.row() < from.row() + || address.row() > to.row() + || address.col() < from.col() + || address.col() > to.col()) + continue; + if(address.row() == from.row()) + flags |= BorderTop; + if(address.row() == to.row()) + flags |= BorderBottom; + if(address.col() == from.col()) + flags |= BorderLeft; + if(address.col() == to.col()) + flags |= BorderRight; + if(flags == BorderAll) + break; + } + return flags; +} + /** * Update the document properties. * @@ -853,6 +899,13 @@ DocumentObjectExecReturn *Sheet::execute(void) { // Remove all aliases first removeAliases(); + boundRanges.clear(); + for(auto &v : ExpressionEngine.getExpressions()) { + CellAddress from,to; + if(!cells.isBindingPath(v.first,&from,&to)) + continue; + boundRanges.emplace_back(from,to); + } // Get dirty cells that we have to recompute std::set dirtyCells = cells.getDirty(); diff --git a/src/Mod/Spreadsheet/App/Sheet.h b/src/Mod/Spreadsheet/App/Sheet.h index d1e7324773..fe5096121e 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -98,6 +98,18 @@ public: Cell *getNewCell(App::CellAddress address); + enum Border { + BorderTop = 1, + BorderLeft = 2, + BorderBottom = 4, + BorderRight = 8, + BorderAll = 15, + }; + unsigned getCellBindingBorder(App::CellAddress address) const; + + PropertySheet::BindingType getCellBinding(App::Range &range, + App::ExpressionPtr *pStart=0, App::ExpressionPtr *pEnd=0) const; + void setCell(const char *address, const char *value); void setCell(App::CellAddress address, const char *value); @@ -185,6 +197,8 @@ public: boost::signals2::signal cellUpdated; + boost::signals2::signal rangeUpdated; + boost::signals2::signal cellSpanChanged; boost::signals2::signal columnWidthChanged; @@ -259,6 +273,8 @@ protected: int currentRow = -1; int currentCol = -1; + std::vector boundRanges; + friend class SheetObserver; friend class PropertySheet; diff --git a/src/Mod/Spreadsheet/Gui/CMakeLists.txt b/src/Mod/Spreadsheet/Gui/CMakeLists.txt index cbffc4e9b3..5f6a54b3cf 100644 --- a/src/Mod/Spreadsheet/Gui/CMakeLists.txt +++ b/src/Mod/Spreadsheet/Gui/CMakeLists.txt @@ -51,6 +51,7 @@ endif() set(SpreadsheetGui_UIC_SRCS Sheet.ui PropertiesDialog.ui + DlgBindSheet.ui ) if(BUILD_QT5) @@ -89,6 +90,8 @@ SET(SpreadsheetGui_SRCS qtcolorpicker.cpp PropertiesDialog.h PropertiesDialog.cpp + DlgBindSheet.h + DlgBindSheet.cpp ${SpreadsheetGui_UIC_HDRS} ) diff --git a/src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp b/src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp new file mode 100644 index 0000000000..d72f0822c0 --- /dev/null +++ b/src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** + * Copyright (c) 2019 Zheng, Lei (realthunder) * + * * + * 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" +#include +#include +#include "DlgBindSheet.h" +#include +#include +#include +#include +#include +#include +#include "ui_DlgBindSheet.h" + +using namespace App; +using namespace Spreadsheet; +using namespace SpreadsheetGui; + +DlgBindSheet::DlgBindSheet(Sheet *sheet, const std::vector &ranges, QWidget *parent) + : QDialog(parent), sheet(sheet), range(ranges.front()), ui(new Ui::DlgBindSheet) +{ + ui->setupUi(this); + + std::string toStart,toEnd; + ExpressionPtr pStart, pEnd; + PropertySheet::BindingType type = sheet->getCellBinding(range,&pStart,&pEnd); + if(type == PropertySheet::BindingNone) { + if(ranges.size()>1) { + toStart = ranges.back().from().toString(); + toEnd = ranges.back().to().toString(); + } else { + CellAddress target(range.to().row()?0:range.to().row()+1,range.from().col()); + toStart = target.toString(); + target.setRow(target.row() + range.to().row() - range.from().row()); + target.setCol(target.col() + range.to().col() - range.from().col()); + toEnd = target.toString(); + } + } else { + ui->lineEditFromStart->setReadOnly(true); + ui->lineEditFromEnd->setReadOnly(true); + ui->checkBoxHREF->setChecked(type==PropertySheet::BindingHiddenRef); + assert(pStart && pEnd); + if(!pStart->hasComponent() && pStart->isDerivedFrom(StringExpression::getClassTypeId())) + toStart = static_cast(pStart.get())->getText(); + else { + toStart = "="; + toStart += pStart->toString(); + } + if(!pEnd->hasComponent() && pEnd->isDerivedFrom(StringExpression::getClassTypeId())) + toEnd = static_cast(pEnd.get())->getText(); + else { + toEnd = "="; + toEnd += pEnd->toString(); + } + } + + ui->lineEditFromStart->setText(QString::fromLatin1(range.from().toString().c_str())); + ui->lineEditFromEnd->setText(QString::fromLatin1(range.to().toString().c_str())); + + ui->lineEditToStart->setDocumentObject(sheet,false); + ui->lineEditToStart->setPrefix('='); + ui->lineEditToEnd->setDocumentObject(sheet,false); + ui->lineEditToEnd->setPrefix('='); + + ui->lineEditToStart->setText(QLatin1String(toStart.c_str())); + ui->lineEditToEnd->setText(QLatin1String(toEnd.c_str())); + + ui->comboBox->addItem(QString::fromLatin1(". (%1)").arg( + QString::fromUtf8(sheet->Label.getValue())), QByteArray("")); + + for(auto obj : sheet->getDocument()->getObjectsOfType()) { + if(obj == sheet) + continue; + QString label; + if(obj->Label.getStrValue() != obj->getNameInDocument()) + label = QString::fromLatin1("%1 (%2)").arg( + QString::fromLatin1(obj->getNameInDocument()), + QString::fromUtf8(obj->Label.getValue())); + else + label = QLatin1String(obj->getNameInDocument()); + ui->comboBox->addItem(label, QByteArray(obj->getNameInDocument())); + } + for(auto doc : GetApplication().getDocuments()) { + if(doc == sheet->getDocument()) + continue; + for(auto obj : sheet->getDocument()->getObjectsOfType()) { + if(obj == sheet) + continue; + std::string fullname = obj->getFullName(); + QString label; + if(obj->Label.getStrValue() != obj->getNameInDocument()) + label = QString::fromLatin1("%1 (%2)").arg( + QString::fromLatin1(fullname.c_str()), + QString::fromUtf8(obj->Label.getValue())); + else + label = QLatin1String(fullname.c_str()); + ui->comboBox->addItem(label, QByteArray(fullname.c_str())); + } + } + + connect(ui->btnDiscard, SIGNAL(clicked()), this, SLOT(onDiscard())); +} + +DlgBindSheet::~DlgBindSheet() +{ + delete ui; +} + +void DlgBindSheet::accept() +{ + bool commandActive = false; + try { + const char *ref = ui->comboBox->itemData(ui->comboBox->currentIndex()).toByteArray().constData(); + auto obj = sheet; + if(ref[0]) { + const char *sep = strchr(ref,'#'); + if(sep) { + std::string docname(ref,sep); + auto doc = GetApplication().getDocument(docname.c_str()); + if(!doc) + FC_THROWM(Base::RuntimeError, "Cannot find document " << docname); + obj = Base::freecad_dynamic_cast(doc->getObject(sep+1)); + } else + obj = Base::freecad_dynamic_cast(sheet->getDocument()->getObject(ref)); + if(!obj) + FC_THROWM(Base::RuntimeError, "Cannot find Spreadsheet '" << ref << "'"); + } + + std::string fromStart(ui->lineEditFromStart->text().trimmed().toLatin1().constData()); + std::string fromEnd(ui->lineEditFromEnd->text().trimmed().toLatin1().constData()); + + std::string toStart(ui->lineEditToStart->text().trimmed().toLatin1().constData()); + if(boost::starts_with(toStart,"=")) + toStart.erase(toStart.begin()); + else + toStart = std::string("<<") + toStart + ">>"; + + std::string toEnd(ui->lineEditToEnd->text().trimmed().toLatin1().constData()); + if(boost::starts_with(toEnd,"=")) + toEnd.erase(toEnd.begin()); + else + toEnd = std::string("<<") + toEnd + ">>"; + + Gui::Command::openCommand("Bind cells"); + commandActive = true; + + if(ui->checkBoxHREF->isChecked()) + Gui::cmdAppObjectArgs(sheet, + "setExpression('.cells.BindHiddenRef.%s.%s', 'hiddenref(tuple(%s.cells, %s, %s))')", + fromStart, fromEnd, ref, toStart, toEnd); + else + Gui::cmdAppObjectArgs(sheet, + "setExpression('.cells.Bind.%s.%s', 'tuple(%s.cells, %s, %s)')", + fromStart, fromEnd, ref, toStart, toEnd); + Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); + Gui::Command::commitCommand(); + QDialog::accept(); + } catch(Base::Exception &e) { + e.ReportException(); + QMessageBox::critical(this, tr("Bind cells"), QString::fromUtf8(e.what())); + if(commandActive) + Gui::Command::abortCommand(); + } +} + +void DlgBindSheet::onDiscard() { + try { + std::string fromStart(ui->lineEditFromStart->text().trimmed().toLatin1().constData()); + std::string fromEnd(ui->lineEditFromEnd->text().trimmed().toLatin1().constData()); + Gui::Command::openCommand("Unbind cells"); + Gui::cmdAppObjectArgs(sheet, "setExpression('.cells.Bind.%s.%s', None)", fromStart, fromEnd); + Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); + Gui::Command::commitCommand(); + reject(); + } catch(Base::Exception &e) { + e.ReportException(); + QMessageBox::critical(this, tr("Unbind cells"), QString::fromUtf8(e.what())); + Gui::Command::abortCommand(); + } +} + +#include "moc_DlgBindSheet.cpp" diff --git a/src/Mod/Spreadsheet/Gui/DlgBindSheet.h b/src/Mod/Spreadsheet/Gui/DlgBindSheet.h new file mode 100644 index 0000000000..9afe922e3f --- /dev/null +++ b/src/Mod/Spreadsheet/Gui/DlgBindSheet.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (c) 2019 Zheng, Lei (realthunder) * + * * + * 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 * + * * + ****************************************************************************/ + +#ifndef DLG_BINDSHEET_H +#define DLG_BINDSHEET_H + +#include +#include + +namespace Ui { +class DlgBindSheet; +} + +namespace SpreadsheetGui { + +class DlgBindSheet : public QDialog +{ + Q_OBJECT + +public: + explicit DlgBindSheet(Spreadsheet::Sheet *sheet, const std::vector &range, QWidget *parent = 0); + ~DlgBindSheet(); + + void accept(); + +public Q_SLOTS: + void onDiscard(); + +private: + Spreadsheet::Sheet * sheet; + App::Range range; + Ui::DlgBindSheet *ui; +}; + +} + +#endif // DLG_BINDSHEET_H diff --git a/src/Mod/Spreadsheet/Gui/DlgBindSheet.ui b/src/Mod/Spreadsheet/Gui/DlgBindSheet.ui new file mode 100644 index 0000000000..79eb651311 --- /dev/null +++ b/src/Mod/Spreadsheet/Gui/DlgBindSheet.ui @@ -0,0 +1,169 @@ + + + DlgBindSheet + + + + 0 + 0 + 387 + 176 + + + + Bind Spreadsheet Cells + + + + + + From cells: + + + + + + + Binding cell range start + + + + + + + Binding cell range end + + + + + + + + To cells: + + + + + + + Starting cell address to bind to. Type '=' if you want to use expression. +The expression must evaluates to a string of some cell address. + + + + + + + Ending cell address to bind to. Type '=' if you want to use expression. +The expression must evaluates to a string of some cell address. + + + + + + + Sheet: + + + + + + + Use hidden reference not avoid creating a depdenecy with the referenced object. Use with caution! + + + Use hidden reference + + + + + + + + + Unbind + + + + + + + Cancel + + + + + + + OK + + + true + + + + + + + + + Select which spread sheet to bind to. + + + + + + + + Gui::ExpressionLineEdit + QLineEdit +
Gui/ExpressionCompleter.h
+
+
+ + lineEditFromStart + lineEditFromEnd + lineEditToStart + lineEditToEnd + comboBox + checkBoxHREF + btnOk + btnCancel + btnDiscard + + + + + btnOk + clicked() + DlgBindSheet + accept() + + + 334 + 152 + + + 193 + 87 + + + + + btnCancel + clicked() + DlgBindSheet + reject() + + + 243 + 152 + + + 193 + 87 + + + + +
diff --git a/src/Mod/Spreadsheet/Gui/SheetModel.cpp b/src/Mod/Spreadsheet/Gui/SheetModel.cpp index c289dcc121..7047f004e7 100644 --- a/src/Mod/Spreadsheet/Gui/SheetModel.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetModel.cpp @@ -50,6 +50,7 @@ SheetModel::SheetModel(Sheet *_sheet, QObject *parent) , sheet(_sheet) { cellUpdatedConnection = sheet->cellUpdated.connect(bind(&SheetModel::cellUpdated, this, bp::_1)); + rangeUpdatedConnection = sheet->rangeUpdated.connect(bind(&SheetModel::rangeUpdated, this, bp::_1)); ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Spreadsheet"); aliasBgColor = QColor(Base::Tools::fromStdString(hGrp->GetASCII("AliasedCellBackgroundColor", "#feff9e"))); @@ -61,6 +62,7 @@ SheetModel::SheetModel(Sheet *_sheet, QObject *parent) SheetModel::~SheetModel() { cellUpdatedConnection.disconnect(); + rangeUpdatedConnection.disconnect(); } int SheetModel::rowCount(const QModelIndex &parent) const @@ -594,4 +596,12 @@ void SheetModel::cellUpdated(CellAddress address) dataChanged(i, i); } +void SheetModel::rangeUpdated(const Range &range) +{ + QModelIndex i = index(range.from().row(), range.from().col()); + QModelIndex j = index(range.to().row(), range.to().col()); + + dataChanged(i, j); +} + #include "moc_SheetModel.cpp" diff --git a/src/Mod/Spreadsheet/Gui/SheetModel.h b/src/Mod/Spreadsheet/Gui/SheetModel.h index 5e418ac067..f6caf69a26 100644 --- a/src/Mod/Spreadsheet/Gui/SheetModel.h +++ b/src/Mod/Spreadsheet/Gui/SheetModel.h @@ -50,8 +50,10 @@ public: private: void cellUpdated(App::CellAddress address); + void rangeUpdated(const App::Range &range); boost::signals2::scoped_connection cellUpdatedConnection; + boost::signals2::scoped_connection rangeUpdatedConnection; Spreadsheet::Sheet * sheet; QColor aliasBgColor; QColor textFgColor; diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index b77e0d12a4..5a7a2f673c 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -47,6 +47,7 @@ #include "SheetTableView.h" #include "LineEdit.h" #include "PropertiesDialog.h" +#include "DlgBindSheet.h" using namespace SpreadsheetGui; using namespace Spreadsheet; @@ -178,6 +179,13 @@ SheetTableView::SheetTableView(QWidget *parent) connect(recompute, SIGNAL(triggered()), this, SLOT(onRecompute())); contextMenu->addAction(recompute); + actionBind = new QAction(tr("Bind..."),this); + connect(actionBind, SIGNAL(triggered()), this, SLOT(onBind())); + contextMenu->addAction(actionBind); + + horizontalHeader()->addAction(actionBind); + verticalHeader()->addAction(actionBind); + contextMenu->addSeparator(); actionMerge = contextMenu->addAction(tr("Merge cells")); connect(actionMerge,SIGNAL(triggered()), this, SLOT(mergeCells())); @@ -206,6 +214,14 @@ void SheetTableView::onRecompute() { Gui::Command::commitCommand(); } +void SheetTableView::onBind() { + auto ranges = selectedRanges(); + if(ranges.size()>=1 && ranges.size()<=2) { + DlgBindSheet dlg(sheet,ranges,this); + dlg.exec(); + } +} + void SheetTableView::cellProperties() { std::unique_ptr dialog(new PropertiesDialog(sheet, selectedRanges(), this)); @@ -909,6 +925,9 @@ void SheetTableView::contextMenuEvent(QContextMenuEvent *) actionMerge->setEnabled(true); } + auto ranges = selectedRanges(); + actionBind->setEnabled(ranges.size()>=1 && ranges.size()<=2); + contextMenu->exec(QCursor::pos()); } diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.h b/src/Mod/Spreadsheet/Gui/SheetTableView.h index b8e6c62522..47ae6534e0 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.h +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.h @@ -81,6 +81,7 @@ protected Q_SLOTS: void removeColumns(); void cellProperties(); void onRecompute(); + void onBind(); protected: bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event); @@ -102,6 +103,7 @@ protected: QAction *actionPaste; QAction *actionCut; QAction *actionDel; + QAction *actionBind; boost::signals2::scoped_connection cellSpanChangedConnection; }; diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp index 6ced58e311..60e554fc7a 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp @@ -24,8 +24,8 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#include -#include +# include +# include #endif #include "SpreadsheetDelegate.h" @@ -33,11 +33,13 @@ #include #include #include +#include "DlgBindSheet.h" +using namespace Spreadsheet; using namespace SpreadsheetGui; SpreadsheetDelegate::SpreadsheetDelegate(Spreadsheet::Sheet * _sheet, QWidget *parent) - : QItemDelegate(parent) + : QStyledItemDelegate(parent) , sheet(_sheet) { } @@ -46,7 +48,13 @@ QWidget *SpreadsheetDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const { - Q_UNUSED(index) + App::CellAddress addr(index.row(),index.column()); + App::Range range(addr,addr); + if(sheet && sheet->getCellBinding(range)) { + FC_ERR("Bound cell " << addr.toString() << " cannot be edited"); + return 0; + } + SpreadsheetGui::LineEdit *editor = new SpreadsheetGui::LineEdit(parent); editor->setDocumentObject(sheet); connect(editor, &SpreadsheetGui::LineEdit::finishedWithKey, this, &SpreadsheetDelegate::on_editorFinishedWithKey); @@ -85,5 +93,32 @@ QSize SpreadsheetDelegate::sizeHint(const QStyleOptionViewItem & option, const Q return QSize(); } +void SpreadsheetDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, const QModelIndex &index ) const +{ + QStyledItemDelegate::paint(painter, option, index); + if(!sheet) + return; + unsigned flags = sheet->getCellBindingBorder(App::CellAddress(index.row(),index.column())); + if(!flags) + return; + QPen pen(Qt::blue); + pen.setWidth(1); + pen.setStyle(Qt::SolidLine); + painter->setPen(pen); + if(flags == Sheet::BorderAll) { + painter->drawRect(option.rect); + return; + } + if(flags & Sheet::BorderLeft) + painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft()); + if(flags & Sheet::BorderTop) + painter->drawLine(option.rect.topLeft(), option.rect.topRight()); + if(flags & Sheet::BorderRight) + painter->drawLine(option.rect.topRight(), option.rect.bottomRight()); + if(flags & Sheet::BorderBottom) + painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight()); +} + #include "moc_SpreadsheetDelegate.cpp" diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h index fe4efc7855..2a477b1cb1 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h @@ -25,7 +25,7 @@ #ifndef SPREADSHEETDELEGATE_H #define SPREADSHEETDELEGATE_H -#include +#include namespace Spreadsheet { class Sheet; @@ -33,7 +33,7 @@ class Sheet; namespace SpreadsheetGui { -class SpreadsheetDelegate : public QItemDelegate +class SpreadsheetDelegate : public QStyledItemDelegate { Q_OBJECT public: @@ -45,8 +45,11 @@ public: const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const; + Q_SIGNALS: void finishedWithKey(int key, Qt::KeyboardModifiers modifiers); + private Q_SLOTS: void on_editorFinishedWithKey(int key, Qt::KeyboardModifiers modifiers); private: