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 GetDelegatedActionTarget() { yield return _undoHistory; } public LoggingContext GetLoggingContext() { LoggingContext context; _taskCollector.OperationScope(out context); return context; } public void Dispose() { _undoHistory?.Dispose(); _undoer?.Dispose(); _taskCollector?.Dispose(); } } }