diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 8ad50d7c13..7a793cfbd7 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -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(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 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; } diff --git a/src/Mod/Spreadsheet/App/PropertySheet.h b/src/Mod/Spreadsheet/App/PropertySheet.h index a40fb5fd35..6138d8753e 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.h +++ b/src/Mod/Spreadsheet/App/PropertySheet.h @@ -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; diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index e0fc1a60e4..89e0043be5 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -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; diff --git a/src/Mod/Spreadsheet/App/Sheet.h b/src/Mod/Spreadsheet/App/Sheet.h index c874988c5c..0c746c2328 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -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); diff --git a/src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp b/src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp index 10fac62b80..126ecc4d2c 100644 --- a/src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp +++ b/src/Mod/Spreadsheet/Gui/DlgBindSheet.cpp @@ -21,7 +21,7 @@ ****************************************************************************/ #include "PreCompiled.h" -#include +#include #include #include "DlgBindSheet.h" #include @@ -45,7 +45,8 @@ DlgBindSheet::DlgBindSheet(Sheet *sheet, const std::vector &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 &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 &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()) { if(obj == sheet) continue; @@ -101,6 +104,8 @@ DlgBindSheet::DlgBindSheet(Sheet *sheet, const std::vector &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 &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(); diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index 2bb7574440..ca1d6bdf7a 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -249,28 +249,9 @@ void SheetTableView::cellProperties() std::vector SheetTableView::selectedRanges() const { - QModelIndexList list = selectionModel()->selectedIndexes(); std::vector result; - - // Insert selected cells into set. This variable should ideally be a hash_set - // but that is not part of standard stl. - std::set > cells; - for (QModelIndexList::const_iterator it = list.begin(); it != list.end(); ++it) - cells.insert(std::make_pair((*it).row(), (*it).column())); - - // Create rectangular cells from the unordered collection of selected cells - std::map, std::pair > rectangles; - createRectangles(cells, rectangles); - - std::map, std::pair >::const_iterator i = rectangles.begin(); - for (; i != rectangles.end(); ++i) { - std::pair ul = (*i).first; - std::pair 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; }