using System.Diagnostics; using System.Linq; using System.Reactive; using System.Reactive.Linq; using System.Windows.Input; using DynamicData; using Intromat.Interfaces; using ReactiveUI; namespace Intromat.ViewModels { public class UndoRedoViewModel : ReactiveObject { private readonly ObservableAsPropertyHelper _currentRedoItem; private readonly ObservableAsPropertyHelper _currentUndoItem; private UndoItemGroup? _currentGroup; public UndoRedoViewModel() { GroupStack.Connect().ActOnEveryObject(g => CurrentGroup = g, _ => CurrentGroup = GroupStack.Count > 0 ? GroupStack.Items.ElementAt(GroupStack.Count - 1) : null); this.WhenAnyObservable(vm => vm.UndoStack.CountChanged) .Select(_ => UndoStack.Count > 0 ? UndoStack.Items.ElementAt(UndoStack.Count - 1) : null) .ToProperty(this, vm => vm.CurrentUndoItem, out _currentUndoItem); this.WhenAnyObservable(vm => vm.RedoStack.CountChanged) .Select(_ => RedoStack.Count > 0 ? RedoStack.Items.ElementAt(RedoStack.Count - 1) : null) .ToProperty(this, vm => vm.CurrentRedoItem, out _currentRedoItem); UndoCommand = ReactiveCommand.Create(Undo, this.WhenAnyValue(vm => vm.CurrentUndoItem).Select(i => i != null)); RedoCommand = ReactiveCommand.Create(Redo, this.WhenAnyValue(vm => vm.CurrentRedoItem).Select(i => i != null)); } public ReactiveCommand RedoCommand { get; } public ReactiveCommand UndoCommand { get; } public ISourceList GroupStack { get; } = new SourceList(); public UndoItemGroup? CurrentGroup { get => _currentGroup; set => this.RaiseAndSetIfChanged(ref _currentGroup, value); } public ISourceList UndoStack { get; } = new SourceList(); public IUndoItem? CurrentUndoItem => _currentUndoItem.Value; public ISourceList RedoStack { get; } = new SourceList(); public IUndoItem? CurrentRedoItem => _currentRedoItem.Value; public bool Recording { get; set; } public void Record(IUndoItem item) { if (!Recording) return; if (CurrentGroup != null) { CurrentGroup.Push(item); } else { UndoStack.Add(item); RedoStack.Clear(); CommandManager.InvalidateRequerySuggested(); } } public void PushGroup(IFile file) { GroupStack.Add(new UndoItemGroup(file)); } public void PopGroup() { var group = CurrentGroup; Debug.Assert(group != null); GroupStack.RemoveAt(GroupStack.Count - 1); if (group.Items.Count > 0) Record(group); } public void Execute(IUndoItem item) { item.Redo(); item.File.IsDirty = true; Record(item); } public void Purge(IFile file) { UndoStack.Edit(list => { for (var i = list.Count - 1; i >= 0; --i) if (list[i].File != file) list.RemoveAt(i); }); RedoStack.Edit(list => { for (var i = list.Count - 1; i >= 0; --i) if (list[i].File != file) list.RemoveAt(i); }); CommandManager.InvalidateRequerySuggested(); } public void Undo() { var item = CurrentUndoItem; Debug.Assert(item != null); UndoStack.RemoveAt(UndoStack.Count - 1); var wasRecording = Recording; if (wasRecording) Recording = false; item.Undo(); item.File.IsDirty = true; if (wasRecording) Recording = true; RedoStack.Add(item); CommandManager.InvalidateRequerySuggested(); } public void Redo() { var item = CurrentRedoItem; Debug.Assert(item != null); RedoStack.RemoveAt(RedoStack.Count - 1); var wasRecording = Recording; if (wasRecording) Recording = false; item.Redo(); item.File.IsDirty = true; if (wasRecording) Recording = true; UndoStack.Add(item); CommandManager.InvalidateRequerySuggested(); } } }