From 864f6d96a6e734968a2dab3334b7e24e460d62f8 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 27 Dec 2019 09:31:13 +0800 Subject: [PATCH 1/6] App: add expression built-in function str() ...for stringify --- src/App/Expression.cpp | 63 +++++++++++++++++++++++++++++++++++++- src/App/ExpressionParser.h | 1 + 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/App/Expression.cpp b/src/App/Expression.cpp index 8149ce708b..d7d69884fc 100644 --- a/src/App/Expression.cpp +++ b/src/App/Expression.cpp @@ -129,7 +129,7 @@ FC_LOG_LEVEL_INIT("Expression",true,true) static inline std::ostream &operator<<(std::ostream &os, const App::Expression *expr) { if(expr) { - os << std::endl; + os << "\nin expression: "; expr->toString(os); } return os; @@ -1715,6 +1715,62 @@ FunctionExpression::FunctionExpression(const DocumentObject *_owner, Function _f , fname(std::move(name)) , args(_args) { + switch (f) { + case ACOS: + case ASIN: + case ATAN: + case ABS: + case EXP: + case LOG: + case LOG10: + case SIN: + case SINH: + case TAN: + case TANH: + case SQRT: + case COS: + case COSH: + case ROUND: + case TRUNC: + case CEIL: + case FLOOR: + case MINVERT: + case STR: + if (args.size() != 1) + EXPR_THROW("Invalid number of arguments: exactly one required."); + break; + case MOD: + case ATAN2: + case POW: + if (args.size() != 2) + EXPR_THROW("Invalid number of arguments: exactly two required."); + break; + case HYPOT: + case CATH: + if (args.size() < 2 || args.size() > 3) + EXPR_THROW("Invalid number of arguments: exactly two, or three required."); + break; + case STDDEV: + case SUM: + case AVERAGE: + case COUNT: + case MIN: + case MAX: + case CREATE: + case MSCALE: + if (args.size() == 0) + EXPR_THROW("Invalid number of arguments: at least one required."); + break; + case LIST: + case TUPLE: + break; + case NONE: + case AGGREGATES: + case LAST: + default: + PARSER_THROW("Unknown function"); + break; + } } FunctionExpression::~FunctionExpression() @@ -2029,6 +2085,8 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std PyObjectBase::__PyInit(res.ptr(),tuple.ptr(),dict.ptr()); } return res; + } else if (f == STR) { + return Py::String(args[0]->getPyValue().as_string()); } Py::Object e1 = args[0]->getPyValue(); @@ -2361,6 +2419,8 @@ void FunctionExpression::_toString(std::ostream &ss, bool persistent,int) const ss << "minvert("; break;; case CREATE: ss << "create("; break;; + case STR: + ss << "str("; break;; default: ss << fname << "("; break;; } @@ -3182,6 +3242,7 @@ static void initParser(const App::DocumentObject *owner) registered_functions["mscale"] = FunctionExpression::MSCALE; registered_functions["minvert"] = FunctionExpression::MINVERT; registered_functions["create"] = FunctionExpression::CREATE; + registered_functions["str"] = FunctionExpression::STR; // Aggregates registered_functions["sum"] = FunctionExpression::SUM; diff --git a/src/App/ExpressionParser.h b/src/App/ExpressionParser.h index 0e3b9f8a95..e0dfe1ee0d 100644 --- a/src/App/ExpressionParser.h +++ b/src/App/ExpressionParser.h @@ -272,6 +272,7 @@ public: MSCALE, // matrix scale by vector MINVERT, // invert matrix/placement/rotation CREATE, // create new object of a given type + STR, // stringify // Aggregates AGGREGATES, From 49074f5af11a1216c632c642438e1a040b2138ac Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 24 Dec 2019 10:34:50 +0800 Subject: [PATCH 2/6] Spreadsheet: add a few context menu options --- src/Mod/Spreadsheet/App/Sheet.cpp | 6 + src/Mod/Spreadsheet/App/Sheet.h | 2 + src/Mod/Spreadsheet/App/SheetPy.xml | 15 +++ src/Mod/Spreadsheet/App/SheetPyImp.cpp | 128 +++++++++++++++++---- src/Mod/Spreadsheet/Gui/SheetTableView.cpp | 73 +++++++++++- src/Mod/Spreadsheet/Gui/SheetTableView.h | 20 +++- 6 files changed, 214 insertions(+), 30 deletions(-) diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index 561e88c303..61e4c2b17b 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -792,6 +792,12 @@ void Sheet::touchCells(Range range) { }while(range.next()); } +void Sheet::recomputeCells(Range range) { + do { + recomputeCell(*range); + }while(range.next()); +} + /** * @brief Recompute cell at address \a p. * @param p Address of cell. diff --git a/src/Mod/Spreadsheet/App/Sheet.h b/src/Mod/Spreadsheet/App/Sheet.h index 18e898b849..d1e7324773 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -179,6 +179,8 @@ public: void touchCells(App::Range range); + void recomputeCells(App::Range range); + // Signals boost::signals2::signal cellUpdated; diff --git a/src/Mod/Spreadsheet/App/SheetPy.xml b/src/Mod/Spreadsheet/App/SheetPy.xml index 679371b665..a2c4bf6330 100644 --- a/src/Mod/Spreadsheet/App/SheetPy.xml +++ b/src/Mod/Spreadsheet/App/SheetPy.xml @@ -165,5 +165,20 @@ Get given spreadsheet row height + + + touchCells(from, to=None): touch cells in the given range + + + + + +recomputeCells(from, to=None) + +Manually recompute cells in the given range with the given order without +following depedency order. + + + diff --git a/src/Mod/Spreadsheet/App/SheetPyImp.cpp b/src/Mod/Spreadsheet/App/SheetPyImp.cpp index 221de6a1c9..815f0c96a5 100644 --- a/src/Mod/Spreadsheet/App/SheetPyImp.cpp +++ b/src/Mod/Spreadsheet/App/SheetPyImp.cpp @@ -91,15 +91,41 @@ PyObject* SheetPy::set(PyObject *args) PyObject* SheetPy::get(PyObject *args) { - char *address; + const char *address; + const char *address2=0; - if (!PyArg_ParseTuple(args, "s:get", &address)) + if (!PyArg_ParseTuple(args, "s|s:get", &address, &address2)) return 0; + PY_TRY { + if(address2) { + auto a1 = getSheetPtr()->getAddressFromAlias(address); + if(a1.empty()) + a1 = address; + auto a2 = getSheetPtr()->getAddressFromAlias(address2); + if(a2.empty()) + a2 = address2; + Range range(a1.c_str(),a2.c_str()); + Py::Tuple tuple(range.size()); + int i=0; + do { + App::Property *prop = getSheetPtr()->getPropertyByName(range.address().c_str()); + if(!prop) { + PyErr_Format(PyExc_ValueError, "Invalid address '%s' in range %s:%s", + range.address().c_str(), address, address2); + return 0; + } + tuple.setItem(i++,Py::Object(prop->getPyObject(),true)); + }while(range.next()); + return Py::new_reference_to(tuple); + } + }PY_CATCH; + App::Property * prop = this->getSheetPtr()->getPropertyByName(address); if (prop == 0) { - PyErr_SetString(PyExc_ValueError, "Invalid address or property."); + PyErr_Format(PyExc_ValueError, + "Invalid cell address or property: %s",address); return 0; } return prop->getPyObject(); @@ -113,21 +139,29 @@ PyObject* SheetPy::getContents(PyObject *args) if (!PyArg_ParseTuple(args, "s:getContents", &strAddress)) return 0; - try { - address = stringToAddress(strAddress); - } - catch (const Base::Exception & e) { - PyErr_SetString(PyExc_ValueError, e.what()); - return 0; - } + PY_TRY { + try { + Sheet * sheet = getSheetPtr(); + std::string addr = sheet->getAddressFromAlias(strAddress); - std::string contents; - const Cell * cell = this->getSheetPtr()->getCell(address); + if (addr.empty()) + address = stringToAddress(strAddress); + else + address = stringToAddress(addr.c_str()); + } + catch (const Base::Exception & e) { + PyErr_SetString(PyExc_ValueError, e.what()); + return 0; + } - if (cell) - cell->getStringContent( contents ); + std::string contents; + const Cell * cell = this->getSheetPtr()->getCell(address); - return Py::new_reference_to( Py::String( contents ) ); + if (cell) + cell->getStringContent( contents ); + + return Py::new_reference_to( Py::String( contents ) ); + } PY_CATCH } PyObject* SheetPy::clear(PyObject *args) @@ -587,8 +621,10 @@ PyObject* SheetPy::setAlignment(PyObject *args) std::string line = PyUnicode_AsUTF8(value); tokenizer > tok(line, e); - for(tokenizer >::iterator i = tok.begin(); i != tok.end();++i) - alignment = Cell::decodeAlignment(*i, alignment); + for(tokenizer >::iterator i = tok.begin(); i != tok.end();++i) { + if(i->size()) + alignment = Cell::decodeAlignment(*i, alignment); + } } else { std::string error = std::string("style must be either set or string, not ") + value->ob_type->tp_name; @@ -903,15 +939,61 @@ PyObject* SheetPy::getRowHeight(PyObject *args) } } +PyObject *SheetPy::touchCells(PyObject *args) { + const char *address; + const char *address2=0; + + if (!PyArg_ParseTuple(args, "s|s:touchCells", &address, &address2)) + return 0; + + PY_TRY { + std::string a1 = getSheetPtr()->getAddressFromAlias(address); + if(a1.empty()) + a1 = address; + + std::string a2; + if(!address2) { + a2 = a1; + } else { + a2 = getSheetPtr()->getAddressFromAlias(address2); + if(a2.empty()) + a2 = address2; + } + getSheetPtr()->touchCells(Range(a1.c_str(),a2.c_str())); + Py_Return; + }PY_CATCH; +} + +PyObject *SheetPy::recomputeCells(PyObject *args) { + const char *address; + const char *address2=0; + + if (!PyArg_ParseTuple(args, "s|s:touchCells", &address, &address2)) + return 0; + + PY_TRY { + std::string a1 = getSheetPtr()->getAddressFromAlias(address); + if(a1.empty()) + a1 = address; + + std::string a2; + if(!address2) { + a2 = a1; + } else { + a2 = getSheetPtr()->getAddressFromAlias(address2); + if(a2.empty()) + a2 = address2; + } + getSheetPtr()->recomputeCells(Range(a1.c_str(),a2.c_str())); + Py_Return; + }PY_CATCH; +} + // +++ custom attributes implementer ++++++++++++++++++++++++++++++++++++++++ -PyObject *SheetPy::getCustomAttributes(const char* attr) const +PyObject *SheetPy::getCustomAttributes(const char*) const { - App::Property * prop = this->getSheetPtr()->getPropertyByName(attr); - - if (prop == 0) - return 0; - return prop->getPyObject(); + return 0; } int SheetPy::setCustomAttributes(const char* , PyObject* ) diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index 86b532213e..634a30e815 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -24,6 +24,7 @@ #ifndef _PreComp_ # include # include +# include # include # include # include @@ -35,6 +36,7 @@ #include #include #include +#include #include #include #include "../App/Utils.h" @@ -108,9 +110,6 @@ SheetTableView::SheetTableView(QWidget *parent) setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); - horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); - verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); - connect(verticalHeader(), &QWidget::customContextMenuRequested, [this](const QPoint &point){ QMenu menu(this); @@ -163,11 +162,46 @@ SheetTableView::SheetTableView(QWidget *parent) auto cellProperties = new QAction(tr("Properties..."), this); addAction(cellProperties); + + horizontalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu); + verticalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu); - setContextMenuPolicy(Qt::ActionsContextMenu); - setTabKeyNavigation(false); + contextMenu = new QMenu(this); + contextMenu->addAction(cellProperties); connect(cellProperties, SIGNAL(triggered()), this, SLOT(cellProperties())); + + contextMenu->addSeparator(); + QAction *recompute = new QAction(tr("Recompute"),this); + connect(recompute, SIGNAL(triggered()), this, SLOT(onRecompute())); + contextMenu->addAction(recompute); + + contextMenu->addSeparator(); + actionMerge = contextMenu->addAction(tr("Merge cells")); + connect(actionMerge,SIGNAL(triggered()), this, SLOT(mergeCells())); + actionSplit = contextMenu->addAction(tr("Split cells")); + connect(actionSplit,SIGNAL(triggered()), this, SLOT(splitCell())); + + contextMenu->addSeparator(); + actionCut = contextMenu->addAction(tr("Cut")); + connect(actionCut,SIGNAL(triggered()), this, SLOT(cutSelection())); + actionCopy = contextMenu->addAction(tr("Copy")); + connect(actionCopy,SIGNAL(triggered()), this, SLOT(copySelection())); + actionPaste = contextMenu->addAction(tr("Paste")); + connect(actionPaste,SIGNAL(triggered()), this, SLOT(pasteClipboard())); + actionDel = contextMenu->addAction(tr("Delete")); + connect(actionDel,SIGNAL(triggered()), this, SLOT(deleteSelection())); + + setTabKeyNavigation(false); +} + +void SheetTableView::onRecompute() { + Gui::Command::openCommand("Recompute cells"); + for(auto &range : selectedRanges()) { + Gui::cmdAppObjectArgs(sheet, "recomputeCells('%s', '%s')", + range.fromCellString(), range.toCellString()); + } + Gui::Command::commitCommand(); } void SheetTableView::cellProperties() @@ -828,6 +862,14 @@ void SheetTableView::ModifyBlockSelection(int targetRow, int targetCol) this->selectionModel()->setCurrentIndex(model()->index(targetRow, targetCol), QItemSelectionModel::Current); } +void SheetTableView::mergeCells() { + Gui::Application::Instance->commandManager().runCommandByName("Spreadsheet_MergeCells"); +} + +void SheetTableView::splitCell() { + Gui::Application::Instance->commandManager().runCommandByName("Spreadsheet_SplitCell"); +} + void SheetTableView::closeEditor(QWidget * editor, QAbstractItemDelegate::EndEditHint hint) { QTableView::closeEditor(editor, hint); @@ -845,4 +887,25 @@ void SheetTableView::edit ( const QModelIndex & index ) QTableView::edit(index); } +void SheetTableView::contextMenuEvent(QContextMenuEvent *) { + const QMimeData* mimeData = QApplication::clipboard()->mimeData(); + if(!selectionModel()->hasSelection()) { + actionCut->setEnabled(false); + actionCopy->setEnabled(false); + actionDel->setEnabled(false); + actionPaste->setEnabled(false); + actionSplit->setEnabled(false); + actionMerge->setEnabled(false); + }else{ + actionPaste->setEnabled(mimeData && (mimeData->hasText() || mimeData->hasText())); + actionCut->setEnabled(true); + actionCopy->setEnabled(true); + actionDel->setEnabled(true); + actionSplit->setEnabled(true); + actionMerge->setEnabled(true); + } + + contextMenu->exec(QCursor::pos()); +} + #include "moc_SheetTableView.cpp" diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.h b/src/Mod/Spreadsheet/Gui/SheetTableView.h index 828856fd4f..840a4961e2 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.h +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.h @@ -58,12 +58,15 @@ public: void edit(const QModelIndex &index); void setSheet(Spreadsheet::Sheet *_sheet); std::vector selectedRanges() const; + +public Q_SLOTS: + void mergeCells(); + void splitCell(); void deleteSelection(); void copySelection(); void cutSelection(); void pasteClipboard(); - void finishEditWithMove(int keyPressed, Qt::KeyboardModifiers modifiers, bool handleTabMotion = false); - + void finishEditWithMove(int keyPressed, Qt::KeyboardModifiers modifiers, bool handleTabMotion = false); void ModifyBlockSelection(int targetRow, int targetColumn); protected Q_SLOTS: @@ -76,16 +79,29 @@ protected Q_SLOTS: void insertColumnsAfter(); void removeColumns(); void cellProperties(); + void onRecompute(); + protected: bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event); bool event(QEvent *event); void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint); void mousePressEvent(QMouseEvent* event); + void contextMenuEvent (QContextMenuEvent * e); + QModelIndex currentEditIndex; Spreadsheet::Sheet * sheet; int tabCounter; + QMenu *contextMenu; + + QAction *actionMerge; + QAction *actionSplit; + QAction *actionCopy; + QAction *actionPaste; + QAction *actionCut; + QAction *actionDel; + boost::signals2::scoped_connection cellSpanChangedConnection; }; From 2a62233641637733aaf68eb4f234c9d1e1d29c0d Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 26 Dec 2019 18:34:05 +0800 Subject: [PATCH 3/6] Gui: handle exception in property item display --- src/Gui/propertyeditor/PropertyItem.cpp | 92 +++++++++++++++---------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index e94075eb73..1fd77d65b8 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -347,49 +347,65 @@ QVariant PropertyItem::toString(const QVariant& prop) const if (prop != QVariant() || propertyItems.size()!=1) return prop; - Base::PyGILStateLocker lock; - Py::Object pyobj(propertyItems[0]->getPyObject(), true); std::ostringstream ss; - if (pyobj.isNone()) { - ss << ""; - } - else if(pyobj.isSequence()) { - ss << '['; - Py::Sequence seq(pyobj); - bool first = true; - Py_ssize_t i=0; - for (i=0; i<2 && i < seq.size(); ++i) { - if (first) - first = false; - else - ss << ", "; - ss << Py::Object(seq[i]).as_string(); + Base::PyGILStateLocker lock; + try { + Base::PyGILStateLocker lock; + Py::Object pyobj(propertyItems[0]->getPyObject(), true); + std::ostringstream ss; + if (pyobj.isNone()) { + ss << ""; } + else if(pyobj.isSequence()) { + ss << '['; + Py::Sequence seq(pyobj); + bool first = true; + Py_ssize_t i=0; + for (i=0; i<2 && i < seq.size(); ++i) { + if (first) + first = false; + else + ss << ", "; + ss << Py::Object(seq[i]).as_string(); + } - if (i < seq.size()) - ss << "..."; - ss << ']'; - } - else if (pyobj.isMapping()) { - ss << '{'; - Py::Mapping map(pyobj); - bool first = true; - auto it = map.begin(); - for(int i=0; i<2 && it != map.end(); ++it, ++i) { - if (first) - first = false; - else - ss << ", "; - const auto &v = *it; - ss << Py::Object(v.first).as_string() << ':' << Py::Object(v.second).as_string(); + if (i < seq.size()) + ss << "..."; + ss << ']'; } + else if (pyobj.isMapping()) { + ss << '{'; + Py::Mapping map(pyobj); + bool first = true; + auto it = map.begin(); + for(int i=0; i<2 && it != map.end(); ++it, ++i) { + if (first) + first = false; + else + ss << ", "; + const auto &v = *it; + ss << Py::Object(v.first).as_string() << ':' << Py::Object(v.second).as_string(); + } - if (it != map.end()) - ss << "..."; - ss << '}'; - } - else { - ss << pyobj.as_string(); + if (it != map.end()) + ss << "..."; + ss << '}'; + } + else + ss << pyobj.as_string(); + } catch (Py::Exception &) { + Base::PyException e; + ss.str(""); + ss << "ERR: " << e.what(); + } catch (Base::Exception &e) { + ss.str(""); + ss << "ERR: " << e.what(); + } catch (std::exception &e) { + ss.str(""); + ss << "ERR: " << e.what(); + } catch (...) { + ss.str(""); + ss << "ERR!"; } return QVariant(QString::fromUtf8(ss.str().c_str())); From 8e51d7a86dbf7de4bbba88891f9734f90601f2f6 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 26 Dec 2019 18:52:46 +0800 Subject: [PATCH 4/6] Spreadsheet: preserve component reference in cell --- src/Mod/Spreadsheet/App/Cell.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Spreadsheet/App/Cell.cpp b/src/Mod/Spreadsheet/App/Cell.cpp index f58fbb89c6..0b23d6eb83 100644 --- a/src/Mod/Spreadsheet/App/Cell.cpp +++ b/src/Mod/Spreadsheet/App/Cell.cpp @@ -247,7 +247,10 @@ const App::Expression *Cell::getExpression(bool withFormat) const bool Cell::getStringContent(std::string & s, bool persistent) const { if (expression) { - if (freecad_dynamic_cast(expression.get())) { + s.clear(); + if(expression->hasComponent()) + s = "=" + expression->toString(persistent); + else if (freecad_dynamic_cast(expression.get())) { s = static_cast(expression.get())->getText(); char * end; errno = 0; From 64053912b1f71612b6a9ebe5299ebac8e3896a7f Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 26 Dec 2019 19:03:41 +0800 Subject: [PATCH 5/6] Spreadsheet: support displaying of integer --- src/Mod/Spreadsheet/Gui/SheetModel.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Mod/Spreadsheet/Gui/SheetModel.cpp b/src/Mod/Spreadsheet/Gui/SheetModel.cpp index 7ac9611fdd..341466214f 100644 --- a/src/Mod/Spreadsheet/Gui/SheetModel.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetModel.cpp @@ -392,10 +392,15 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const { /* Number */ double d; + long l; + bool isInteger = false; if(prop->isDerivedFrom(App::PropertyFloat::getClassTypeId())) d = static_cast(prop)->getValue(); - else - d = static_cast(prop)->getValue(); + else { + isInteger = true; + l = static_cast(prop)->getValue(); + d = l; + } switch (role) { case Qt::ForegroundRole: { @@ -431,10 +436,11 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const //QString number = QString::number(d / displayUnit.scaler); v = number + Base::Tools::fromStdString(" " + displayUnit.stringRep); } - else { - v = QLocale().toString(d,'f',Base::UnitsApi::getDecimals()); + else if (!isInteger) { + v = QLocale::system().toString(d,'f',Base::UnitsApi::getDecimals()); //v = QString::number(d); - } + } else + v = QString::number(l); return QVariant(v); } default: From 9933d4fbcef08cdc7063c0160a1e36797439bc95 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 23 Oct 2019 07:22:33 +0800 Subject: [PATCH 6/6] App: add signalChanged to Property For more efficient tracking of single property changes --- src/App/Property.cpp | 8 +++++++- src/App/Property.h | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/App/Property.cpp b/src/App/Property.cpp index 8f2142fd52..d4571eadd6 100644 --- a/src/App/Property.cpp +++ b/src/App/Property.cpp @@ -32,6 +32,7 @@ #include "ObjectIdentifier.h" #include "PropertyContainer.h" #include +#include #include "Application.h" #include "DocumentObject.h" @@ -212,8 +213,13 @@ void Property::setReadOnly(bool readOnly) void Property::hasSetValue(void) { PropertyCleaner guard(this); - if (father) + if (father) { father->onChanged(this); + if(!testStatus(Busy)) { + Base::BitsetLocker guard(StatusBits,Busy); + signalChanged(*this); + } + } StatusBits.set(Touched); } diff --git a/src/App/Property.h b/src/App/Property.h index 7a1cc64944..9a33b6dad8 100644 --- a/src/App/Property.h +++ b/src/App/Property.h @@ -31,6 +31,7 @@ #include #include #include +#include namespace Py { class Object; @@ -75,6 +76,7 @@ public: // relevant for the container using it EvalOnRestore = 14, // In case of expression binding, evaluate the // expression on restore and touch the object on value change. + Busy = 15, // internal use to avoid recursive signaling // The following bits are corresponding to PropertyType set when the // property added. These types are meant to be static, and cannot be @@ -269,6 +271,9 @@ private: private: PropertyContainer *father; const char *myName; + +public: + boost::signals2::signal signalChanged; };