using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace Rozpisky.Behaviors;
///
/// Schránka pro (Ctrl+C / Ctrl+V) přes ClipboardContentBinding sloupců –
/// WPF DataGrid vkládání nativně neumí a jeho kopírování se v jiných aplikacích (Excel) nemusí projevit
/// (data dává na schránku bez „flush"). Zapíná se per-grid: beh:GridPaste.Enabled="True".
///
/// - Ctrl+C: obálku výběru zkopíruje jako TSV (tab mezi sloupci, CRLF mezi řádky) a přes
/// (s persistem) – Excel i vlastní grid ji pak převezmou.
/// - Ctrl+V: schránku (TSV) vloží do buněk. Při vícebuňkovém výběru se oblast výběru vyplní
/// dlaždicově (vzor ze schránky se opakuje – jako fill handle); jinak se blok vloží od aktivní buňky
/// doprava/dolů. Řádky se nepřidávají (přetečení se zahodí), mimo rozsah sloupců i read-only sloupce
/// se přeskočí. Zápis jde přes (bez edit
/// módu), nekonvertibilní hodnota buňku tiše vynechá – shodně s Theming.GridFill.
/// - Ctrl+D: „fill down" jako v Excelu – při výběru přes víc řádků vyplní horní řádek výběru
/// dolů do zbytku výběru; u výběru v jednom řádku zkopíruje hodnoty z řádku nad ním. Kopíruje se
/// přes ClipboardContentBinding sloupce (bez edit módu), stejně jako fill handle.
///
/// Při otevřené editaci buňky se klávesa nechá projít (kopíruje/vkládá se text v editoru). Bez
/// CSV-uvozovkového escapování a bez Ctrl+Z – shodně s fill handle.
///
public static class GridPaste
{
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(GridPaste),
new PropertyMetadata(false, OnEnabledChanged));
public static void SetEnabled(DependencyObject o, bool v) => o.SetValue(EnabledProperty, v);
public static bool GetEnabled(DependencyObject o) => (bool)o.GetValue(EnabledProperty);
private static void OnEnabledChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (o is not DataGrid grid) return;
if ((bool)e.NewValue) grid.PreviewKeyDown += OnKeyDown;
else grid.PreviewKeyDown -= OnKeyDown;
}
private static void OnKeyDown(object sender, KeyEventArgs e)
{
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) return;
if (sender is not DataGrid grid) return;
if (IsEditing(e.OriginalSource)) return; // rozeditovaná buňka → schránku řeší editor
if (e.Key == Key.C) { if (Copy(grid)) e.Handled = true; }
else if (e.Key == Key.V) { if (Paste(grid)) e.Handled = true; }
else if (e.Key == Key.D) { if (FillDown(grid)) e.Handled = true; }
}
// ---------------------------------------------------------------- kopírování
/// Zkopíruje obálku výběru jako TSV na schránku (s persistem, ať ji převezme i Excel).
private static bool Copy(DataGrid grid)
{
if (SelectionBounds(grid) is not (int r0, int r1, int c0, int c1)) return false;
var cols = ColumnsByDisplay(grid);
var sb = new StringBuilder();
for (int r = r0; r <= r1; r++)
{
if (r < 0 || r >= grid.Items.Count) continue;
var item = grid.Items[r];
if (item == CollectionView.NewItemPlaceholder) continue;
for (int c = c0; c <= c1; c++)
{
if (c > c0) sb.Append('\t');
if (c >= 0 && c < cols.Count)
sb.Append(cols[c].OnCopyingCellClipboardContent(item)?.ToString());
}
sb.Append("\r\n");
}
try { Clipboard.SetText(sb.ToString(), TextDataFormat.UnicodeText); return true; }
catch { return false; } // schránku drží jiný proces → nech případně proběhnout nativní kopii
}
// ---------------------------------------------------------------- vkládání
private static bool Paste(DataGrid grid)
{
if (grid.IsReadOnly) return false;
var text = Clipboard.ContainsText() ? Clipboard.GetText() : null;
if (string.IsNullOrEmpty(text)) return false;
grid.CommitEdit(DataGridEditingUnit.Row, true);
var data = ParseTsv(text);
if (data.Count == 0) return false;
var cols = ColumnsByDisplay(grid);
// Vícebuňkový výběr → vyplň jeho obálku dlaždicově vzorem ze schránky (jako fill handle);
// 1×1 schránka tím vyplní celý výběr, 1×N se v širším výběru opakuje.
if (SelectionBounds(grid) is (int r0, int r1, int c0, int c1) && (r1 > r0 || c1 > c0))
{
for (int r = r0; r <= r1; r++)
{
if (r < 0 || r >= grid.Items.Count) continue;
var item = grid.Items[r];
if (item == CollectionView.NewItemPlaceholder) continue;
var src = data[Mod(r - r0, data.Count)];
if (src.Length == 0) continue;
for (int c = c0; c <= c1; c++)
if (c >= 0 && c < cols.Count)
WriteCell(cols[c], item, src[Mod(c - c0, src.Length)]);
}
return true;
}
// Jedna buňka (nebo bez výběru) → vlož celý blok od aktivní buňky doprava/dolů.
if (Anchor(grid) is not (int ar, int ac)) return false;
for (int i = 0; i < data.Count; i++)
{
int r = ar + i;
if (r >= grid.Items.Count) break;
var item = grid.Items[r];
if (item == CollectionView.NewItemPlaceholder) continue;
var src = data[i];
for (int j = 0; j < src.Length; j++)
{
int c = ac + j;
if (c >= 0 && c < cols.Count) WriteCell(cols[c], item, src[j]);
}
}
return true;
}
// ---------------------------------------------------------------- fill down (Ctrl+D)
/// Excelovské „fill down": vícebuňkový výběr přes řádky vyplní horní řádek výběru dolů;
/// výběr v jediném řádku převezme hodnoty z řádku nad ním. Kopíruje se přes ClipboardContentBinding
/// sloupce (bez edit módu). Vrací true, když se aspoň něco dalo zapsat.
private static bool FillDown(DataGrid grid)
{
if (grid.IsReadOnly) return false;
if (SelectionBounds(grid) is not (int r0, int r1, int c0, int c1)) return false;
grid.CommitEdit(DataGridEditingUnit.Row, true);
var cols = ColumnsByDisplay(grid);
// Výběr v jednom řádku → zdroj je řádek nad výběrem (jinak horní řádek výběru).
int srcRow = r1 > r0 ? r0 : r0 - 1;
if (srcRow < 0 || srcRow >= grid.Items.Count) return false;
var srcItem = grid.Items[srcRow];
if (srcItem == CollectionView.NewItemPlaceholder) return false;
bool any = false;
for (int r = r1 > r0 ? r0 + 1 : r0; r <= r1; r++)
{
if (r < 0 || r >= grid.Items.Count) continue;
var item = grid.Items[r];
if (item == CollectionView.NewItemPlaceholder) continue;
for (int c = c0; c <= c1; c++)
if (c >= 0 && c < cols.Count && CopyCell(cols[c], srcItem, item)) any = true;
}
return any;
}
/// Zkopíruje hodnotu buňky ze zdrojové do cílové položky přes clipboard-binding sloupce
/// (bez edit módu). Read-only sloupec a nekonvertibilní hodnotu tiše vynechá. Vrací true při zápisu.
private static bool CopyCell(DataGridColumn column, object srcItem, object dstItem)
{
if (column.IsReadOnly) return false;
try { column.OnPastingCellClipboardContent(dstItem, column.OnCopyingCellClipboardContent(srcItem)); return true; }
catch { return false; }
}
/// Zapíše hodnotu do buňky přes clipboard-binding sloupce (bez edit módu). Read-only sloupec
/// a nekonvertibilní hodnotu tiše vynechá – stejně jako GridFill.CopyCell.
private static void WriteCell(DataGridColumn column, object item, string value)
{
if (column.IsReadOnly) return;
try { column.OnPastingCellClipboardContent(item, value); }
catch { /* nekonvertibilní hodnota (např. text do bool/čísla) → buňku vynech */ }
}
// ---------------------------------------------------------------- pomocné
/// Obálka výběru: min/max index řádku a min/max DisplayIndex sloupce, nebo null.
/// (DisplayIndex je souvislý 0..n-1, takže odpovídá indexu do .)
private static (int r0, int r1, int c0, int c1)? SelectionBounds(DataGrid grid)
{
var cells = grid.SelectedCells;
if (cells.Count == 0) return null;
int r0 = int.MaxValue, r1 = -1, c0 = int.MaxValue, c1 = -1;
var rowIndex = new Dictionary