diff --git a/BallanceTASEditor.csproj b/BallanceTASEditor.csproj index 7bb7a53..1e57390 100644 --- a/BallanceTASEditor.csproj +++ b/BallanceTASEditor.csproj @@ -58,6 +58,7 @@ Designer + AddItem.xaml diff --git a/Core/FileOperation.cs b/Core/FileOperation.cs new file mode 100644 index 0000000..eda25f8 --- /dev/null +++ b/Core/FileOperation.cs @@ -0,0 +1,403 @@ +using BallanceTASEditor.Core.TASStruct; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BallanceTASEditor.Core.FileOperation { + public abstract class RevocableOperation { + public RevocableOperation() { + hasBeenDone = false; + } + + private bool hasBeenDone; + public virtual void Do(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + if (hasBeenDone) throw new Exception("Try to call operation.do when the operation has been done."); + hasBeenDone = true; + } + + public virtual void Undo(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + if (!hasBeenDone) throw new Exception("Try to call operation.undo when the operation has not been done."); + hasBeenDone = false; + } + } + + + public class SetOperation : RevocableOperation { + private SelectionRange field; + private SelectionRange absoluteRange; + private bool? isSet; + + private uint internalOffset; + + public SetOperation(SelectionRange _field, SelectionRange _absoluteRange, bool? _isSet) : base() { + field = _field; + absoluteRange = _absoluteRange; + isSet = _isSet; + + // calc offset first for following operation + internalOffset = 0; + for (int i = (int)field.start; i <= (int)field.end; i++) { + internalOffset |= ConstValue.Mapping[(FrameDataField)i]; + } + } + + public override void Do(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + base.Do(ref mMem, ref mPointer, ref mPointerIndex); + if (mPointer == null) return; + + foreach (var item in mMem.IterateWithSelectionRange(absoluteRange, mPointer, mPointerIndex)) { + if (isSet == null) item.Value.ReverseKeyStates(internalOffset); + else if (isSet == true) item.Value.SetKeyStates(internalOffset); + else if (isSet == false) item.Value.UnsetKeyStates(internalOffset); + } + } + + public override void Undo(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + base.Undo(ref mMem, ref mPointer, ref mPointerIndex); + if (mPointer == null) return; + + foreach (var item in mMem.IterateWithSelectionRange(absoluteRange, mPointer, mPointerIndex)) { + // just like do operation, but switch set and unset operation + if (isSet == null) item.Value.ReverseKeyStates(internalOffset); + else if (isSet == true) item.Value.UnsetKeyStates(internalOffset); + else if (isSet == false) item.Value.SetKeyStates(internalOffset); + } + } + } + + public class RemoveOperation : RevocableOperation { + private SelectionRange absoluteRange; + + private LinkedList removedItems; + private LinkedListNode oldPointer; + private long oldPointerIndex; + private LinkedListNode removeStartNode; + + public RemoveOperation(SelectionRange _absoluteRange) : base() { + absoluteRange = _absoluteRange; + + removedItems = new LinkedList(); + } + + public override void Do(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + base.Do(ref mMem, ref mPointer, ref mPointerIndex); + if (mPointer == null) return; + + // init backups list and backups 2 data + // and backups remove start node(ps: if it is null, mean removed from head) + removedItems.Clear(); + oldPointer = mPointer; + oldPointerIndex = mPointerIndex; + removeStartNode = absoluteRange.start == 1 ? null : mMem.FastGetNode(mPointer, mPointerIndex, absoluteRange.start - 1); + + // find proper pointer after remove first. but we do not apply it in there. + // if state is true, it mean the deleted content is placed before pointer previously. we should consider pointer data and we should correct them. + LinkedListNode newPointer; + long newPointerIndex; + if (mPointerIndex >= absoluteRange.start) { + // if point within removed content, we need to shift it to the head of removed content, + // otherwise we only need to minus index with the length of removed content. + if (absoluteRange.Within(mPointerIndex)) { + // this contains 3 situation + // if full delete, mPointer is null and mPointerIndex is invalid(with wrong data: 0) + // if delete from head, mPointer and mPointerIndex all are valid. but it is the tail of removed content + // otherwise, just find the head of removed content and shift to it. + if (absoluteRange.start == 1 && absoluteRange.end == mMem.Count) { + // fully remove + newPointer = null; + newPointerIndex = 0; + } else if (absoluteRange.start == 1) { + // remove from head + newPointerIndex = absoluteRange.end + 1; + newPointer = mMem.FastGetNode(mPointer, mPointerIndex, newPointerIndex); + } else { + // simple remove + newPointerIndex = absoluteRange.start - 1; + newPointer = mMem.FastGetNode(mPointer, mPointerIndex, newPointerIndex); + } + } else { + newPointer = mPointer; + newPointerIndex = mPointerIndex - absoluteRange.GetCount() + 1; + } + } else { + // not affected situation + newPointer = mPointer; + newPointerIndex = mPointerIndex; + } + + // the real remove operation + foreach (var item in mMem.IterateWithSelectionRange(absoluteRange, mPointer, mPointerIndex)) { + removedItems.AddLast(item); // backups node first; + mMem.Remove(item); + } + + // apply gotten new pointer + mPointer = newPointer; + mPointerIndex = newPointerIndex; + + } + + public override void Undo(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + base.Undo(ref mMem, ref mPointer, ref mPointerIndex); + if (mPointer == null) return; + + // re-insert data + foreach (var item in removedItems.IterateFullReversed()) { + if (removeStartNode == null) { + // insert at first + mMem.AddFirst(item); + } else { + // insert after this node + mMem.AddAfter(removeStartNode, item); + } + } + + // reset pointer + mPointer = oldPointer; + mPointerIndex = oldPointerIndex; + } + } + + public class AddOperation : RevocableOperation { + private long absolutePos; + private long count; + private float deltaTime; + private bool isAddBefore; + + private LinkedListNode addStartNode; + private LinkedListNode oldPointer; + private long oldPointerIndex; + + public AddOperation(long _absolutePos, long _count, float _deltaTime, bool _isAddBefore) : base() { + absolutePos = _absolutePos; + count = _count; + deltaTime = _deltaTime; + isAddBefore = _isAddBefore; + } + + public override void Do(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + base.Do(ref mMem, ref mPointer, ref mPointerIndex); + if (count <= 0) return; + + // backups 2 data + oldPointer = mPointer; + oldPointerIndex = mPointerIndex; + + // real add operation + if (mPointer == null) { + // backups start pointer + addStartNode = null; + + // add into blank list, absolutePos and isAddBefore parameters are invalid + // specially process + for (long i = 0; i < count; i++) { + mMem.AddFirst(new FrameData(deltaTime, 0)); + } + mPointer = mMem.First; + mPointerIndex = 0; + } else { + // normal add + LinkedListNode node = mMem.FastGetNode(mPointer, mPointerIndex, absolutePos); + // backups start pointer + addStartNode = node; + if (isAddBefore) { + for (long i = 0; i < count; i++) { + mMem.AddBefore(node, new FrameData(deltaTime, 0)); + } + } else { + for (long i = 0; i < count; i++) { + mMem.AddAfter(node, new FrameData(deltaTime, 0)); + } + } + + // if the items are added before pointer, the index should add with the count of added items + // but pointer don't need to be shifted. + if (mPointerIndex > absolutePos) + mPointerIndex += count; + } + } + + public override void Undo(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + base.Undo(ref mMem, ref mPointer, ref mPointerIndex); + if (count <= 0) return; + + if (addStartNode == null) { + // original state is blank + // just clear mmem is ok + mMem.Clear(); + } else { + if (isAddBefore) { + for (long i = 0; i < count; i++) { + mMem.Remove(addStartNode.Previous); + } + } else { + for (long i = 0; i < count; i++) { + mMem.Remove(addStartNode.Next); + } + } + } + + // re-set pointer + mPointer = oldPointer; + mPointerIndex = oldPointerIndex; + } + } + + public class InsertOperation : RevocableOperation { + private long absolutePos; + private LinkedList data; + private bool isInsertBefore; + private bool isOverwritten; + + private LinkedListNode addStartNode; + private LinkedList oldItems; + private LinkedListNode oldPointer; + private long oldPointerIndex; + + public InsertOperation(long _absolutePos, LinkedList _data, bool _isInsertBefore, bool _isOverwritten) : base() { + absolutePos = _absolutePos; + data = _data; + isInsertBefore = _isInsertBefore; + isOverwritten = _isOverwritten; + + oldItems = new LinkedList(); + } + + public override void Do(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + base.Do(ref mMem, ref mPointer, ref mPointerIndex); + if (data.Count == 0) return; + + // init backups list and backups 2 data + oldItems.Clear(); + oldPointer = mPointer; + oldPointerIndex = mPointerIndex; + + // if the list is blank, overwritten is invalid, just normal add them. + if (mPointer == null) { + // backups start pointer + addStartNode = null; + + foreach (var item in data.IterateFull()) { + mMem.AddFirst(item.Value); + } + mPointer = mMem.First; + mPointerIndex = 0; + } else { + LinkedListNode node = mMem.FastGetNode(mPointer, mPointerIndex, absolutePos); + // if list is not a blank list, we should consider overwritten + // if in overwritten mode, we need to overwrite data from selected item. + // otherwise, not in overwritten mode, just normally add them just like add operation. + if (isOverwritten) { + // in overwritten mode, if follwoing item is not enough to fufill the count of overwritten data + // normally add them + // we use delete and add method to do this + // another protential dangerous spot is pointer can be overwritten, its dangerous, + // so we need create a backup pos to store its relative postion and in add stage set pointer to the new data. + + var backupsNode = isInsertBefore ? node.Next : node.Previous; + // backups start pointer + addStartNode = backupsNode; + + long gottenPointerPos = -1; + for(long i = 0; i < data.Count; i++) { + if (node == null) break; + if (node == mPointer) gottenPointerPos = i; + mMem.Remove(node); + + // backup and shift to next + if (isInsertBefore) { + oldItems.AddFirst(node); + node = node.Previous; + } else { + oldItems.AddLast(node); + node = node.Next; + } + } + + node = backupsNode; + if (node == null) { + foreach (var item in data.IterateFullReversed()) { + // add from head + mMem.AddFirst(item); + } + } else { + long counter = 0; + if (isInsertBefore) { + foreach (var item in data.IterateFull()) { + mMem.AddBefore(node, item.Value); + if (counter == gottenPointerPos) + mPointer = node.Previous; + counter++; + } + } else { + foreach (var item in data.IterateFullReversed()) { + mMem.AddAfter(node, item.Value); + if (counter == gottenPointerPos) + mPointer = node.Next; + counter++; + } + } + } + + } else { + // backups start pointer + addStartNode = node; + + if (isInsertBefore) { + foreach (var item in data.IterateFull()) { + mMem.AddBefore(node, item.Value); + } + } else { + foreach (var item in data.IterateFullReversed()) { + mMem.AddAfter(node, item.Value); + } + } + if (mPointerIndex > absolutePos) + mPointerIndex += data.Count; + } + + } + } + + public override void Undo(ref LinkedList mMem, ref LinkedListNode mPointer, ref long mPointerIndex) { + base.Undo(ref mMem, ref mPointer, ref mPointerIndex); + if (data.Count == 0) return; + + if (addStartNode == null) { + // original state is blank + // just clear mmem is ok + mMem.Clear(); + } else { + // in overwrite or not in overwrite mode, we all need to remove inserted data first + if (isInsertBefore) { + for (long i = 0; i < data.Count; i++) { + mMem.Remove(addStartNode.Previous); + } + } else { + for (long i = 0; i < data.Count; i++) { + mMem.Remove(addStartNode.Next); + } + } + + // if we use overwrite mode, we need re-add lost data + if (isOverwritten) { + if (isInsertBefore) { + foreach (var item in data.IterateFull()) { + mMem.AddBefore(addStartNode, item); + } + } else { + foreach (var item in data.IterateFullReversed()) { + mMem.AddAfter(addStartNode, item); + } + } + } + } + + // re-set pointer + mPointer = oldPointer; + mPointerIndex = oldPointerIndex; + } + } + +}