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:
@@ -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<Sheet>(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<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);
|
||||
@@ -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<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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<CellAddress> dirtyCells = cells.getDirty();
|
||||
|
||||
@@ -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<void (App::CellAddress)> cellUpdated;
|
||||
|
||||
boost::signals2::signal<void (App::Range)> rangeUpdated;
|
||||
|
||||
boost::signals2::signal<void (App::CellAddress)> cellSpanChanged;
|
||||
|
||||
boost::signals2::signal<void (int, int)> columnWidthChanged;
|
||||
@@ -259,6 +273,8 @@ protected:
|
||||
int currentRow = -1;
|
||||
int currentCol = -1;
|
||||
|
||||
std::vector<App::Range> boundRanges;
|
||||
|
||||
friend class SheetObserver;
|
||||
|
||||
friend class PropertySheet;
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
|
||||
|
||||
202
src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp
Normal file
202
src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2019 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
|
||||
* *
|
||||
* 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 <boost/algorithm/string/predicate.hpp>
|
||||
#include <QMessageBox>
|
||||
#include "DlgBindSheet.h"
|
||||
#include <Base/Tools.h>
|
||||
#include <App/Range.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/Application.h>
|
||||
#include <App/ExpressionParser.h>
|
||||
#include <Gui/CommandT.h>
|
||||
#include "ui_DlgBindSheet.h"
|
||||
|
||||
using namespace App;
|
||||
using namespace Spreadsheet;
|
||||
using namespace SpreadsheetGui;
|
||||
|
||||
DlgBindSheet::DlgBindSheet(Sheet *sheet, const std::vector<Range> &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<StringExpression*>(pStart.get())->getText();
|
||||
else {
|
||||
toStart = "=";
|
||||
toStart += pStart->toString();
|
||||
}
|
||||
if(!pEnd->hasComponent() && pEnd->isDerivedFrom(StringExpression::getClassTypeId()))
|
||||
toEnd = static_cast<StringExpression*>(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<Sheet>()) {
|
||||
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<Sheet>()) {
|
||||
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<Sheet>(doc->getObject(sep+1));
|
||||
} else
|
||||
obj = Base::freecad_dynamic_cast<Sheet>(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"
|
||||
56
src/Mod/Spreadsheet/Gui/DlgBindSheet.h
Normal file
56
src/Mod/Spreadsheet/Gui/DlgBindSheet.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2019 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
|
||||
* *
|
||||
* 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 <QDialog>
|
||||
#include <Mod/Spreadsheet/App/Sheet.h>
|
||||
|
||||
namespace Ui {
|
||||
class DlgBindSheet;
|
||||
}
|
||||
|
||||
namespace SpreadsheetGui {
|
||||
|
||||
class DlgBindSheet : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DlgBindSheet(Spreadsheet::Sheet *sheet, const std::vector<App::Range> &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
|
||||
169
src/Mod/Spreadsheet/Gui/DlgBindSheet.ui
Normal file
169
src/Mod/Spreadsheet/Gui/DlgBindSheet.ui
Normal file
@@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DlgBindSheet</class>
|
||||
<widget class="QDialog" name="DlgBindSheet">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>387</width>
|
||||
<height>176</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Bind Spreadsheet Cells</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>From cells:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lineEditFromStart">
|
||||
<property name="toolTip">
|
||||
<string>Binding cell range start</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="lineEditFromEnd">
|
||||
<property name="toolTip">
|
||||
<string>Binding cell range end
|
||||
</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>To cells:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::ExpressionLineEdit" name="lineEditToStart">
|
||||
<property name="toolTip">
|
||||
<string>Starting cell address to bind to. Type '=' if you want to use expression.
|
||||
The expression must evaluates to a string of some cell address.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="Gui::ExpressionLineEdit" name="lineEditToEnd">
|
||||
<property name="toolTip">
|
||||
<string>Ending cell address to bind to. Type '=' if you want to use expression.
|
||||
The expression must evaluates to a string of some cell address.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Sheet:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBoxHREF">
|
||||
<property name="toolTip">
|
||||
<string>Use hidden reference not avoid creating a depdenecy with the referenced object. Use with caution!</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use hidden reference</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnDiscard">
|
||||
<property name="text">
|
||||
<string>Unbind</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnCancel">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnOk">
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
<property name="toolTip">
|
||||
<string>Select which spread sheet to bind to.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::ExpressionLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header location="global">Gui/ExpressionCompleter.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>lineEditFromStart</tabstop>
|
||||
<tabstop>lineEditFromEnd</tabstop>
|
||||
<tabstop>lineEditToStart</tabstop>
|
||||
<tabstop>lineEditToEnd</tabstop>
|
||||
<tabstop>comboBox</tabstop>
|
||||
<tabstop>checkBoxHREF</tabstop>
|
||||
<tabstop>btnOk</tabstop>
|
||||
<tabstop>btnCancel</tabstop>
|
||||
<tabstop>btnDiscard</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>btnOk</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>DlgBindSheet</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>334</x>
|
||||
<y>152</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>193</x>
|
||||
<y>87</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>btnCancel</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>DlgBindSheet</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>243</x>
|
||||
<y>152</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>193</x>
|
||||
<y>87</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<PropertiesDialog> 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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
|
||||
#include "PreCompiled.h"
|
||||
#ifndef _PreComp_
|
||||
#include <QItemDelegate>
|
||||
#include <QLineEdit>
|
||||
# include <QLineEdit>
|
||||
# include <QPainter>
|
||||
#endif
|
||||
|
||||
#include "SpreadsheetDelegate.h"
|
||||
@@ -33,11 +33,13 @@
|
||||
#include <App/DocumentObject.h>
|
||||
#include <Mod/Spreadsheet/App/Sheet.h>
|
||||
#include <Gui/ExpressionCompleter.h>
|
||||
#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"
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#ifndef SPREADSHEETDELEGATE_H
|
||||
#define SPREADSHEETDELEGATE_H
|
||||
|
||||
#include <QItemDelegate>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user