Skip to content

Commit

Permalink
Copy/Paste with header should work
Browse files Browse the repository at this point in the history
Fixes jasp-stats/INTERNAL-jasp#2432

Copy/paste with/without header from JASP to JASP and JASP from/to Excel.
Test with empty values.
Test also copy/paste with shortkeys
  • Loading branch information
boutinb authored and JorisGoosen committed Nov 14, 2023
1 parent 6b47452 commit 828a28d
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 34 deletions.
29 changes: 27 additions & 2 deletions Desktop/components/JASP/Widgets/DataTableView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,6 @@ FocusScope
dataTableView.showPopupMenu(parent, mapToGlobal(mouseEvent.x, mouseEvent.y), rowIndex, -1);
}
}

}

columnHeaderDelegate: Rectangle
Expand All @@ -516,6 +515,30 @@ FocusScope
property real iconTextPadding: 10
readonly property int __iconDim: baseBlockDim * preferencesModel.uiScale

Keys.onPressed: (event) =>
{
var controlPressed = Boolean(event.modifiers & Qt.ControlModifier)

if (controlPressed)
{
switch(event.key)
{
case Qt.Key_C:
theView.copy(Qt.point(columnIndex, -1));
event.accepted = true;
break;
case Qt.Key_X:
theView.cut(Qt.point(columnIndex, -1));
event.accepted = true;
break;
case Qt.Key_V:
theView.paste(Qt.point(columnIndex, -1));
event.accepted = true;
break;
}
}
}

