Spreadsheet: improve copy/cut/paste cells
Add color bound around user copy/cut ranges. Do not touch Spreadsheet object when cutting, only do so when pasting.
This commit is contained in:
@@ -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); }
|
||||
|
||||
|
||||
@@ -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<App::Range> &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<Range> oldRangeSet(boundRanges.begin(), boundRanges.end());
|
||||
std::set<Range> newRangeSet;
|
||||
std::set<Range> 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<CellAddress> 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<App::Range> &ranges, bool copy)
|
||||
{
|
||||
std::set<Range> rangeSet(copyCutRanges.begin(), copyCutRanges.end());
|
||||
copyCutRanges = ranges;
|
||||
rangeSet.insert(copyCutRanges.begin(), copyCutRanges.end());
|
||||
for(auto range : rangeSet)
|
||||
rangeUpdated(range);
|
||||
hasCopyRange = copy;
|
||||
}
|
||||
|
||||
const std::vector<Range> &Sheet::getCopyOrCutRange(bool copy) const
|
||||
{
|
||||
static const std::vector<Range> 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)
|
||||
|
||||
@@ -217,8 +217,14 @@ public:
|
||||
|
||||
virtual void renameObjectIdentifiers(const std::map<App::ObjectIdentifier, App::ObjectIdentifier> & paths);
|
||||
|
||||
void setCopyOrCutRanges(const std::vector<App::Range> &ranges, bool copy=true);
|
||||
const std::vector<App::Range> &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<App::CellAddress> 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<App::Range> boundRanges;
|
||||
|
||||
std::vector<App::Range> copyCutRanges;
|
||||
bool hasCopyRange = false;
|
||||
|
||||
friend class SheetObserver;
|
||||
|
||||
friend class PropertySheet;
|
||||
|
||||
@@ -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<App::Range> &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;
|
||||
|
||||
@@ -92,6 +92,8 @@ protected:
|
||||
|
||||
void contextMenuEvent (QContextMenuEvent * e);
|
||||
|
||||
void _copySelection(const std::vector<App::Range> &ranges, bool copy);
|
||||
|
||||
QModelIndex currentEditIndex;
|
||||
Spreadsheet::Sheet * sheet;
|
||||
int tabCounter;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
# include <QPainter>
|
||||
#endif
|
||||
|
||||
#include <Base/Console.h>
|
||||
#include "SpreadsheetDelegate.h"
|
||||
#include "LineEdit.h"
|
||||
#include <Base/Console.h>
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user