Spreadsheet: improve range binding feature (#6995)
* Spreadsheet: improve range binding feature
This commit is contained in:
@@ -1766,8 +1766,11 @@ bool PropertySheet::isBindingPath(const ObjectIdentifier &path,
|
||||
return true;
|
||||
}
|
||||
|
||||
PropertySheet::BindingType PropertySheet::getBinding(
|
||||
const Range &range, ExpressionPtr *pStart, ExpressionPtr *pEnd) const
|
||||
PropertySheet::BindingType
|
||||
PropertySheet::getBinding(const Range &range,
|
||||
ExpressionPtr *pStart,
|
||||
ExpressionPtr *pEnd,
|
||||
App::ObjectIdentifier *pTarget) const
|
||||
{
|
||||
if(!owner)
|
||||
return BindingNone;
|
||||
@@ -1791,6 +1794,10 @@ PropertySheet::BindingType PropertySheet::getBinding(
|
||||
}
|
||||
|
||||
if(expr->getFunction() == FunctionExpression::TUPLE && expr->getArgs().size()==3) {
|
||||
if (pTarget) {
|
||||
if (auto e = Base::freecad_dynamic_cast<VariableExpression>(expr->getArgs()[0]))
|
||||
*pTarget = e->getPath();
|
||||
}
|
||||
if(pStart)
|
||||
pStart->reset(expr->getArgs()[1]->copy());
|
||||
if(pEnd)
|
||||
@@ -1837,71 +1844,123 @@ void PropertySheet::setPathValue(const ObjectIdentifier &path, const boost::any
|
||||
App::CellAddress targetTo = other->getCellAddress(
|
||||
Py::Object(seq[2].ptr()).as_string().c_str(), false);
|
||||
|
||||
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();
|
||||
expr += otherOwner->getNameInDocument();
|
||||
else
|
||||
expr = otherOwner->getFullName();
|
||||
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;
|
||||
auto normalize = [](CellAddress &from, CellAddress &to) {
|
||||
if (from.row() > to.row()) {
|
||||
int tmp = from.row();
|
||||
from.setRow(to.row());
|
||||
to.setRow(tmp);
|
||||
}
|
||||
if (from.col() > to.col()) {
|
||||
int tmp = from.col();
|
||||
from.setCol(to.col());
|
||||
to.setCol(tmp);
|
||||
}
|
||||
};
|
||||
|
||||
Cell *dst = other->getValue(target);
|
||||
Cell *src = getValue(source);
|
||||
if(!dst) {
|
||||
if(src) {
|
||||
normalize(from, to);
|
||||
normalize(targetFrom, targetTo);
|
||||
App::Range totalRange(from, to);
|
||||
std::set<CellAddress> touched;
|
||||
|
||||
while(from.row() <= to.row()
|
||||
&& from.col() <= to.col()
|
||||
&& targetFrom.row() <= targetTo.row()
|
||||
&& targetFrom.col() <= targetTo.col())
|
||||
{
|
||||
App::Range range(from, to);
|
||||
App::Range rangeTarget(targetFrom, targetTo);
|
||||
int rowCount = std::min(range.rowCount(), rangeTarget.rowCount());
|
||||
int colCount = std::min(range.colCount(), rangeTarget.colCount());
|
||||
if (rowCount == range.rowCount())
|
||||
from.setCol(from.col() + colCount);
|
||||
else if (colCount == range.colCount())
|
||||
from.setRow(from.row() + rowCount);
|
||||
if (rowCount == rangeTarget.rowCount())
|
||||
targetFrom.setCol(targetFrom.col() + colCount);
|
||||
else if (colCount == rangeTarget.colCount())
|
||||
targetFrom.setRow(targetFrom.row() + rowCount);
|
||||
|
||||
range = App::Range(range.from().row(),
|
||||
range.from().col(),
|
||||
range.from().row()+rowCount-1,
|
||||
range.from().col()+colCount-1);
|
||||
rangeTarget = App::Range(rangeTarget.from().row(),
|
||||
rangeTarget.from().col(),
|
||||
rangeTarget.from().row()+rowCount-1,
|
||||
rangeTarget.from().col()+colCount-1);
|
||||
do {
|
||||
CellAddress target(*rangeTarget);
|
||||
CellAddress source(*range);
|
||||
if(other == this && source.row() >= rangeTarget.from().row()
|
||||
&& source.row() <= rangeTarget.to().row()
|
||||
&& source.col() >= rangeTarget.from().col()
|
||||
&& source.col() <= rangeTarget.to().col())
|
||||
continue;
|
||||
|
||||
Cell *dst = other->getValue(target);
|
||||
Cell *src = getValue(source);
|
||||
if(!dst)
|
||||
continue;
|
||||
|
||||
touched.insert(source);
|
||||
|
||||
if(!src) {
|
||||
signaller.aboutToChange();
|
||||
owner->clear(source);
|
||||
owner->cellUpdated(source);
|
||||
src = createCell(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);
|
||||
}
|
||||
}
|
||||
|
||||
std::string alias;
|
||||
if(this!=other && dst->getAlias(alias)) {
|
||||
auto *oldCell = getValueFromAlias(alias);
|
||||
if(oldCell && oldCell!=dst) {
|
||||
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();
|
||||
oldCell->setAlias("");
|
||||
src->setExpression(std::move(e));
|
||||
}
|
||||
std::string oldAlias;
|
||||
if(!src->getAlias(oldAlias) || oldAlias!=alias) {
|
||||
|
||||
} while(range.next() && rangeTarget.next());
|
||||
}
|
||||
|
||||
if (totalRange.size() != (int)touched.size()) {
|
||||
do {
|
||||
CellAddress addr(*totalRange);
|
||||
if (touched.count(addr))
|
||||
continue;
|
||||
Cell *src = getValue(addr);
|
||||
if (src && src->getExpression()) {
|
||||
signaller.aboutToChange();
|
||||
setAlias(source,alias);
|
||||
src->setExpression(nullptr);
|
||||
}
|
||||
}
|
||||
} while(totalRange.next());
|
||||
}
|
||||
|
||||
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);
|
||||
owner->rangeUpdated(totalRange);
|
||||
signaller.tryInvoke();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -200,7 +200,9 @@ public:
|
||||
BindingHiddenRef,
|
||||
};
|
||||
BindingType getBinding(const App::Range &range,
|
||||
App::ExpressionPtr *pStart=nullptr, App::ExpressionPtr *pEnd=nullptr) const;
|
||||
App::ExpressionPtr *pStart=nullptr,
|
||||
App::ExpressionPtr *pEnd=nullptr,
|
||||
App::ObjectIdentifier *pTarget=nullptr) const;
|
||||
|
||||
protected:
|
||||
virtual void hasSetValue() override;
|
||||
|
||||
@@ -824,8 +824,11 @@ void Sheet::recomputeCell(CellAddress p)
|
||||
cellSpanChanged(p);
|
||||
}
|
||||
|
||||
PropertySheet::BindingType Sheet::getCellBinding(Range &range,
|
||||
ExpressionPtr *pStart, ExpressionPtr *pEnd) const
|
||||
PropertySheet::BindingType
|
||||
Sheet::getCellBinding(Range &range,
|
||||
ExpressionPtr *pStart,
|
||||
ExpressionPtr *pEnd,
|
||||
App::ObjectIdentifier *pTarget) const
|
||||
{
|
||||
do {
|
||||
CellAddress addr = *range;
|
||||
@@ -835,7 +838,7 @@ PropertySheet::BindingType Sheet::getCellBinding(Range &range,
|
||||
&& addr.col()>=r.from().col()
|
||||
&& addr.col()<=r.to().col())
|
||||
{
|
||||
auto res = cells.getBinding(r,pStart,pEnd);
|
||||
auto res = cells.getBinding(r,pStart,pEnd,pTarget);
|
||||
if(res != PropertySheet::BindingNone) {
|
||||
range = r;
|
||||
return res;
|
||||
|
||||
@@ -108,7 +108,9 @@ public:
|
||||
unsigned getCellBindingBorder(App::CellAddress address) const;
|
||||
|
||||
PropertySheet::BindingType getCellBinding(App::Range &range,
|
||||
App::ExpressionPtr *pStart=nullptr, App::ExpressionPtr *pEnd=nullptr) const;
|
||||
App::ExpressionPtr *pStart=nullptr,
|
||||
App::ExpressionPtr *pEnd=nullptr,
|
||||
App::ObjectIdentifier *pTarget=nullptr) const;
|
||||
|
||||
void setCell(const char *address, const char *value);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
****************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <QMessageBox>
|
||||
#include "DlgBindSheet.h"
|
||||
#include <Base/Tools.h>
|
||||
@@ -45,7 +45,8 @@ DlgBindSheet::DlgBindSheet(Sheet *sheet, const std::vector<Range> &ranges, QWidg
|
||||
|
||||
std::string toStart,toEnd;
|
||||
ExpressionPtr pStart, pEnd;
|
||||
PropertySheet::BindingType type = sheet->getCellBinding(range,&pStart,&pEnd);
|
||||
App::ObjectIdentifier bindingTarget;
|
||||
PropertySheet::BindingType type = sheet->getCellBinding(range,&pStart,&pEnd,&bindingTarget);
|
||||
if(type == PropertySheet::BindingNone) {
|
||||
if(ranges.size()>1) {
|
||||
toStart = ranges.back().from().toString();
|
||||
@@ -57,6 +58,7 @@ DlgBindSheet::DlgBindSheet(Sheet *sheet, const std::vector<Range> &ranges, QWidg
|
||||
target.setCol(target.col() + range.to().col() - range.from().col());
|
||||
toEnd = target.toString();
|
||||
}
|
||||
ui->btnDiscard->setDisabled(true);
|
||||
} else {
|
||||
ui->lineEditFromStart->setReadOnly(true);
|
||||
ui->lineEditFromEnd->setReadOnly(true);
|
||||
@@ -90,6 +92,7 @@ DlgBindSheet::DlgBindSheet(Sheet *sheet, const std::vector<Range> &ranges, QWidg
|
||||
ui->comboBox->addItem(QString::fromLatin1(". (%1)").arg(
|
||||
QString::fromUtf8(sheet->Label.getValue())), QByteArray(""));
|
||||
|
||||
App::DocumentObject *target = bindingTarget.getDocumentObject();
|
||||
for(auto obj : sheet->getDocument()->getObjectsOfType<Sheet>()) {
|
||||
if(obj == sheet)
|
||||
continue;
|
||||
@@ -101,6 +104,8 @@ DlgBindSheet::DlgBindSheet(Sheet *sheet, const std::vector<Range> &ranges, QWidg
|
||||
else
|
||||
label = QLatin1String(obj->getNameInDocument());
|
||||
ui->comboBox->addItem(label, QByteArray(obj->getNameInDocument()));
|
||||
if (obj == target)
|
||||
ui->comboBox->setCurrentIndex(ui->comboBox->count()-1);
|
||||
}
|
||||
for(auto doc : GetApplication().getDocuments()) {
|
||||
if(doc == sheet->getDocument())
|
||||
@@ -117,6 +122,8 @@ DlgBindSheet::DlgBindSheet(Sheet *sheet, const std::vector<Range> &ranges, QWidg
|
||||
else
|
||||
label = QLatin1String(fullname.c_str());
|
||||
ui->comboBox->addItem(label, QByteArray(fullname.c_str()));
|
||||
if (obj == target)
|
||||
ui->comboBox->setCurrentIndex(ui->comboBox->count()-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,32 +155,65 @@ void DlgBindSheet::accept()
|
||||
FC_THROWM(Base::RuntimeError, "Cannot find Spreadsheet '" << ref << "'");
|
||||
}
|
||||
|
||||
auto checkAddress = [](std::string &addr, CellAddress &cell, bool quote) {
|
||||
std::string copy(addr);
|
||||
boost::to_upper(copy);
|
||||
cell = App::stringToAddress(copy.c_str(), true);
|
||||
if (!cell.isValid()) {
|
||||
std::string msg("Invalid cell: ");
|
||||
msg += addr;
|
||||
throw Base::ValueError(msg.c_str());
|
||||
}
|
||||
if (quote)
|
||||
addr = std::string("<<") + copy + ">>";
|
||||
else
|
||||
addr = copy;
|
||||
};
|
||||
|
||||
CellAddress fromCellStart, fromCellEnd, toCellStart, toCellEnd;
|
||||
std::string fromStart(ui->lineEditFromStart->text().trimmed().toLatin1().constData());
|
||||
std::string fromEnd(ui->lineEditFromEnd->text().trimmed().toLatin1().constData());
|
||||
checkAddress(fromStart, fromCellStart, false);
|
||||
checkAddress(fromEnd, fromCellEnd, false);
|
||||
|
||||
std::string toStart(ui->lineEditToStart->text().trimmed().toLatin1().constData());
|
||||
if(boost::starts_with(toStart,"="))
|
||||
toStart.erase(toStart.begin());
|
||||
else
|
||||
toStart = std::string("<<") + toStart + ">>";
|
||||
checkAddress(toStart, toCellStart, true);
|
||||
|
||||
std::string toEnd(ui->lineEditToEnd->text().trimmed().toLatin1().constData());
|
||||
if(boost::starts_with(toEnd,"="))
|
||||
toEnd.erase(toEnd.begin());
|
||||
else
|
||||
toEnd = std::string("<<") + toEnd + ">>";
|
||||
else {
|
||||
checkAddress(toEnd, toCellEnd, true);
|
||||
if (toCellStart.isValid()) {
|
||||
App::Range fromRange(fromCellStart, fromCellEnd);
|
||||
App::Range toRange(toCellStart, toCellEnd);
|
||||
if (fromRange.size() != toRange.size()) {
|
||||
auto res = QMessageBox::warning(this, tr("Bind cells"),
|
||||
tr("Source and target cell count mismatch. Partial binding may still work.\n\n"
|
||||
"Do you want to continue?"), QMessageBox::Yes|QMessageBox::No);
|
||||
if (res == QMessageBox::No)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gui::Command::openCommand("Bind cells");
|
||||
commandActive = true;
|
||||
|
||||
if(ui->checkBoxHREF->isChecked())
|
||||
if(ui->checkBoxHREF->isChecked()) {
|
||||
Gui::cmdAppObjectArgs(sheet, "setExpression('.cells.Bind.%s.%s', None)", fromStart, fromEnd);
|
||||
Gui::cmdAppObjectArgs(sheet,
|
||||
"setExpression('.cells.BindHiddenRef.%s.%s', 'hiddenref(tuple(%s.cells, %s, %s))')",
|
||||
fromStart, fromEnd, ref, toStart, toEnd);
|
||||
else
|
||||
} else {
|
||||
Gui::cmdAppObjectArgs(sheet, "setExpression('.cells.BindHiddenRef.%s.%s', None)", fromStart, fromEnd);
|
||||
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();
|
||||
@@ -191,6 +231,7 @@ void DlgBindSheet::onDiscard() {
|
||||
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::cmdAppObjectArgs(sheet, "setExpression('.cells.BindHiddenRef.%s.%s', None)", fromStart, fromEnd);
|
||||
Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()");
|
||||
Gui::Command::commitCommand();
|
||||
reject();
|
||||
|
||||
@@ -249,28 +249,9 @@ void SheetTableView::cellProperties()
|
||||
|
||||
std::vector<Range> SheetTableView::selectedRanges() const
|
||||
{
|
||||
QModelIndexList list = selectionModel()->selectedIndexes();
|
||||
std::vector<Range> result;
|
||||
|
||||
// Insert selected cells into set. This variable should ideally be a hash_set
|
||||
// but that is not part of standard stl.
|
||||
std::set<std::pair<int, int> > cells;
|
||||
for (QModelIndexList::const_iterator it = list.begin(); it != list.end(); ++it)
|
||||
cells.insert(std::make_pair<int,int>((*it).row(), (*it).column()));
|
||||
|
||||
// Create rectangular cells from the unordered collection of selected cells
|
||||
std::map<std::pair<int, int>, std::pair<int, int> > rectangles;
|
||||
createRectangles(cells, rectangles);
|
||||
|
||||
std::map<std::pair<int, int>, std::pair<int, int> >::const_iterator i = rectangles.begin();
|
||||
for (; i != rectangles.end(); ++i) {
|
||||
std::pair<int, int> ul = (*i).first;
|
||||
std::pair<int, int> size = (*i).second;
|
||||
|
||||
result.emplace_back(ul.first, ul.second,
|
||||
ul.first + size.first - 1, ul.second + size.second - 1);
|
||||
}
|
||||
|
||||
for (const auto &sel : selectionModel()->selection())
|
||||
result.emplace_back(sel.top(), sel.left(), sel.bottom(), sel.right());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user