From 55e2c918a9f7c2a2828274cfadd0fe3d0e6a191f Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 26 Dec 2019 07:57:03 +0800 Subject: [PATCH] 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. --- src/Mod/Spreadsheet/App/PropertySheet.cpp | 140 ++++++++++++++++----- src/Mod/Spreadsheet/App/PropertySheet.h | 2 +- src/Mod/Spreadsheet/Gui/SheetTableView.cpp | 16 +-- 3 files changed, 119 insertions(+), 39 deletions(-) diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 7f73663540..cef6b26087 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -44,6 +44,7 @@ #include #include #include + FC_LOG_LEVEL_INIT("Spreadsheet", true, true) using namespace App; @@ -368,45 +369,124 @@ void PropertySheet::copyCells(Base::Writer& writer, const std::vector& ra writer.Stream() << "" << 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 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 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(); diff --git a/src/Mod/Spreadsheet/App/PropertySheet.h b/src/Mod/Spreadsheet/App/PropertySheet.h index 60694b85b5..a98fee3087 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.h +++ b/src/Mod/Spreadsheet/App/PropertySheet.h @@ -74,7 +74,7 @@ public: void copyCells(Base::Writer &writer, const std::vector &ranges) const; - void pasteCells(Base::XMLReader &reader, const App::CellAddress &addr); + void pasteCells(Base::XMLReader &reader, App::Range dstRange); Cell *createCell(App::CellAddress address); diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index d5f079138e..b77e0d12a4 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -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("", 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)