JD2022-TU1/main/tools/framework/JD.Timeline/View/DataBinding/TapeViewReplicator.cs

275 lines
No EOL
8.1 KiB
C#

using JD.Core.Collections;
using JD.Timeline.View.ClipView;
using JD.Timeline.View.ClipView.Widgets;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using JD.Application.Utils;
using JD.Collections.Extensions;
using JD.Kernel.Classes;
using JD.Sequencers.Times;
using TeaBox;
using JD.Serialization;
namespace JD.Timeline.View.DataBinding
{
/// <summary>
/// Responsible of updating the view when the tape content changes.
/// </summary>
public class TapeViewReplicator : IDisposable
{
#region Variables
private readonly Tape _tape;
private readonly TrackView _trackView;
private readonly Dictionary<Clip, ClipWidget> _clipToWidgetDictionary = new Dictionary<Clip, ClipWidget>();
private readonly Dictionary<TapeTrack, TapeTrackWidget> _trackToWidgetDictionary = new Dictionary<TapeTrack, TapeTrackWidget>();
#endregion
#region Constructor
public TapeViewReplicator(Tape tape, TrackView trackView)
{
_tape = tape;
_trackView = trackView;
_tape.UpdateOperation().EndScope += TapeViewReplicator_EndScope;
_tape.Clips.CollectionChanged += clips_CollectionChanged;
_tape.Clips.ItemChanged += clips_ItemChanged;
_tape.Tracks.CollectionChanged += tracks_CollectionChanged;
_tape.EditionContextChanged += tape_EditionContextChanged;
Refresh();
}
public void Dispose()
{
_tape.UpdateOperation().EndScope -= TapeViewReplicator_EndScope;
_tape.Clips.CollectionChanged -= clips_CollectionChanged;
_tape.Clips.ItemChanged -= clips_ItemChanged;
_tape.Tracks.CollectionChanged -= tracks_CollectionChanged;
_tape.EditionContextChanged -= tape_EditionContextChanged;
}
#endregion
#region Properties
public ClipWidget this[Clip clip]
{
get
{
ClipWidget result;
_clipToWidgetDictionary.TryGetValue(clip, out result);
return result;
}
}
public TapeTrackWidget this[TapeTrack track]
{
get
{
if (track != null)
{
TapeTrackWidget result;
_trackToWidgetDictionary.TryGetValue(track, out result);
return result;
}
return null;
}
}
#endregion
#region Methods
/// <summary>
/// A full refresh of the view.
/// </summary>
public void Refresh()
{
using (KeepSelection())
{
_trackToWidgetDictionary.Keys.ToArray().ForEach(UnregisterTrack);
_trackToWidgetDictionary.Clear();
_clipToWidgetDictionary.Clear();
_tape.Tracks.ForEach(RegisterTrack);
_tape.Clips.ForEach(Register);
}
}
private IDisposable KeepSelection()
{
var selection = _clipToWidgetDictionary.Values.Where(widget => widget.Selected).Select(widget => widget.Model.Id).ToSet();
return new DisposableHelper(() =>
{
var newSelection = _clipToWidgetDictionary.Values.Where(widget => selection.Contains(widget.Model.Id));
_trackView.Selection.Select(newSelection);
});
}
private void Register(Clip clip)
{
var view = clip.CreateClipWidget();
_clipToWidgetDictionary.Add(clip, view);
view.Update();
var track = GetTrack(clip);
track?.Children.Add(view);
}
private void Unregister(Clip clip)
{
var view = _clipToWidgetDictionary[clip];
_clipToWidgetDictionary.Remove(clip);
view.Dispose();
var track = GetTrack(clip);
track?.Children.Remove(view);
}
private void RegisterTrack(TapeTrack track)
{
var view = track.CreateTapeTrackWidget();
_trackToWidgetDictionary.Add(track, view);
_trackView.AddTrack(view);
_trackView.Invalidate();
}
private void UnregisterTrack(TapeTrack track)
{
var view = _trackToWidgetDictionary[track] ;
_trackView.RemoveTrack(view);
_trackToWidgetDictionary.Remove(track);
view.Dispose();
_trackView.Invalidate();
}
private TapeTrackWidget GetTrack(Clip clip)
{
var track = _tape.Tracks.FirstOrDefault(x => x.Id == clip.TrackId);
return track == null ? null : _trackToWidgetDictionary[track];
}
private void UpdateClip(Clip clip)
{
if (!_clipToWidgetDictionary.ContainsKey(clip))
return;
var trackItem = _clipToWidgetDictionary[clip];
if (trackItem == null)
{
return;
}
var expectedTrack = GetTrack(clip);
if (expectedTrack == null)
{
return;
}
if (trackItem.Track == null)
{
Console.WriteLine(trackItem + " On a null track");
return;
}
if (trackItem.Track != expectedTrack)
{
// Removing the item from its container removes the selection.
// here we keep the selection since we are changing track.
bool wasSelected = trackItem.Selected;
trackItem.Invalidate();
trackItem.Track.Children.Remove(trackItem);
expectedTrack.Children.Add(trackItem);
if (wasSelected)
{
trackItem.Track.TrackView.Selection.Add(trackItem);
}
}
trackItem.Update();
}
#endregion
#region Handlers
void tape_EditionContextChanged(object sender, EventArgs e)
{
Refresh();
}
private void clips_ItemChanged(object sender, ItemChangedEventArgs<Clip> args)
{
if (_tape.UpdateOperation().InScope)
{
return;
}
try
{
UpdateClip(args.Item);
}
catch (Exception)
{
MessageService.MessageService.ShowError("Error while trynig to update clip " + args.Item);
}
}
/// <summary>
/// Each time the source collection is modified, the corresponding items are added and removed.
/// </summary>
private void clips_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_tape.UpdateOperation().InScope)
{
return;
}
if (e.Action == NotifyCollectionChangedAction.Reset)
{
Refresh();
}
else
{
e.OldItems<Clip>().ForEach(Unregister);
e.NewItems<Clip>().ForEach(Register);
}
}
private void tracks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_tape.UpdateOperation().InScope)
{
return;
}
//Reorder
if (e.Action == NotifyCollectionChangedAction.Reset || e.Action == NotifyCollectionChangedAction.Move || e.Action == NotifyCollectionChangedAction.Add)
{
Refresh();
}
else
{
foreach (TapeTrack track in e.OldItems<TapeTrack>())
{
UnregisterTrack(track);
}
foreach (TapeTrack track in e.NewItems<TapeTrack>())
{
RegisterTrack(track);
}
}
}
void TapeViewReplicator_EndScope(object sender, EventArgs e)
{
Refresh();
}
#endregion
}
}