Spreadsheet: improve range binding feature (#6995)

* Spreadsheet: improve range binding feature
This commit is contained in:
Zheng Lei
2022-06-09 01:19:41 +08:00
committed by GitHub
parent 920e8e0469
commit 5864e17d9d
6 changed files with 170 additions and 82 deletions

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;
}