mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-07-08 18:16:08 +00:00
update tile-pasting to match Excel's behavior
This commit is contained in:
committed by
Atanas Korchev
parent
483e1c0901
commit
0a44fb7815
@@ -36,23 +36,22 @@ public class PasteCommandTests
|
||||
sheet.Cells[1, 0].Value = 20d;
|
||||
sheet.Cells[2, 0].Value = 30d;
|
||||
sheet.Cells[10, 3].Formula = "=SUM(B1:B3)"; // depends on the paste destination
|
||||
sheet.Cells[10, 3].Formula = "=SUM(B1:B3)";
|
||||
|
||||
var clipboard = new SpreadsheetClipboard();
|
||||
sheet.Selection.Select(new RangeRef(new CellRef(0, 0), new CellRef(2, 0)));
|
||||
sheet.Selection.Select(new RangeRef(new CellRef(0, 0), new CellRef(2, 0))); // A1:A3
|
||||
clipboard.Copy(sheet);
|
||||
|
||||
var evals = 0;
|
||||
sheet.Cells[10, 3].Changed += _ => evals++;
|
||||
|
||||
var stack = new UndoRedoStack(sheet);
|
||||
stack.Execute(new PasteCommand(clipboard, sheet, new RangeRef(new CellRef(0, 1), new CellRef(0, 1)), null));
|
||||
stack.Execute(new PasteCommand(clipboard, sheet, new RangeRef(new CellRef(0, 1), new CellRef(0, 1)), null)); // paste to B1
|
||||
|
||||
Assert.Equal(60d, sheet.Cells[10, 3].Value);
|
||||
Assert.True(evals <= 2);
|
||||
|
||||
stack.Undo();
|
||||
Assert.Equal(0d, sheet.Cells[10, 3].Value);
|
||||
Assert.Equal(0d, sheet.Cells[10, 3].Value); // destination cleared -> SUM 0
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -67,7 +66,7 @@ public class PasteCommandTests
|
||||
clipboard.Cut(sheet);
|
||||
|
||||
var stack = new UndoRedoStack(sheet);
|
||||
stack.Execute(new PasteCommand(clipboard, sheet, new RangeRef(new CellRef(0, 2), new CellRef(0, 2)), null));
|
||||
stack.Execute(new PasteCommand(clipboard, sheet, new RangeRef(new CellRef(0, 2), new CellRef(0, 2)), null)); // -> C1
|
||||
|
||||
Assert.Equal("A", sheet.Cells[0, 2].Value);
|
||||
Assert.Equal("B", sheet.Cells[1, 2].Value);
|
||||
@@ -93,7 +92,7 @@ public class PasteCommandTests
|
||||
clipboard.Cut(sheet);
|
||||
|
||||
var stack = new UndoRedoStack(sheet);
|
||||
stack.Execute(new PasteCommand(clipboard, sheet, new RangeRef(new CellRef(0, 2), new CellRef(0, 2)), null));
|
||||
stack.Execute(new PasteCommand(clipboard, sheet, new RangeRef(new CellRef(0, 2), new CellRef(0, 2)), null)); // move A1 -> C1
|
||||
stack.Undo();
|
||||
|
||||
Assert.Equal("A", sheet.Cells[0, 0].Value);
|
||||
@@ -110,23 +109,27 @@ public class PasteCommandTests
|
||||
public void TiledTextPaste_FillsSelectedRange()
|
||||
{
|
||||
var sheet = new Worksheet(20, 5);
|
||||
sheet.Cells[0, 0].Value = "X";
|
||||
|
||||
var clipboard = new SpreadsheetClipboard();
|
||||
var stack = new UndoRedoStack(sheet);
|
||||
|
||||
var destination = new RangeRef(new CellRef(0, 0), new CellRef(1, 1));
|
||||
|
||||
stack.Execute(new PasteCommand(clipboard, sheet, destination, "X"));
|
||||
sheet.Selection.Select(new CellRef(0, 0));
|
||||
clipboard.Copy(sheet);
|
||||
|
||||
var stack = new UndoRedoStack(sheet);
|
||||
var destination = new RangeRef(new CellRef(0, 1), new CellRef(1, 2));
|
||||
|
||||
stack.Execute(new PasteCommand(clipboard, sheet, destination, null));
|
||||
|
||||
Assert.Equal("X", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("X", sheet.Cells[0, 1].Value);
|
||||
Assert.Equal("X", sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("X", sheet.Cells[0, 2].Value);
|
||||
Assert.Equal("X", sheet.Cells[1, 1].Value);
|
||||
Assert.Equal("X", sheet.Cells[1, 2].Value);
|
||||
|
||||
stack.Undo();
|
||||
|
||||
Assert.Null(sheet.Cells[0, 0].Value);
|
||||
Assert.Null(sheet.Cells[0, 1].Value);
|
||||
Assert.Null(sheet.Cells[1, 0].Value);
|
||||
Assert.Null(sheet.Cells[0, 2].Value);
|
||||
Assert.Null(sheet.Cells[1, 1].Value);
|
||||
Assert.Null(sheet.Cells[1, 2].Value);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class SpreadsheetClipboardTests
|
||||
sheet.Selection.Select(CellRef.Parse("A1"));
|
||||
var clipboard = new SpreadsheetClipboard();
|
||||
clipboard.Cut(sheet);
|
||||
clipboard.Paste(sheet, CellRef.Parse("B2"));
|
||||
clipboard.Paste(sheet, RangeRef.Parse("B2"));
|
||||
Assert.Equal("=A1", sheet.Cells[1, 1].Formula); // not adjusted
|
||||
Assert.Null(sheet.Cells[0, 0].Formula); // source cleared
|
||||
Assert.Null(sheet.Cells[0, 0].Value);
|
||||
|
||||
@@ -12,8 +12,6 @@ namespace Radzen.Blazor.Spreadsheet;
|
||||
// the live clipboard. Batching comes from the UndoRedoStack.
|
||||
class PasteCommand : RangeSnapshotCommandBase
|
||||
{
|
||||
private static readonly string[] LineSeparators = ["\r\n", "\r", "\n"];
|
||||
|
||||
private readonly SpreadsheetClipboard clipboard;
|
||||
private readonly RangeRef destination;
|
||||
private readonly string? text;
|
||||
@@ -38,13 +36,8 @@ class PasteCommand : RangeSnapshotCommandBase
|
||||
Restore(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text) && !destination.Collapsed)
|
||||
{
|
||||
return ExecuteTiledTextPaste();
|
||||
}
|
||||
|
||||
var destinationRange = clipboard.GetPasteRange(sheet, destination.Start, text);
|
||||
|
||||
var destinationRange = clipboard.GetPasteRange(sheet, destination, text);
|
||||
|
||||
if (destinationRange == RangeRef.Invalid)
|
||||
{
|
||||
@@ -60,70 +53,18 @@ class PasteCommand : RangeSnapshotCommandBase
|
||||
|
||||
if (text is not null)
|
||||
{
|
||||
clipboard.Paste(sheet, destination.Start, text);
|
||||
clipboard.Paste(sheet, destination, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
clipboard.Paste(sheet, destination.Start);
|
||||
clipboard.Paste(sheet, destination);
|
||||
}
|
||||
|
||||
CaptureResult();
|
||||
pasted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ExecuteTiledTextPaste()
|
||||
{
|
||||
var rows = text!.Split(LineSeparators, StringSplitOptions.None);
|
||||
var data = rows.Select(r => r.Split('\t')).ToArray();
|
||||
|
||||
int dataRowCount = data.Length;
|
||||
if (dataRowCount > 0 && data[dataRowCount - 1].Length > 0 && string.IsNullOrEmpty(data[dataRowCount - 1][0]))
|
||||
{
|
||||
dataRowCount--;
|
||||
}
|
||||
|
||||
if (dataRowCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int dataColCount = data[0].Length;
|
||||
int startRow = destination.Start.Row;
|
||||
int startCol = destination.Start.Column;
|
||||
int endRow = destination.End.Row;
|
||||
int endCol = destination.End.Column;
|
||||
|
||||
var cellsToCapture = new List<CellRef>();
|
||||
for (int r = startRow; r <= endRow; r++)
|
||||
{
|
||||
for (int c = startCol; c <= endCol; c++)
|
||||
{
|
||||
cellsToCapture.Add(new CellRef(r, c));
|
||||
}
|
||||
}
|
||||
|
||||
CaptureRange(cellsToCapture);
|
||||
|
||||
for (int r = startRow; r <= endRow; r++)
|
||||
{
|
||||
for (int c = startCol; c <= endCol; c++)
|
||||
{
|
||||
var cellRef = new CellRef(r, c);
|
||||
if (sheet.IsCellEditable(cellRef))
|
||||
{
|
||||
int dataRowIndex = (r - startRow) % dataRowCount;
|
||||
int dataColIndex = (c - startCol) % dataColCount;
|
||||
sheet.Cells[r, c].Value = data[dataRowIndex][dataColIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CaptureResult();
|
||||
pasted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override void Unexecute() => RestoreSnapshot();
|
||||
|
||||
private void CaptureResult()
|
||||
|
||||
@@ -35,26 +35,45 @@ class SpreadsheetClipboard
|
||||
csv = sheet.GetDelimitedString(range.Value);
|
||||
}
|
||||
|
||||
public void Paste(Worksheet targetSheet, CellRef destinationStart)
|
||||
public void Paste(Worksheet targetSheet, RangeRef destination)
|
||||
{
|
||||
if (range.HasValue && sheet is not null)
|
||||
{
|
||||
var adjustment = operation == ClipboardOperation.Copy ? FormulaAdjustment.AdjustRelative : FormulaAdjustment.Preserve;
|
||||
targetSheet.PasteRange(sheet, range.Value, destinationStart, adjustment);
|
||||
var source = range.Value;
|
||||
|
||||
if (operation == ClipboardOperation.Copy &&
|
||||
!destination.Collapsed &&
|
||||
destination.Rows % source.Rows == 0 &&
|
||||
destination.Columns % source.Columns == 0)
|
||||
{
|
||||
for (var r = 0; r < destination.Rows; r += source.Rows)
|
||||
{
|
||||
for (var c = 0; c < destination.Columns; c += source.Columns)
|
||||
{
|
||||
var tileStart = new CellRef(destination.Start.Row + r, destination.Start.Column + c);
|
||||
targetSheet.PasteRange(sheet, source, tileStart, adjustment);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetSheet.PasteRange(sheet, source, destination.Start, adjustment);
|
||||
}
|
||||
|
||||
if (operation == ClipboardOperation.Move)
|
||||
{
|
||||
Clear(sheet, range.Value);
|
||||
Clear(sheet, source);
|
||||
ClearInternal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPaste(Worksheet targetSheet, CellRef destinationStart, string pastedText)
|
||||
public bool TryPaste(Worksheet targetSheet, RangeRef destination, string pastedText)
|
||||
{
|
||||
if (range.HasValue && sheet is not null && !string.IsNullOrEmpty(csv) && pastedText == csv)
|
||||
{
|
||||
Paste(targetSheet, destinationStart);
|
||||
Paste(targetSheet, destination);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -63,20 +82,29 @@ class SpreadsheetClipboard
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Paste(Worksheet targetSheet, CellRef destinationStart, string pastedText)
|
||||
public void Paste(Worksheet targetSheet, RangeRef destination, string pastedText)
|
||||
{
|
||||
if (!TryPaste(targetSheet, destinationStart, pastedText))
|
||||
if (!TryPaste(targetSheet, destination, pastedText))
|
||||
{
|
||||
targetSheet.InsertDelimitedString(destinationStart, pastedText);
|
||||
targetSheet.InsertDelimitedString(destination.Start, pastedText);
|
||||
}
|
||||
}
|
||||
|
||||
public RangeRef GetPasteRange(Worksheet targetSheet, CellRef destinationStart, string? pastedText)
|
||||
public RangeRef GetPasteRange(Worksheet targetSheet, RangeRef destination, string? pastedText)
|
||||
{
|
||||
if (range.HasValue && sheet is not null && !string.IsNullOrEmpty(csv) && (pastedText is null || pastedText == csv))
|
||||
{
|
||||
var source = range.Value;
|
||||
return new RangeRef(destinationStart, new CellRef(destinationStart.Row + source.Rows - 1, destinationStart.Column + source.Columns - 1));
|
||||
|
||||
if (operation == ClipboardOperation.Copy &&
|
||||
!destination.Collapsed &&
|
||||
destination.Rows % source.Rows == 0 &&
|
||||
destination.Columns % source.Columns == 0)
|
||||
{
|
||||
return destination;
|
||||
}
|
||||
|
||||
return new RangeRef(destination.Start, new CellRef(destination.Start.Row + source.Rows - 1, destination.Start.Column + source.Columns - 1));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(pastedText))
|
||||
@@ -85,21 +113,21 @@ class SpreadsheetClipboard
|
||||
}
|
||||
|
||||
var lines = pastedText.Split(["\r\n", "\r", "\n"], StringSplitOptions.None);
|
||||
var rowCount = Math.Min(lines.Length, targetSheet.RowCount - destinationStart.Row);
|
||||
var rowCount = Math.Min(lines.Length, targetSheet.RowCount - destination.Start.Row);
|
||||
|
||||
var columnCount = 0;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
columnCount = Math.Max(columnCount, line.Split('\t').Length);
|
||||
}
|
||||
columnCount = Math.Min(columnCount, targetSheet.ColumnCount - destinationStart.Column);
|
||||
columnCount = Math.Min(columnCount, targetSheet.ColumnCount - destination.Start.Column);
|
||||
|
||||
if (rowCount <= 0 || columnCount <= 0)
|
||||
{
|
||||
return RangeRef.Invalid;
|
||||
}
|
||||
|
||||
return new RangeRef(destinationStart, new CellRef(destinationStart.Row + rowCount - 1, destinationStart.Column + columnCount - 1));
|
||||
return new RangeRef(destination.Start, new CellRef(destination.Start.Row + rowCount - 1, destination.Start.Column + columnCount - 1));
|
||||
}
|
||||
|
||||
// Cross-sheet moves are not covered: per-sheet undo stacks can't snapshot a source on another sheet.
|
||||
|
||||
Reference in New Issue
Block a user