From 6763337e25befce9c77e1a51da2cf57556fbfd0e Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Sun, 6 Jun 2021 12:45:53 -0400 Subject: [PATCH 1/5] Add IsUndoEnabled to TextBox --- src/Avalonia.Controls/TextBox.cs | 87 +++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 3c221cbf27..e38531e540 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -119,9 +119,9 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(RevealPassword)); public static readonly DirectProperty CanCutProperty = - AvaloniaProperty.RegisterDirect( - nameof(CanCut), - o => o.CanCut); + AvaloniaProperty.RegisterDirect( + nameof(CanCut), + o => o.CanCut); public static readonly DirectProperty CanCopyProperty = AvaloniaProperty.RegisterDirect( @@ -129,9 +129,14 @@ namespace Avalonia.Controls o => o.CanCopy); public static readonly DirectProperty CanPasteProperty = - AvaloniaProperty.RegisterDirect( - nameof(CanPaste), - o => o.CanPaste); + AvaloniaProperty.RegisterDirect( + nameof(CanPaste), + o => o.CanPaste); + + public static readonly StyledProperty IsUndoEnabledProperty = + AvaloniaProperty.Register( + nameof(IsUndoEnabled), + defaultValue: true); struct UndoRedoState : IEquatable { @@ -218,7 +223,7 @@ namespace Avalonia.Controls value = CoerceCaretIndex(value); SetAndRaise(CaretIndexProperty, ref _caretIndex, value); UndoRedoState state; - if (_undoRedoHelper.TryGetLastState(out state) && state.Text == Text) + if (IsUndoEnabled && _undoRedoHelper.TryGetLastState(out state) && state.Text == Text) _undoRedoHelper.UpdateLastState(); } } @@ -316,7 +321,7 @@ namespace Avalonia.Controls SelectionEnd = CoerceCaretIndex(SelectionEnd, value); CaretIndex = CoerceCaretIndex(caretIndex, value); - if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing) + if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) { _undoRedoHelper.Clear(); } @@ -329,7 +334,7 @@ namespace Avalonia.Controls get { return GetSelection(); } set { - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); if (string.IsNullOrEmpty(value)) { DeleteSelection(); @@ -338,7 +343,7 @@ namespace Avalonia.Controls { HandleTextInput(value); } - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); } } @@ -446,6 +451,27 @@ namespace Avalonia.Controls private set { SetAndRaise(CanPasteProperty, ref _canPaste, value); } } + /// + /// Property for determining whether undo/redo is enabled + /// + public bool IsUndoEnabled + { + get { return GetValue(IsUndoEnabledProperty); } + set + { + SetValue(IsUndoEnabledProperty, value); + if (value == false) + { + // from docs at + // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: + // "Setting this property to false clears the undo stack. + // Therefore, if you disable undo and then re-enable it, undo commands still do not work + // because the undo stack was emptied when you disabled undo." + _undoRedoHelper.Clear(); + } + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); @@ -551,7 +577,10 @@ namespace Avalonia.Controls SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); CaretIndex += input.Length; ClearSelection(); - _undoRedoHelper.DiscardRedo(); + if (IsUndoEnabled) + { + _undoRedoHelper.DiscardRedo(); + } } } @@ -570,10 +599,10 @@ namespace Avalonia.Controls var text = GetSelection(); if (text is null) return; - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); Copy(); DeleteSelection(); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); } public async void Copy() @@ -591,9 +620,9 @@ namespace Avalonia.Controls if (text is null) return; - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); HandleTextInput(text); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); } protected override void OnKeyDown(KeyEventArgs e) @@ -638,7 +667,7 @@ namespace Avalonia.Controls Paste(); handled = true; } - else if (Match(keymap.Undo)) + else if (Match(keymap.Undo) && IsUndoEnabled) { try { @@ -652,7 +681,7 @@ namespace Avalonia.Controls handled = true; } - else if (Match(keymap.Redo)) + else if (Match(keymap.Redo) && IsUndoEnabled) { try { @@ -752,7 +781,7 @@ namespace Avalonia.Controls break; case Key.Back: - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); if (hasWholeWordModifiers && SelectionStart == SelectionEnd) { SetSelectionForControlBackspace(); @@ -776,13 +805,13 @@ namespace Avalonia.Controls CaretIndex -= removedCharacters; ClearSelection(); } - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; break; case Key.Delete: - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); if (hasWholeWordModifiers && SelectionStart == SelectionEnd) { SetSelectionForControlDelete(); @@ -804,7 +833,7 @@ namespace Avalonia.Controls SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters)); } - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; break; @@ -812,9 +841,9 @@ namespace Avalonia.Controls case Key.Enter: if (AcceptsReturn) { - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); HandleTextInput(NewLine); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; } @@ -823,9 +852,9 @@ namespace Avalonia.Controls case Key.Tab: if (AcceptsTab) { - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); HandleTextInput("\t"); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; } else @@ -1251,5 +1280,13 @@ namespace Avalonia.Controls ClearSelection(); } } + + private void SnapshotUndoRedo() + { + if (IsUndoEnabled) + { + _undoRedoHelper.Snapshot(); + } + } } } From c6eb57c5350cf2e236f6c3c8bc26297b24d70ef7 Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Sun, 6 Jun 2021 13:19:46 -0400 Subject: [PATCH 2/5] Add TextBox UndoLimit --- src/Avalonia.Controls/TextBox.cs | 20 +++++++++++++++++++ src/Avalonia.Controls/Utils/UndoRedoHelper.cs | 11 ++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index e38531e540..ffe0d39cab 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -138,6 +138,13 @@ namespace Avalonia.Controls nameof(IsUndoEnabled), defaultValue: true); + public static readonly DirectProperty UndoLimitProperty = + AvaloniaProperty.RegisterDirect( + nameof(UndoLimit), + o => o._undoRedoHelper.Limit, + (o, v) => o._undoRedoHelper.Limit = v, + unsetValue: -1); + struct UndoRedoState : IEquatable { public string Text { get; } @@ -472,6 +479,19 @@ namespace Avalonia.Controls } } + public int UndoLimit + { + get { return GetValue(UndoLimitProperty); } + set + { + SetValue(UndoLimitProperty, value); + // from docs at + // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: + // "Setting UndoLimit clears the undo queue." + _undoRedoHelper.Clear(); + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index 17cf681f15..7374f20a0c 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -22,6 +22,10 @@ namespace Avalonia.Controls.Utils private LinkedListNode _currentNode; + /// + /// Maximum number of states this helper can store for undo/redo. + /// If -1, no limit is imposed. + /// public int Limit { get; set; } = 10; public UndoRedoHelper(IUndoRedoHost host) @@ -54,7 +58,10 @@ namespace Avalonia.Controls.Utils public bool HasState => _currentNode != null; public void UpdateLastState(TState state) { - _states.Last.Value = state; + if (_states.Last != null) + { + _states.Last.Value = state; + } } public void UpdateLastState() @@ -86,7 +93,7 @@ namespace Avalonia.Controls.Utils DiscardRedo(); _states.AddLast(current); _currentNode = _states.Last; - if (_states.Count > Limit) + if (Limit != -1 && _states.Count > Limit) _states.RemoveFirst(); } } From 4fe29b0f9e9568a85f0ced91e5041e80ee55d2c5 Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Sun, 6 Jun 2021 21:27:47 -0400 Subject: [PATCH 3/5] UndoLimit now uses property properly --- src/Avalonia.Controls/TextBox.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index ffe0d39cab..06745bb3bc 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -141,8 +141,8 @@ namespace Avalonia.Controls public static readonly DirectProperty UndoLimitProperty = AvaloniaProperty.RegisterDirect( nameof(UndoLimit), - o => o._undoRedoHelper.Limit, - (o, v) => o._undoRedoHelper.Limit = v, + o => o.UndoLimit, + (o, v) => o.UndoLimit = v, unsetValue: -1); struct UndoRedoState : IEquatable @@ -481,10 +481,10 @@ namespace Avalonia.Controls public int UndoLimit { - get { return GetValue(UndoLimitProperty); } + get { return _undoRedoHelper.Limit; } set { - SetValue(UndoLimitProperty, value); + _undoRedoHelper.Limit = value; // from docs at // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: // "Setting UndoLimit clears the undo queue." From b5bb89348df5bdd201664c6e3ef71a5ed13400fe Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Sun, 6 Jun 2021 21:38:50 -0400 Subject: [PATCH 4/5] Handle IsUndoEnabled in property changed --- src/Avalonia.Controls/TextBox.cs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 06745bb3bc..351d648afc 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -464,19 +464,7 @@ namespace Avalonia.Controls public bool IsUndoEnabled { get { return GetValue(IsUndoEnabledProperty); } - set - { - SetValue(IsUndoEnabledProperty, value); - if (value == false) - { - // from docs at - // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: - // "Setting this property to false clears the undo stack. - // Therefore, if you disable undo and then re-enable it, undo commands still do not work - // because the undo stack was emptied when you disabled undo." - _undoRedoHelper.Clear(); - } - } + set { SetValue(IsUndoEnabledProperty, value); } } public int UndoLimit @@ -511,6 +499,15 @@ namespace Avalonia.Controls UpdatePseudoclasses(); UpdateCommandStates(); } + else if (change.Property == IsUndoEnabledProperty && change.NewValue.GetValueOrDefault() == false) + { + // from docs at + // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: + // "Setting this property to false clears the undo stack. + // Therefore, if you disable undo and then re-enable it, undo commands still do not work + // because the undo stack was emptied when you disabled undo." + _undoRedoHelper.Clear(); + } } private void UpdateCommandStates() From 8ed27724793cd7315c9fd2cd73f804a9429ab1ae Mon Sep 17 00:00:00 2001 From: Deadpikle Date: Mon, 7 Jun 2021 07:56:35 -0400 Subject: [PATCH 5/5] Fix not raising property change on UndoLimit --- src/Avalonia.Controls/TextBox.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 351d648afc..1bee15bccd 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -472,7 +472,15 @@ namespace Avalonia.Controls get { return _undoRedoHelper.Limit; } set { - _undoRedoHelper.Limit = value; + if (_undoRedoHelper.Limit != value) + { + // can't use SetAndRaise due to using _undoRedoHelper.Limit + // (can't send a ref of a property to SetAndRaise), + // so use RaisePropertyChanged instead. + var oldValue = _undoRedoHelper.Limit; + _undoRedoHelper.Limit = value; + RaisePropertyChanged(UndoLimitProperty, oldValue, value); + } // from docs at // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: // "Setting UndoLimit clears the undo queue."