|
|
|
@ -18,6 +18,15 @@ namespace Avalonia.Controls |
|
|
|
{ |
|
|
|
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost |
|
|
|
{ |
|
|
|
public static KeyGesture CutGesture { get; } = AvaloniaLocator.Current |
|
|
|
.GetService<PlatformHotkeyConfiguration>()?.Cut.FirstOrDefault(); |
|
|
|
|
|
|
|
public static KeyGesture CopyGesture { get; } = AvaloniaLocator.Current |
|
|
|
.GetService<PlatformHotkeyConfiguration>()?.Copy.FirstOrDefault(); |
|
|
|
|
|
|
|
public static KeyGesture PasteGesture { get; } = AvaloniaLocator.Current |
|
|
|
.GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault(); |
|
|
|
|
|
|
|
public static readonly StyledProperty<bool> AcceptsReturnProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn)); |
|
|
|
|
|
|
|
@ -103,6 +112,21 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
public static readonly StyledProperty<bool> RevealPasswordProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword)); |
|
|
|
|
|
|
|
public static readonly DirectProperty<TextBox, bool> CanCutProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, bool>( |
|
|
|
nameof(CanCut), |
|
|
|
o => o.CanCut); |
|
|
|
|
|
|
|
public static readonly DirectProperty<TextBox, bool> CanCopyProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, bool>( |
|
|
|
nameof(CanCopy), |
|
|
|
o => o.CanCopy); |
|
|
|
|
|
|
|
public static readonly DirectProperty<TextBox, bool> CanPasteProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, bool>( |
|
|
|
nameof(CanPaste), |
|
|
|
o => o.CanPaste); |
|
|
|
|
|
|
|
struct UndoRedoState : IEquatable<UndoRedoState> |
|
|
|
{ |
|
|
|
@ -126,6 +150,9 @@ namespace Avalonia.Controls |
|
|
|
private UndoRedoHelper<UndoRedoState> _undoRedoHelper; |
|
|
|
private bool _isUndoingRedoing; |
|
|
|
private bool _ignoreTextChanges; |
|
|
|
private bool _canCut; |
|
|
|
private bool _canCopy; |
|
|
|
private bool _canPaste; |
|
|
|
private string _newLine = Environment.NewLine; |
|
|
|
private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; |
|
|
|
|
|
|
|
@ -369,6 +396,41 @@ namespace Avalonia.Controls |
|
|
|
get { return _newLine; } |
|
|
|
set { SetAndRaise(NewLineProperty, ref _newLine, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Clears the current selection, maintaining the <see cref="CaretIndex"/>
|
|
|
|
/// </summary>
|
|
|
|
public void ClearSelection() |
|
|
|
{ |
|
|
|
SelectionStart = SelectionEnd = CaretIndex; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Property for determining if the Cut command can be executed.
|
|
|
|
/// </summary>
|
|
|
|
public bool CanCut |
|
|
|
{ |
|
|
|
get { return _canCut; } |
|
|
|
private set { SetAndRaise(CanCutProperty, ref _canCut, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Property for determining if the Copy command can be executed.
|
|
|
|
/// </summary>
|
|
|
|
public bool CanCopy |
|
|
|
{ |
|
|
|
get { return _canCopy; } |
|
|
|
private set { SetAndRaise(CanCopyProperty, ref _canCopy, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Property for determining if the Paste command can be executed.
|
|
|
|
/// </summary>
|
|
|
|
public bool CanPaste |
|
|
|
{ |
|
|
|
get { return _canPaste; } |
|
|
|
private set { SetAndRaise(CanPasteProperty, ref _canPaste, value); } |
|
|
|
} |
|
|
|
|
|
|
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|
|
|
{ |
|
|
|
@ -387,9 +449,19 @@ namespace Avalonia.Controls |
|
|
|
if (change.Property == TextProperty) |
|
|
|
{ |
|
|
|
UpdatePseudoclasses(); |
|
|
|
UpdateCommandStates(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void UpdateCommandStates() |
|
|
|
{ |
|
|
|
var text = GetSelection(); |
|
|
|
var isNullOrEmpty = string.IsNullOrEmpty(text); |
|
|
|
CanCopy = !isNullOrEmpty; |
|
|
|
CanCut = !isNullOrEmpty && !IsReadOnly; |
|
|
|
CanPaste = !IsReadOnly; |
|
|
|
} |
|
|
|
|
|
|
|
protected override void OnGotFocus(GotFocusEventArgs e) |
|
|
|
{ |
|
|
|
base.OnGotFocus(e); |
|
|
|
@ -404,6 +476,8 @@ namespace Avalonia.Controls |
|
|
|
SelectAll(); |
|
|
|
} |
|
|
|
|
|
|
|
UpdateCommandStates(); |
|
|
|
|
|
|
|
_presenter?.ShowCaret(); |
|
|
|
} |
|
|
|
|
|
|
|
@ -413,11 +487,12 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
if (ContextMenu == null || !ContextMenu.IsOpen) |
|
|
|
{ |
|
|
|
SelectionStart = 0; |
|
|
|
SelectionEnd = 0; |
|
|
|
ClearSelection(); |
|
|
|
RevealPassword = false; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
UpdateCommandStates(); |
|
|
|
|
|
|
|
_presenter?.HideCaret(); |
|
|
|
} |
|
|
|
|
|
|
|
@ -444,7 +519,7 @@ namespace Avalonia.Controls |
|
|
|
text = Text ?? string.Empty; |
|
|
|
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); |
|
|
|
CaretIndex += input.Length; |
|
|
|
SelectionStart = SelectionEnd = CaretIndex; |
|
|
|
ClearSelection(); |
|
|
|
_undoRedoHelper.DiscardRedo(); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -460,19 +535,31 @@ namespace Avalonia.Controls |
|
|
|
return text; |
|
|
|
} |
|
|
|
|
|
|
|
private async void Copy() |
|
|
|
public async void Cut() |
|
|
|
{ |
|
|
|
var text = GetSelection(); |
|
|
|
if (text is null) return; |
|
|
|
|
|
|
|
_undoRedoHelper.Snapshot(); |
|
|
|
Copy(); |
|
|
|
DeleteSelection(); |
|
|
|
_undoRedoHelper.Snapshot(); |
|
|
|
} |
|
|
|
|
|
|
|
public async void Copy() |
|
|
|
{ |
|
|
|
var text = GetSelection(); |
|
|
|
if (text is null) return; |
|
|
|
|
|
|
|
await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))) |
|
|
|
.SetTextAsync(GetSelection()); |
|
|
|
.SetTextAsync(text); |
|
|
|
} |
|
|
|
|
|
|
|
private async void Paste() |
|
|
|
public async void Paste() |
|
|
|
{ |
|
|
|
var text = await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))).GetTextAsync(); |
|
|
|
if (text == null) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (text is null) return; |
|
|
|
|
|
|
|
_undoRedoHelper.Snapshot(); |
|
|
|
HandleTextInput(text); |
|
|
|
@ -511,23 +598,18 @@ namespace Avalonia.Controls |
|
|
|
{ |
|
|
|
if (!IsPasswordBox) |
|
|
|
{ |
|
|
|
_undoRedoHelper.Snapshot(); |
|
|
|
Copy(); |
|
|
|
DeleteSelection(); |
|
|
|
_undoRedoHelper.Snapshot(); |
|
|
|
Cut(); |
|
|
|
} |
|
|
|
|
|
|
|
handled = true; |
|
|
|
} |
|
|
|
else if (Match(keymap.Paste)) |
|
|
|
{ |
|
|
|
|
|
|
|
Paste(); |
|
|
|
handled = true; |
|
|
|
} |
|
|
|
else if (Match(keymap.Undo)) |
|
|
|
{ |
|
|
|
|
|
|
|
try |
|
|
|
{ |
|
|
|
_isUndoingRedoing = true; |
|
|
|
@ -662,7 +744,7 @@ namespace Avalonia.Controls |
|
|
|
SetTextInternal(text.Substring(0, caretIndex - removedCharacters) + |
|
|
|
text.Substring(caretIndex)); |
|
|
|
CaretIndex -= removedCharacters; |
|
|
|
SelectionStart = SelectionEnd = CaretIndex; |
|
|
|
ClearSelection(); |
|
|
|
} |
|
|
|
_undoRedoHelper.Snapshot(); |
|
|
|
|
|
|
|
@ -735,7 +817,7 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
else if (movement) |
|
|
|
{ |
|
|
|
SelectionStart = SelectionEnd = CaretIndex; |
|
|
|
ClearSelection(); |
|
|
|
} |
|
|
|
|
|
|
|
if (handled || movement) |
|
|
|
@ -1042,7 +1124,8 @@ namespace Avalonia.Controls |
|
|
|
var end = Math.Max(selectionStart, selectionEnd); |
|
|
|
var text = Text; |
|
|
|
SetTextInternal(text.Substring(0, start) + text.Substring(end)); |
|
|
|
SelectionStart = SelectionEnd = CaretIndex = start; |
|
|
|
CaretIndex = start; |
|
|
|
ClearSelection(); |
|
|
|
return true; |
|
|
|
} |
|
|
|
else |
|
|
|
@ -1131,7 +1214,8 @@ namespace Avalonia.Controls |
|
|
|
set |
|
|
|
{ |
|
|
|
Text = value.Text; |
|
|
|
SelectionStart = SelectionEnd = CaretIndex = value.CaretPosition; |
|
|
|
CaretIndex = value.CaretPosition; |
|
|
|
ClearSelection(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|