using System.Linq;
using System.Windows.Forms;
using JD.Timeline.Model;
using JD.Timeline.Model.Utility;
using JD.Timeline.Plugin;
using JD.Timeline.View.ClipView.Widgets;
using TeaBox;
using TeaBox.Actions;
using TeaBox.Gui;
using TeaBox.ObjectModel;
using TeaBox.ObjectModel.Pads;
using TeaBox.ObjectModel.Transactions;
using JD.Core;
using System;
using System.Collections.Generic;
using System.IO;
using JD.Application.Model.UndoRedo;
using JD.Application.Utils;
using JD.Application.View.PropertyGrid;
using JD.Kernel.StaticHelpers;
using JD.Kernel.Structs;
using JD.LogService;
using JD.MessageService;
using JD.Sequencers.Windowlesses;
using JD.Serialization;
using Path = System.IO.Path;
using PropertyChangedEventArgs = System.ComponentModel.PropertyChangedEventArgs;
namespace JD.Timeline.Document
{
///
/// Track document maps to a single wave with its annotations. A view can be open to allow the edition of the document.
///
public class TapeDocument : TeaBox.ObjectModel.Document, IGridEditableDocument, IUndoableContent, IDelegatedActionRouter, IDisposable
{
#region Variables
private Tape _tape;
private UndoHistory _undoHistory;
private TapeUndoer _undoer;
private readonly CommandStack _commandStack = new CommandStack();
private readonly OperationTaskCollector _taskCollector = new OperationTaskCollector();
///
/// Depending if the document was load from a file or from the plugin.
///
protected bool _fromFile;
///
/// Depending if the document was imported from a tpl or open from a tape directly.
///
protected bool _isImportedFromTpl;
#endregion
#region Constructor
public TapeDocument(DocumentCreationContext context, Tape tape, bool fromFile = false)
: base(context)
{
Tape = tape;
if (Tape != null && FileUtility.IsValidPath(Tape.ToString()))
{
FileName = DataRoot.ToAbsolutePath(Tape.ToString());
}
UpdateTitle();
IsDirty = false;
_fromFile = fromFile;
}
protected override void Dispose(bool disposing)
{
Tape = null;
base.Dispose(disposing);
}
#endregion
#region Properties
public Tape Tape
{
get { return _tape; }
protected set
{
if (_tape != null)
{
_tape.PropertyChanged -= Tape_PropertyChanged;
_undoHistory.Dispose();
}
_tape = value;
if (_tape != null)
{
_tape.PropertyChanged += Tape_PropertyChanged;
_undoer = new TapeUndoer(_tape, _commandStack);
_undoHistory = new UndoHistory(FileName, _undoer);
}
}
}
public OperationTaskCollector TaskCollector
{
get
{
return _taskCollector;
}
}
public bool IsLoadedFromFile
{
get
{
return _fromFile;
}
}
public bool IsImportedFromTpl
{
get
{
return _isImportedFromTpl;
}
}
public delegate void ValidationFunc(Tape tape, ILogger logger);
public event ValidationFunc Validation;
#endregion
#region Methods
private void UpdateTitle()
{
TitleName = GetTitleNameImpl();
}
protected override string GetTitleNameImpl()
{
string name = _tape == null ? "" : _tape.ToString().NonEmpty("No name");
if (FileUtility.IsValidPath(name))
{
name = Path.GetFileNameWithoutExtension(name);
}
return name;
}
public override bool DirectSave()
{
if (IsDirty && !_fromFile)
{
if (!ValidateTape())
return false;
TimelinePlugin.Instance?.SendSaveTape(_tape.Id);
IsDirty = false;
return true;
}
return false;
}
protected override void LoadImpl(string filename, DocumentLoadingContext context, bool isReloading)
{
base.LoadImpl(filename, context, isReloading);
_fromFile = true;
string extension = Path.GetExtension(filename) ?? "";
_isImportedFromTpl = extension.Equals(FileExtensions.Template, StringComparison.OrdinalIgnoreCase);
Tape = Tape.CreateTape(filename);
Tape.IsOffline = true;
Tape.UpdateTapeReferences();
}
protected override bool SaveImpl(string filePath, Flags context)
{
base.SaveImpl(filePath, context);
if (!ValidateTape())
return false;
if (_isImportedFromTpl && File.Exists(filePath))
{
_isImportedFromTpl = false;
var dialog = new MessageDialog
{
MainIcon = MessageDialogIcon.Warning,
MainMessage = $"File '{Path.GetFileName(filePath)}' already exists",
Content = "Tpl files need to be converted in tape files in order to open them in DanceEditor."
};
dialog.AddButton("Overwrite existing file");
var rename = dialog.AddButton("Save to different location");
var cancel = dialog.AddCommonButton(MessageDialogCommonButton.Cancel);
var result = dialog.ShowDialog();
if (result == rename)
{
var dlgSaveFileDialog = new SaveFileDialog { FileName = filePath, Filter = "Dance Tape files (*.dtape)|*.dtape" };
if (dlgSaveFileDialog.ShowDialog() == DialogResult.OK)
{
filePath = dlgSaveFileDialog.FileName;
}
else
{
return false;
}
}
if (result == cancel)
{
return false;
}
}
if (_fromFile)
{
Tape.Save(filePath);
}
return true;
}
private bool ValidateTape()
{
LoggingContext loggingContext;
using (_taskCollector.OperationScope(out loggingContext))
{
Validation?.Invoke(Tape, loggingContext);
Tape.SanitizeClipIds(loggingContext);
}
if (loggingContext.Errors.Any() || loggingContext.Warnings.Any())
{
TeaBox.ObjectModel.Application.Main.Dispatcher.DispatchAsync(() => TeaBox.ObjectModel.Application.Main.SelectPad(typeof(ErrorListPad), false));
}
if (loggingContext.Errors.Any())
{
const string error = "Current tape contains invalid data and cannot be saved in this state.\n\n" +
"Consult the error list for more details";
MessageService.MessageService.ShowError(error);
return false;
}
if (loggingContext.Warnings.Any())
{
var dialog = new MessageDialog
{
MainIcon = MessageDialogIcon.Warning,
MainMessage = "Current tape contains suspicious data (warnings). We suggest to fix the data prior of saving."
};
var save = dialog.AddButton("Save anyway");
dialog.AddCommonButton(MessageDialogCommonButton.Cancel);
var result = dialog.ShowDialog();
return (result == save);
}
return true;
}
#endregion
#region Handlers
private void Tape_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
UpdateTitle();
if (!_tape.IsComputedProperty(e.PropertyName))
{
OnChanged(e);
}
}
#endregion
#region IGridEditableDocument
object IGridEditableDocument.MainObject
{
get
{
if (_tape?.EditionContext != null)
{
return _tape.EditionContext;
}
return _tape;
}
}
object IGridEditableDocument.ToPropertyGrid(WindowlessControl control)
{
return (control as ClipWidget)?.Model;
}
#endregion
#region Undo/Redo
int IUndoableContent.UndoCount
{
get { return _commandStack.UndoCount; }
}
int IUndoableContent.RedoCount
{
get { return _commandStack.RedoCount; }
}
void IUndoableContent.Undo(int count)
{
_commandStack.Undo(count);
}
void IUndoableContent.Redo(int count)
{
_commandStack.Redo(count);
}
#endregion
public IEnumerable