Spreadsheet: support paste into cell range
For single range copy, the range selection when pasting determines the start cell and the number of duplications. For example, when copying a range A1:B2 (i.e. a 2x2 square) and pasting into a selection of C1:C5 (i.e. a 5x1 vertical line), the square will be duplicated once in horizontal, but twice in vertical, resulting new cells range from C1:D4. This logic is borrowed from google sheet. For multi-ranged copy, no multi duplication is intended. If more than one selection range exists before pasting, only the top left cell of the last selected range is used to determine the starting cell for pasting. The cells will be copied with the exact cell layout keeping any empty cells in between. This logic is different from google sheet, where it disallows unalligned multi-ranged copy, and will condense and eliminate any empty cells for aligned multi-range copy.
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
#include <PropertySheetPy.h>
|
||||
#include <App/ExpressionVisitors.h>
|
||||
#include <App/ExpressionParser.h>
|
||||
|
||||
FC_LOG_LEVEL_INIT("Spreadsheet", true, true)
|
||||
|
||||
using namespace App;
|
||||
@@ -368,45 +369,124 @@ void PropertySheet::copyCells(Base::Writer& writer, const std::vector<Range>& ra
|
||||
writer.Stream() << "</Cells>" << std::endl;
|
||||
}
|
||||
|
||||
void PropertySheet::pasteCells(XMLReader& reader, const CellAddress& addr) {
|
||||
AtomicPropertyChange signaller(*this);
|
||||
|
||||
bool first = true;
|
||||
int roffset = 0, coffset = 0;
|
||||
|
||||
void PropertySheet::pasteCells(XMLReader &reader, Range dstRange) {
|
||||
reader.readElement("Cells");
|
||||
for (int rangeCount = reader.getAttributeAsInteger("count"); rangeCount > 0; --rangeCount) {
|
||||
int rangeCount = reader.getAttributeAsInteger("count");
|
||||
if(rangeCount<=0)
|
||||
return;
|
||||
|
||||
int dstRows = dstRange.rowCount();
|
||||
int dstCols = dstRange.colCount();
|
||||
CellAddress dstFrom = dstRange.from();
|
||||
|
||||
int roffset,coffset;
|
||||
|
||||
AtomicPropertyChange signaller(*this);
|
||||
for(int ri=0; ri < rangeCount; ++ri) {
|
||||
reader.readElement("Range");
|
||||
CellAddress from(reader.getAttribute("from"));
|
||||
CellAddress to(reader.getAttribute("to"));
|
||||
Range range(from, to);
|
||||
for (int cellCount = reader.getAttributeAsInteger("count"); cellCount > 0; --cellCount) {
|
||||
if (first) {
|
||||
first = false;
|
||||
roffset = addr.row() - from.row();
|
||||
coffset = addr.col() - from.col();
|
||||
}
|
||||
int cellCount = reader.getAttributeAsInteger("count");
|
||||
|
||||
Range range(from,to);
|
||||
|
||||
CellAddress addr(dstFrom);
|
||||
if(!ri) {
|
||||
roffset = addr.row() - from.row();
|
||||
coffset = addr.col() - from.col();
|
||||
}
|
||||
|
||||
int rcount,ccount;
|
||||
if(rangeCount>1) {
|
||||
rcount = 1;
|
||||
ccount = 1;
|
||||
} else {
|
||||
rcount = dstRows/range.rowCount();
|
||||
if(!rcount)
|
||||
rcount = 1;
|
||||
ccount = dstCols/range.colCount();
|
||||
if(!ccount)
|
||||
ccount = 1;
|
||||
}
|
||||
for(int ci=0; ci < cellCount; ++ci) {
|
||||
reader.readElement("Cell");
|
||||
CellAddress src(reader.getAttribute("address"));
|
||||
CellAddress dst(src.row() + roffset, src.col() + coffset);
|
||||
owner->clear(dst);
|
||||
owner->cellUpdated(dst);
|
||||
|
||||
auto cell = owner->getNewCell(dst);
|
||||
cell->setSpans(-1, -1);
|
||||
cell->restore(reader, true);
|
||||
int rows, cols;
|
||||
if (cell->getSpans(rows, cols) && (rows > 1 || cols > 1))
|
||||
mergeCells(dst, CellAddress(dst.row() + rows - 1, dst.col() + cols - 1));
|
||||
if(ci)
|
||||
range.next();
|
||||
|
||||
if (roffset || coffset) {
|
||||
OffsetCellsExpressionVisitor<PropertySheet> visitor(*this, roffset, coffset);
|
||||
cell->visit(visitor);
|
||||
if (visitor.changed())
|
||||
recomputeDependencies(dst);
|
||||
while(src!=*range) {
|
||||
for(int r=0; r < rcount; ++r) {
|
||||
for(int c=0; c < ccount; ++c) {
|
||||
CellAddress dst(range.row()+roffset+r*range.rowCount(),
|
||||
range.column()+coffset+c*range.colCount());
|
||||
if(!dst.isValid())
|
||||
continue;
|
||||
owner->clear(dst);
|
||||
owner->cellUpdated(dst);
|
||||
}
|
||||
}
|
||||
range.next();
|
||||
}
|
||||
dirty.insert(dst);
|
||||
owner->cellUpdated(dst);
|
||||
|
||||
CellAddress newCellAddr;
|
||||
for(int r=0; r < rcount; ++r) {
|
||||
for(int c=0; c < ccount; ++c) {
|
||||
CellAddress dst(src.row()+roffset+r*range.rowCount(),
|
||||
src.col()+coffset+c*range.colCount());
|
||||
if(!dst.isValid())
|
||||
continue;
|
||||
|
||||
auto cell = owner->getNewCell(dst);
|
||||
cell->setSpans(-1,-1);
|
||||
|
||||
int roffset_cur, coffset_cur;
|
||||
if(!newCellAddr.isValid()) {
|
||||
roffset_cur = roffset;
|
||||
coffset_cur = coffset;
|
||||
newCellAddr = dst;
|
||||
cell->restore(reader,true);
|
||||
} else {
|
||||
roffset_cur = r*range.rowCount();
|
||||
coffset_cur = c*range.colCount();
|
||||
auto newCell = owner->getCell(newCellAddr);
|
||||
const Expression *expr;
|
||||
if(!newCell || !(expr=newCell->getExpression(true))) {
|
||||
FC_THROWM(Base::RuntimeError, "Failed to copy cell "
|
||||
<< getFullName() << '.' << dst.toString()
|
||||
<< " from " << newCellAddr.toString());
|
||||
}
|
||||
cell->setExpression(ExpressionPtr(expr->copy()));
|
||||
}
|
||||
|
||||
int rows, cols;
|
||||
if (cell->getSpans(rows, cols) && (rows > 1 || cols > 1))
|
||||
mergeCells(dst, CellAddress(dst.row() + rows - 1, dst.col() + cols - 1));
|
||||
|
||||
if(roffset_cur || coffset_cur) {
|
||||
OffsetCellsExpressionVisitor<PropertySheet> visitor(*this, roffset_cur, coffset_cur);
|
||||
cell->visit(visitor);
|
||||
if(visitor.changed())
|
||||
recomputeDependencies(dst);
|
||||
}
|
||||
dirty.insert(dst);
|
||||
owner->cellUpdated(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!cellCount || range.next()) {
|
||||
do {
|
||||
for(int r=0; r < rcount; ++r) {
|
||||
for(int c=0; c < ccount; ++c) {
|
||||
CellAddress dst(range.row()+roffset+r*range.rowCount(),
|
||||
range.column()+coffset+c*range.colCount());
|
||||
if(!dst.isValid())
|
||||
continue;
|
||||
owner->clear(dst);
|
||||
owner->cellUpdated(dst);
|
||||
}
|
||||
}
|
||||
}while(range.next());
|
||||
}
|
||||
}
|
||||
signaller.tryInvoke();
|
||||
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
|
||||
void copyCells(Base::Writer &writer, const std::vector<App::Range> &ranges) const;
|
||||
|
||||
void pasteCells(Base::XMLReader &reader, const App::CellAddress &addr);
|
||||
void pasteCells(Base::XMLReader &reader, App::Range dstRange);
|
||||
|
||||
Cell *createCell(App::CellAddress address);
|
||||
|
||||
|
||||
@@ -597,18 +597,16 @@ void SheetTableView::pasteClipboard()
|
||||
if(!mimeData || !mimeData->hasText())
|
||||
return;
|
||||
|
||||
if(selectionModel()->selectedIndexes().size()>1) {
|
||||
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Spreadsheet"),
|
||||
QObject::tr("Spreadsheet does not support range selection when pasting.\n"
|
||||
"Please select one cell only."));
|
||||
auto ranges = selectedRanges();
|
||||
if(ranges.empty())
|
||||
return;
|
||||
}
|
||||
|
||||
QModelIndex current = currentIndex();
|
||||
Range range = ranges.back();
|
||||
|
||||
App::AutoTransaction committer("Paste cell");
|
||||
try {
|
||||
if (!mimeData->hasFormat(_SheetMime)) {
|
||||
CellAddress current = range.from();
|
||||
QStringList cells;
|
||||
QString text = mimeData->text();
|
||||
int i=0;
|
||||
@@ -616,7 +614,7 @@ void SheetTableView::pasteClipboard()
|
||||
QStringList cols = it.split(QLatin1Char('\t'));
|
||||
int j=0;
|
||||
for (auto jt : cols) {
|
||||
QModelIndex index = model()->index(current.row()+i, current.column()+j);
|
||||
QModelIndex index = model()->index(current.row()+i, current.col()+j);
|
||||
model()->setData(index, jt);
|
||||
j++;
|
||||
}
|
||||
@@ -628,7 +626,7 @@ void SheetTableView::pasteClipboard()
|
||||
std::istream in(0);
|
||||
in.rdbuf(&buf);
|
||||
Base::XMLReader reader("<memory>", in);
|
||||
sheet->getCells()->pasteCells(reader,CellAddress(current.row(),current.column()));
|
||||
sheet->getCells()->pasteCells(reader,range);
|
||||
}
|
||||
|
||||
GetApplication().getActiveDocument()->recompute();
|
||||
@@ -637,7 +635,9 @@ void SheetTableView::pasteClipboard()
|
||||
e.ReportException();
|
||||
QMessageBox::critical(Gui::getMainWindow(), QObject::tr("Copy & Paste failed"),
|
||||
QString::fromLatin1(e.what()));
|
||||
return;
|
||||
}
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
void SheetTableView::finishEditWithMove(int keyPressed, Qt::KeyboardModifiers modifiers, bool handleTabMotion)
|
||||
|
||||
Reference in New Issue
Block a user