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:
Zheng, Lei
2019-12-26 07:57:03 +08:00
committed by Chris Hennes
parent 50ab1c558b
commit 55e2c918a9
3 changed files with 119 additions and 39 deletions

View File

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

View File

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

View File

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