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;
+ }
+ }
+
+}