diff --git a/Desktop/components/JASP/Widgets/DataTableView.qml b/Desktop/components/JASP/Widgets/DataTableView.qml index 83c2b7c63b..6b6b484ca4 100644 --- a/Desktop/components/JASP/Widgets/DataTableView.qml +++ b/Desktop/components/JASP/Widgets/DataTableView.qml @@ -505,7 +505,6 @@ FocusScope dataTableView.showPopupMenu(parent, mapToGlobal(mouseEvent.x, mouseEvent.y), rowIndex, -1); } } - } columnHeaderDelegate: Rectangle @@ -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 @@ -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 @@ -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) diff --git a/Desktop/data/datasetpackage.cpp b/Desktop/data/datasetpackage.cpp index 3717733741..275eba838a 100644 --- a/Desktop/data/datasetpackage.cpp +++ b/Desktop/data/datasetpackage.cpp @@ -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> & cells, intvec coltypes) +void DataSetPackage::pasteSpreadsheet(size_t row, size_t col, const std::vector> & cells, const intvec & coltypes, const QStringList & colNames) { JASPTIMER_SCOPE(DataSetPackage::pasteSpreadsheet); @@ -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; @@ -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> & cells, intvec colTypes = intvec()); + void pasteSpreadsheet(size_t row, size_t column, const std::vector> & cells, const intvec & colTypes = intvec(), const QStringList & colNames = {}); void storeMissingData( const std::string & columnName, const intstrmap & missingData); diff --git a/Desktop/data/datasettablemodel.cpp b/Desktop/data/datasettablemodel.cpp index 058a31cc5d..94f4a5edb6 100644 --- a/Desktop/data/datasettablemodel.cpp +++ b/Desktop/data/datasettablemodel.cpp @@ -74,10 +74,10 @@ bool DataSetTableModel::columnUsedInEasyFilter(int column) const ).toBool(); } -void DataSetTableModel::pasteSpreadsheet(size_t row, size_t col, const std::vector > & cells, std::vector colTypes) +void DataSetTableModel::pasteSpreadsheet(size_t row, size_t col, const std::vector > & cells, const std::vector & 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& props) diff --git a/Desktop/data/datasettablemodel.h b/Desktop/data/datasettablemodel.h index 6116323a69..612ace8bdc 100644 --- a/Desktop/data/datasettablemodel.h +++ b/Desktop/data/datasettablemodel.h @@ -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> & cells, std::vector colTypes = std::vector()); + void pasteSpreadsheet(size_t row, size_t col, const std::vector> & cells, const std::vector & colTypes = std::vector(), const QStringList & colNames = {}); bool showInactive() const { return _showInactive; } QString insertColumnSpecial(int column, const QMap& props); diff --git a/Desktop/data/expanddataproxymodel.cpp b/Desktop/data/expanddataproxymodel.cpp index 94b0478bcb..1235cf4b6a 100644 --- a/Desktop/data/expanddataproxymodel.cpp +++ b/Desktop/data/expanddataproxymodel.cpp @@ -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> & cells) +void ExpandDataProxyModel::pasteSpreadsheet(int row, int col, const std::vector> & 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) diff --git a/Desktop/data/expanddataproxymodel.h b/Desktop/data/expanddataproxymodel.h index 61d47bb989..85a8bc9923 100644 --- a/Desktop/data/expanddataproxymodel.h +++ b/Desktop/data/expanddataproxymodel.h @@ -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> & cells); + void pasteSpreadsheet(int row, int col, const std::vector> & cells, const QStringList& colNames = {}); int setColumnType(int columnIndex, int columnType); void copyColumns(int startCol, const std::vector& copiedColumns); Json::Value serializedColumn(int col); diff --git a/Desktop/data/undostack.cpp b/Desktop/data/undostack.cpp index 7dc86d9d0c..30b0f84fb3 100644 --- a/Desktop/data/undostack.cpp +++ b/Desktop/data/undostack.cpp @@ -179,8 +179,8 @@ void RemoveRowsCommand::redo() _model->removeRows(_start, _count); } -PasteSpreadsheetCommand::PasteSpreadsheetCommand(QAbstractItemModel *model, int row, int col, const std::vector > &cells) - : UndoModelCommand(model), _row{row}, _col{col}, _newCells{cells} +PasteSpreadsheetCommand::PasteSpreadsheetCommand(QAbstractItemModel *model, int row, int col, const std::vector > &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))); } @@ -190,7 +190,7 @@ void PasteSpreadsheetCommand::undo() DataSetTableModel* dataSetTable = qobject_cast(_model); if (dataSetTable) - dataSetTable->pasteSpreadsheet(_row, _col, _oldCells); + dataSetTable->pasteSpreadsheet(_row, _col, _oldCells, {}, _oldColNames); } void PasteSpreadsheetCommand::redo() @@ -199,6 +199,7 @@ void PasteSpreadsheetCommand::redo() for (int c = 0; c < _newCells.size(); c++) { _oldCells.push_back(std::vector()); + _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()); } @@ -206,7 +207,7 @@ void PasteSpreadsheetCommand::redo() DataSetTableModel* dataSetTable = qobject_cast(_model); if (dataSetTable) - dataSetTable->pasteSpreadsheet(_row, _col, _newCells); + dataSetTable->pasteSpreadsheet(_row, _col, _newCells, {}, _newColNames); } diff --git a/Desktop/data/undostack.h b/Desktop/data/undostack.h index ed3e330759..cc3ea39b89 100644 --- a/Desktop/data/undostack.h +++ b/Desktop/data/undostack.h @@ -201,7 +201,7 @@ class SetDataCommand : public UndoModelCommand class PasteSpreadsheetCommand : public UndoModelCommand { public: - PasteSpreadsheetCommand(QAbstractItemModel *model, int row, int col, const std::vector>& cells); + PasteSpreadsheetCommand(QAbstractItemModel *model, int row, int col, const std::vector>& cells, const QStringList & colNames); void undo() override; void redo() override; @@ -209,6 +209,8 @@ class PasteSpreadsheetCommand : public UndoModelCommand private: std::vector> _newCells, _oldCells; + QStringList _newColNames, + _oldColNames; int _row = -1, _col = -1; }; diff --git a/Desktop/qquick/datasetview.cpp b/Desktop/qquick/datasetview.cpp index 872a482982..f099a17070 100644 --- a/Desktop/qquick/datasetview.cpp +++ b/Desktop/qquick/datasetview.cpp @@ -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(maxCol - minCol + 1, ""))); @@ -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) { @@ -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> 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> 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); @@ -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); } } diff --git a/Desktop/qquick/datasetview.h b/Desktop/qquick/datasetview.h index a2e7c99f14..40ae7c2677 100644 --- a/Desktop/qquick/datasetview.h +++ b/Desktop/qquick/datasetview.h @@ -333,7 +333,8 @@ public slots: long _selectScrollMs = 0; QPoint _selectionStart = QPoint(-1, -1), _selectionEnd = QPoint(-1, -1); - std::vector _copiedColumns; + std::vector _copiedColumns; + QString _lastJaspCopyIntoClipboard; };