Image
{
id: colIcon
Expand All @@ -525,7 +548,7 @@ FocusScope


source: String(dataSetModel.getColumnTypesWithIcons()[columnType]) === "" ? "" : jaspTheme.iconPath + dataSetModel.getColumnTypesWithIcons()[columnType]
width: headerRoot.__iconDim
width: source == "" ? 0 : headerRoot.__iconDim
height: headerRoot.__iconDim

sourceSize { width: width * 2
Expand Down Expand Up @@ -696,6 +719,8 @@ FocusScope
{
if(columnIndex >= 0)
{
headerRoot.forceActiveFocus()

if(mouseEvent.button === Qt.LeftButton || mouseEvent.button === Qt.RightButton)
dataTableView.view.columnSelect(columnIndex, mouseEvent.modifiers & Qt.ShiftModifier, mouseEvent.button === Qt.RightButton)

Expand Down
6 changes: 2 additions & 4 deletions Desktop/data/datasetpackage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2083,7 +2083,7 @@ void DataSetPackage::setWorkspaceEmptyValues(const stringset &emptyValues, bool
emit workspaceEmptyValuesChanged();
}

void DataSetPackage::pasteSpreadsheet(size_t row, size_t col, const std::vector<std::vector<QString>> & cells, intvec coltypes)
void DataSetPackage::pasteSpreadsheet(size_t row, size_t col, const std::vector<std::vector<QString>> & cells, const intvec & coltypes, const QStringList & colNames)
{
JASPTIMER_SCOPE(DataSetPackage::pasteSpreadsheet);

Expand All @@ -2096,8 +2096,6 @@ void DataSetPackage::pasteSpreadsheet(size_t row, size_t col, const std::vector<

if(colCountChanged || rowCountChanged)
setDataSetSize(std::max(size_t(dataColumnCount()), colMax + col), std::max(size_t(dataRowCount()), rowMax + row));

stringvec colNames = getColumnNames();

stringvec changed;

Expand All @@ -2106,6 +2104,7 @@ void DataSetPackage::pasteSpreadsheet(size_t row, size_t col, const std::vector<
int dataCol = c + col;
stringvec colVals = getColumnDataStrs(dataCol);
columnType desiredType = coltypes.size() > c ? columnType(coltypes[c]) : columnType::unknown;
std::string colName = (colNames.size() > c && !colNames[c].isEmpty()) ? fq(colNames[c]) : getColumnName(dataCol);

for(int r=0; r<rowMax; r++)
{
Expand All @@ -2119,7 +2118,6 @@ void DataSetPackage::pasteSpreadsheet(size_t row, size_t col, const std::vector<
colVals[r + row] = cellVal == ColumnUtils::emptyValue ? "" : cellVal;
}

std::string colName = getColumnName(dataCol);
initColumnWithStrings(dataCol, colName, colVals, "", desiredType);

changed.push_back(colName);
Expand Down
2 changes: 1 addition & 1 deletion Desktop/data/datasetpackage.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau
void initColumnWithStrings( QVariant colId, const std::string & newName, const stringvec & values, const std::string & title = "", columnType desiredType = columnType::unknown);
void initializeComputedColumns();

void pasteSpreadsheet(size_t row, size_t column, const std::vector<std::vector<QString>> & cells, intvec colTypes = intvec());
void pasteSpreadsheet(size_t row, size_t column, const std::vector<std::vector<QString>> & cells, const intvec & colTypes = intvec(), const QStringList & colNames = {});

void storeMissingData( const std::string & columnName, const intstrmap & missingData);

Expand Down
4 changes: 2 additions & 2 deletions Desktop/data/datasettablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ bool DataSetTableModel::columnUsedInEasyFilter(int column) const
).toBool();
}

void DataSetTableModel::pasteSpreadsheet(size_t row, size_t col, const std::vector<std::vector<QString> > & cells, std::vector<int> colTypes)
void DataSetTableModel::pasteSpreadsheet(size_t row, size_t col, const std::vector<std::vector<QString> > & cells, const std::vector<int> & colTypes, const QStringList & colNames)
{
QModelIndex idx = mapToSource(index(row, col));
DataSetPackage::pkg()->pasteSpreadsheet(idx.row(), idx.column(), cells, colTypes);
DataSetPackage::pkg()->pasteSpreadsheet(idx.row(), idx.column(), cells, colTypes, colNames);
}

QString DataSetTableModel::insertColumnSpecial(int column, const QMap<QString, QVariant>& props)
Expand Down
2 changes: 1 addition & 1 deletion Desktop/data/datasettablemodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class DataSetTableModel : public DataSetTableProxy

int getColumnIndex(const std::string& col) const { return DataSetPackage::pkg()->getColumnIndex(col); }
bool synchingData() const { return DataSetPackage::pkg()->synchingData(); }
void pasteSpreadsheet(size_t row, size_t col, const std::vector<std::vector<QString>> & cells, std::vector<int> colTypes = std::vector<int>());
void pasteSpreadsheet(size_t row, size_t col, const std::vector<std::vector<QString>> & cells, const std::vector<int> & colTypes = std::vector<int>(), const QStringList & colNames = {});
bool showInactive() const { return _showInactive; }

QString insertColumnSpecial(int column, const QMap<QString, QVariant>& props);
Expand Down
4 changes: 2 additions & 2 deletions Desktop/data/expanddataproxymodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,13 @@ void ExpandDataProxyModel::setData(int row, int col, const QVariant &value, int
_undoStack->endMacro(new SetDataCommand(_sourceModel, row, col, value, role));
}

void ExpandDataProxyModel::pasteSpreadsheet(int row, int col, const std::vector<std::vector<QString>> & cells)
void ExpandDataProxyModel::pasteSpreadsheet(int row, int col, const std::vector<std::vector<QString>> & cells, const QStringList& colNames)
{
if (!_sourceModel || row < 0 || col < 0 || cells.size() == 0 || cells[0].size() == 0)
return;

_expandIfNecessary(row + cells[0].size() - 1, col + cells.size() - 1);
_undoStack->endMacro(new PasteSpreadsheetCommand(_sourceModel, row, col, cells));
_undoStack->endMacro(new PasteSpreadsheetCommand(_sourceModel, row, col, cells, colNames));
}

int ExpandDataProxyModel::setColumnType(int columnIndex, int columnType)
Expand Down
2 changes: 1 addition & 1 deletion Desktop/data/expanddataproxymodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class ExpandDataProxyModel : public QObject
void removeColumns(int start, int count);
void insertRow(int row);
void insertColumn(int col, bool computed, bool R);
void pasteSpreadsheet(int row, int col, const std::vector<std::vector<QString>> & cells);
void pasteSpreadsheet(int row, int col, const std::vector<std::vector<QString>> & cells, const QStringList& colNames = {});
int setColumnType(int columnIndex, int columnType);
void copyColumns(int startCol, const std::vector<Json::Value>& copiedColumns);
Json::Value serializedColumn(int col);
Expand Down
9 changes: 5 additions & 4 deletions Desktop/data/undostack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ void RemoveRowsCommand::redo()
_model->removeRows(_start, _count);
}

PasteSpreadsheetCommand::PasteSpreadsheetCommand(QAbstractItemModel *model, int row, int col, const std::vector<std::vector<QString> > &cells)
: UndoModelCommand(model), _row{row}, _col{col}, _newCells{cells}
PasteSpreadsheetCommand::PasteSpreadsheetCommand(QAbstractItemModel *model, int row, int col, const std::vector<std::vector<QString> > &cells, const QStringList& colNames)
: UndoModelCommand(model), _row{row}, _col{col}, _newCells{cells}, _newColNames{colNames}
{
setText(QObject::tr("Paste values at row %1 column '%2'").arg(rowName(_row)).arg(columnName(_col)));
}
Expand All @@ -190,7 +190,7 @@ void PasteSpreadsheetCommand::undo()
DataSetTableModel* dataSetTable = qobject_cast<DataSetTableModel*>(_model);

if (dataSetTable)
dataSetTable->pasteSpreadsheet(_row, _col, _oldCells);
dataSetTable->pasteSpreadsheet(_row, _col, _oldCells, {}, _oldColNames);
}

void PasteSpreadsheetCommand::redo()
Expand All @@ -199,14 +199,15 @@ void PasteSpreadsheetCommand::redo()
for (int c = 0; c < _newCells.size(); c++)
{
_oldCells.push_back(std::vector<QString>());
_oldColNames.push_back(_model->headerData(_col + c, Qt::Horizontal).toString());
for (int r = 0; r < _newCells[c].size(); r++)
_oldCells[c].push_back(_model->data(_model->index(_row + r, _col + c)).toString());
}

DataSetTableModel* dataSetTable = qobject_cast<DataSetTableModel*>(_model);

if (dataSetTable)
dataSetTable->pasteSpreadsheet(_row, _col, _newCells);
dataSetTable->pasteSpreadsheet(_row, _col, _newCells, {}, _newColNames);
}


Expand Down
4 changes: 3 additions & 1 deletion Desktop/data/undostack.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,16 @@ class SetDataCommand : public UndoModelCommand
class PasteSpreadsheetCommand : public UndoModelCommand
{
public:
PasteSpreadsheetCommand(QAbstractItemModel *model, int row, int col, const std::vector<std::vector<QString>>& cells);
PasteSpreadsheetCommand(QAbstractItemModel *model, int row, int col, const std::vector<std::vector<QString>>& cells, const QStringList & colNames);

void undo() override;
void redo() override;

private:
std::vector<std::vector<QString>> _newCells,
_oldCells;
QStringList _newColNames,
_oldColNames;
int _row = -1,
_col = -1;
};
Expand Down
42 changes: 27 additions & 15 deletions Desktop/qquick/datasetview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1150,9 +1150,13 @@ void DataSetView::_copy(QPoint where, bool clear)
previousRow = selectee.row();
}

int rowsSelected = rows.size();
if(rows.size() == 0)
return; //Nothing to copy

_copiedColumns.clear();

int rowsSelected = rows.size();

if(isColumnHeader(where))
{
rows.insert(rows.begin(), tq(std::vector<std::string>(maxCol - minCol + 1, "")));
Expand All @@ -1163,17 +1167,13 @@ void DataSetView::_copy(QPoint where, bool clear)
}
}

if(rows.size() == 0)
return; //Nothing to copy

QStringList all;
for(const auto & row : rows)
all.append(row.join("\t"));
QString copyThis = all.join("\n");

//Log::log() << "copying:\n" << copyThis << "\nThats it" << std::endl;

QGuiApplication::clipboard()->setText(copyThis);
_lastJaspCopyIntoClipboard = copyThis;

if(clear)
{
Expand Down Expand Up @@ -1203,22 +1203,36 @@ void DataSetView::_copy(QPoint where, bool clear)

void DataSetView::paste(QPoint where)
{
if (where.isNull())
where = selectionTopLeft();

QString clipboardStr = QGuiApplication::clipboard()->text();

if (_lastJaspCopyIntoClipboard != clipboardStr)
// The text in the clipboard does not come from a JASP copy. So clear the reference to the copied columns, so that they are not used for the paste action.
_copiedColumns.clear();

if (isColumnHeader(where) && where.x() >= 0 && _copiedColumns.size() > 0)
_model->copyColumns(where.x(), _copiedColumns);
else
{
QPoint topLeft = isCell(where) ? where : selectionTopLeft();
std::vector<std::vector<QString>> newData;
QStringList colNames,
rows = clipboardStr.contains("\r\n") ? clipboardStr.split("\r\n") : clipboardStr.split("\n");

QClipboard * clipboard = QGuiApplication::clipboard();
//Log::log() << "Clipboard: " << clipboard->text(); //We should not log clipboard for privacy/data anonimity reasons
if (rows.isEmpty()) return;

std::vector<std::vector<QString>> newData;
if (isColumnHeader(where))
{
colNames = rows[0].split("\t");
rows.removeFirst();
}

size_t row = 0, col = 0;
for(const QString & rowStr : clipboard->text().split("\n"))
for(const QString & rowStr : rows)
{
col = 0;
if (rowStr.isEmpty()) continue; // Some editors might throw an empty line onto the end of the clipboard. Should at least have one value on the row
if (rowStr.isEmpty() && row == rows.length() - 1) continue; // Some editors might throw an empty line onto the end of the clipboard. Should at least have one value on the row
for(const QString & cellStr : rowStr.split("\t"))
{
if(newData.size() <= col) newData. resize(col+1);
Expand All @@ -1232,9 +1246,7 @@ void DataSetView::paste(QPoint where)
for(auto& column : newData)
column.resize(row); // Make sure that all columns have the same number of rows

Log::log() << "DataSetView about to paste data (" << col << " columns and " << row << " rows) at row: " << topLeft.y() << " and col: " << topLeft.x() << std::endl;

_model->pasteSpreadsheet(topLeft.y(), topLeft.x(), newData);
_model->pasteSpreadsheet(isColumnHeader(where) ? 0 : where.y(), where.x(), newData, colNames);
}
}

Expand Down
3 changes: 2 additions & 1 deletion Desktop/qquick/datasetview.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ public slots:
long _selectScrollMs = 0;
QPoint _selectionStart = QPoint(-1, -1),
_selectionEnd = QPoint(-1, -1);
std::vector<Json::Value> _copiedColumns;
std::vector<Json::Value> _copiedColumns;
QString _lastJaspCopyIntoClipboard;


};
Expand Down

0 comments on commit 828a28d

Please sign in to comment.