diff --git a/src/App/Range.h b/src/App/Range.h index 6fb914c869..fd02734edc 100644 --- a/src/App/Range.h +++ b/src/App/Range.h @@ -64,6 +64,8 @@ struct AppExport CellAddress { inline bool operator<(const CellAddress & other) const { return asInt() < other.asInt(); } + inline bool operator>(const CellAddress & other) const { return asInt() > other.asInt(); } + inline bool operator==(const CellAddress & other) const { return asInt() == other.asInt(); } inline bool operator!=(const CellAddress & other) const { return asInt() != other.asInt(); } @@ -148,6 +150,14 @@ public: CellAddress operator*() const { return CellAddress(row_curr, col_curr); } + inline bool operator<(const Range & other) const { + if(from() < other.from()) + return true; + if(from() > other.from()) + return false; + return to() < other.to(); + } + /** Number of elements in range */ inline int size() const { return (row_end - row_begin + 1) * (col_end - col_begin + 1); } diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index 6d151e5e4d..27e25130dc 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -89,6 +89,9 @@ Sheet::Sheet() ADD_PROPERTY_TYPE(columnWidths, (), "Spreadsheet", (PropertyType)(Prop_ReadOnly|Prop_Hidden|Prop_Output), "Column widths"); ADD_PROPERTY_TYPE(rowHeights, (), "Spreadsheet", (PropertyType)(Prop_ReadOnly|Prop_Hidden|Prop_Output), "Row heights"); ADD_PROPERTY_TYPE(rowHeights, (), "Spreadsheet", (PropertyType)(Prop_ReadOnly|Prop_Hidden), "Row heights"); + ExpressionEngine.expressionChanged.connect([this](const App::ObjectIdentifier &) { + this->updateBindings(); + }); } /** @@ -841,9 +844,11 @@ PropertySheet::BindingType Sheet::getCellBinding(Range &range, return PropertySheet::BindingNone; } -unsigned Sheet::getCellBindingBorder(App::CellAddress address) const { +static inline unsigned _getBorder( + const std::vector &ranges, const App::CellAddress &address) +{ unsigned flags = 0; - for(auto &range : boundRanges) { + for(auto &range : ranges) { auto from = range.from(); auto to = range.to(); if(address.row() < from.row() @@ -852,19 +857,46 @@ unsigned Sheet::getCellBindingBorder(App::CellAddress address) const { || address.col() > to.col()) continue; if(address.row() == from.row()) - flags |= BorderTop; + flags |= Sheet::BorderTop; if(address.row() == to.row()) - flags |= BorderBottom; + flags |= Sheet::BorderBottom; if(address.col() == from.col()) - flags |= BorderLeft; + flags |= Sheet::BorderLeft; if(address.col() == to.col()) - flags |= BorderRight; - if(flags == BorderAll) + flags |= Sheet::BorderRight; + if(flags == Sheet::BorderAll) break; } return flags; } +unsigned Sheet::getCellBindingBorder(App::CellAddress address) const { + return _getBorder(boundRanges, address); +} + +void Sheet::updateBindings() +{ + std::set oldRangeSet(boundRanges.begin(), boundRanges.end()); + std::set newRangeSet; + std::set rangeSet; + boundRanges.clear(); + for(auto &v : ExpressionEngine.getExpressions()) { + CellAddress from,to; + if(!cells.isBindingPath(v.first,&from,&to)) + continue; + App::Range range(from,to); + if(!oldRangeSet.erase(range)) + newRangeSet.insert(range); + rangeSet.insert(range); + } + boundRanges.reserve(rangeSet.size()); + boundRanges.insert(boundRanges.end(),rangeSet.begin(),rangeSet.end()); + for(auto &range : oldRangeSet) + rangeUpdated(range); + for(auto &range : newRangeSet) + rangeUpdated(range); +} + /** * Update the document properties. * @@ -872,13 +904,7 @@ unsigned Sheet::getCellBindingBorder(App::CellAddress address) const { DocumentObjectExecReturn *Sheet::execute(void) { - boundRanges.clear(); - for(auto &v : ExpressionEngine.getExpressions()) { - CellAddress from,to; - if(!cells.isBindingPath(v.first,&from,&to)) - continue; - boundRanges.emplace_back(from,to); - } + updateBindings(); // Get dirty cells that we have to recompute std::set dirtyCells = cells.getDirty(); @@ -1535,6 +1561,42 @@ std::string Sheet::getColumn(int offset) const { return txt; } +void Sheet::onChanged(const App::Property *prop) { + if(prop == &cells) { + decltype(copyCutRanges) tmp; + tmp.swap(copyCutRanges); + for(auto &range : tmp) + rangeUpdated(range); + } + + App::DocumentObject::onChanged(prop); +} + +void Sheet::setCopyOrCutRanges(const std::vector &ranges, bool copy) +{ + std::set rangeSet(copyCutRanges.begin(), copyCutRanges.end()); + copyCutRanges = ranges; + rangeSet.insert(copyCutRanges.begin(), copyCutRanges.end()); + for(auto range : rangeSet) + rangeUpdated(range); + hasCopyRange = copy; +} + +const std::vector &Sheet::getCopyOrCutRange(bool copy) const +{ + static const std::vector nullRange; + if(hasCopyRange != copy) + return nullRange; + return copyCutRanges; +} + +unsigned Sheet::getCopyOrCutBorder(CellAddress address, bool copy) const +{ + if(hasCopyRange != copy) + return 0; + return _getBorder(copyCutRanges, address); +} + /////////////////////////////////////////////////////////////////////////////// TYPESYSTEM_SOURCE(Spreadsheet::PropertySpreadsheetQuantity, App::PropertyQuantity) diff --git a/src/Mod/Spreadsheet/App/Sheet.h b/src/Mod/Spreadsheet/App/Sheet.h index 34a202772f..49dea4f756 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -217,8 +217,14 @@ public: virtual void renameObjectIdentifiers(const std::map & paths); + void setCopyOrCutRanges(const std::vector &ranges, bool copy=true); + const std::vector &getCopyOrCutRange(bool copy=true) const; + unsigned getCopyOrCutBorder(App::CellAddress address, bool copy=true) const; + protected: + virtual void onChanged(const App::Property *prop); + void updateColumnsOrRows(bool horizontal, int section, int count) ; std::set providesTo(App::CellAddress address) const; @@ -245,6 +251,8 @@ protected: virtual void onSettingDocument(); + void updateBindings(); + /* Properties for used cells */ App::DynamicProperty &props; @@ -274,6 +282,9 @@ protected: std::vector boundRanges; + std::vector copyCutRanges; + bool hasCopyRange = false; + friend class SheetObserver; friend class PropertySheet; diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index acf95dddc7..c2564abc4f 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -500,6 +500,9 @@ bool SheetTableView::event(QEvent* event) case Qt::Key_Delete: deleteSelection(); return true; + case Qt::Key_Escape: + sheet->setCopyOrCutRanges({}); + return true; default: break; } @@ -570,8 +573,8 @@ void SheetTableView::deleteSelection() Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.clear('%s')", sheet->getNameInDocument(), i->rangeString().c_str()); } - Gui::Command::commitCommand(); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); + Gui::Command::commitCommand(); } } @@ -579,18 +582,20 @@ static const QLatin1String _SheetMime("application/x-fc-spreadsheet"); void SheetTableView::copySelection() { - QModelIndexList selection = selectionModel()->selectedIndexes(); + _copySelection(selectedRanges(), true); +} + +void SheetTableView::_copySelection(const std::vector &ranges, bool copy) +{ int minRow = INT_MAX; int maxRow = 0; int minCol = INT_MAX; int maxCol = 0; - for (auto it : selection) { - int row = it.row(); - int col = it.column(); - minRow = std::min(minRow, row); - maxRow = std::max(maxRow, row); - minCol = std::min(minCol, col); - maxCol = std::max(maxCol, col); + for (auto &range : ranges) { + minRow = std::min(minRow, range.from().row()); + maxRow = std::max(maxRow, range.to().row()); + minCol = std::min(minCol, range.from().col()); + maxCol = std::max(maxCol, range.to().col()); } QString selectedText; @@ -607,33 +612,51 @@ void SheetTableView::copySelection() } Base::StringWriter writer; - sheet->getCells()->copyCells(writer,selectedRanges()); + sheet->getCells()->copyCells(writer,ranges); QMimeData *mime = new QMimeData(); mime->setText(selectedText); mime->setData(_SheetMime,QByteArray(writer.getString().c_str())); QApplication::clipboard()->setMimeData(mime); + + sheet->setCopyOrCutRanges(std::move(ranges), copy); } void SheetTableView::cutSelection() { - copySelection(); - deleteSelection(); + _copySelection(selectedRanges(), false); } void SheetTableView::pasteClipboard() { - const QMimeData* mimeData = QApplication::clipboard()->mimeData(); - if(!mimeData || !mimeData->hasText()) - return; - - auto ranges = selectedRanges(); - if(ranges.empty()) - return; - - Range range = ranges.back(); - App::AutoTransaction committer("Paste cell"); try { + bool copy = true; + auto ranges = sheet->getCopyOrCutRange(copy); + if(ranges.empty()) { + copy = false; + ranges = sheet->getCopyOrCutRange(copy); + } + + if(ranges.size()) + _copySelection(ranges, copy); + + const QMimeData* mimeData = QApplication::clipboard()->mimeData(); + if(!mimeData || !mimeData->hasText()) + return; + + if(!copy) { + for(auto range : ranges) { + do { + sheet->clear(*range); + } while (range.next()); + } + } + + ranges = selectedRanges(); + if(ranges.empty()) + return; + + Range range = ranges.back(); if (!mimeData->hasFormat(_SheetMime)) { CellAddress current = range.from(); QStringList cells; diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.h b/src/Mod/Spreadsheet/Gui/SheetTableView.h index 741fa1cf38..8a045c4fa6 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.h +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.h @@ -92,6 +92,8 @@ protected: void contextMenuEvent (QContextMenuEvent * e); + void _copySelection(const std::vector &ranges, bool copy); + QModelIndex currentEditIndex; Spreadsheet::Sheet * sheet; int tabCounter; diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp index 450d584a7d..2eb1647ba3 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp @@ -28,6 +28,7 @@ # include #endif +#include #include "SpreadsheetDelegate.h" #include "LineEdit.h" #include @@ -96,31 +97,41 @@ QSize SpreadsheetDelegate::sizeHint(const QStyleOptionViewItem & option, const Q return QSize(); } +static inline void drawBorder(QPainter *painter, const QStyleOptionViewItem &option, + unsigned flags, QColor color, Qt::PenStyle style) +{ + if(!flags) + return; + QPen pen(color); + pen.setWidth(2); + pen.setStyle(style); + painter->setPen(pen); + + QRect rect = option.rect.adjusted(1,1,0,0); + if(flags == Sheet::BorderAll) { + painter->drawRect(rect); + return; + } + if(flags & Sheet::BorderLeft) + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + if(flags & Sheet::BorderTop) + painter->drawLine(rect.topLeft(), rect.topRight()); + if(flags & Sheet::BorderRight) + painter->drawLine(rect.topRight(), rect.bottomRight()); + if(flags & Sheet::BorderBottom) + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); +} + void SpreadsheetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const { QStyledItemDelegate::paint(painter, option, index); if(!sheet) return; - unsigned flags = sheet->getCellBindingBorder(App::CellAddress(index.row(),index.column())); - if(!flags) - return; - QPen pen(Qt::blue); - pen.setWidth(1); - pen.setStyle(Qt::SolidLine); - painter->setPen(pen); - if(flags == Sheet::BorderAll) { - painter->drawRect(option.rect); - return; - } - if(flags & Sheet::BorderLeft) - painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft()); - if(flags & Sheet::BorderTop) - painter->drawLine(option.rect.topLeft(), option.rect.topRight()); - if(flags & Sheet::BorderRight) - painter->drawLine(option.rect.topRight(), option.rect.bottomRight()); - if(flags & Sheet::BorderBottom) - painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight()); + App::CellAddress addr(index.row(), index.column()); + drawBorder(painter, option, sheet->getCellBindingBorder(addr), Qt::blue, Qt::SolidLine); + drawBorder(painter, option, sheet->getCopyOrCutBorder(addr,true), Qt::green, Qt::DashLine); + drawBorder(painter, option, sheet->getCopyOrCutBorder(addr,false), Qt::red, Qt::DashLine); } #include "moc_SpreadsheetDelegate.cpp"