428 lines
14 KiB
C#
428 lines
14 KiB
C#
using Divelements.SandGrid;
|
|
using JD.Timeline.Document;
|
|
using JD.Timeline.Model;
|
|
using JD.Timeline.View.ClipView;
|
|
using JD.Timeline.View.ScoreView;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
using JD.Collections.Extensions;
|
|
using JD.Collections.Interfaces;
|
|
using JD.Controls;
|
|
using JD.Kernel.Classes;
|
|
using JD.Kernel.StaticHelpers;
|
|
using JD.Kernel.Structs;
|
|
using JD.Sequencers.Controls;
|
|
using JD.Sequencers.Times;
|
|
using TeaBox;
|
|
using TeaBox.Actions;
|
|
using TeaBox.Gui;
|
|
using JD.Serialization;
|
|
|
|
namespace JD.Timeline.View.TrackTreeView
|
|
{
|
|
public class TrackTreeView : UIHierarchy
|
|
{
|
|
#region Variables
|
|
|
|
private readonly TD.SandBar.ToolBar _toolbar;
|
|
private readonly Tape _tape;
|
|
private readonly FixedTracksTreeView _fixedTracksTreeView;
|
|
private readonly TrackView _trackView;
|
|
private readonly RefreshTask _updateTracksTask;
|
|
private readonly Scope _isUpdatingSelection = new Scope();
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
public TrackTreeView(Tape tape, TrackView trackView)
|
|
{
|
|
Throw.IfNull(tape, "tape");
|
|
Throw.IfNull(trackView, "trackView");
|
|
|
|
_tape = tape;
|
|
_trackView = trackView;
|
|
_updateTracksTask = new RefreshTask(UpdateTracks);
|
|
|
|
MainColumn.AutoSize = ColumnAutoSizeMode.Spring;
|
|
|
|
Grid.ShowTreeLines = false;
|
|
Grid.ShowTreeButtons = true;
|
|
Grid.BackColor = StyleSheet.BackgroundColor;
|
|
Grid.HideVerticalScrollbar = true;
|
|
Grid.HideScrollbars = true;
|
|
Grid.RowHighlightType = RowHighlightType.Full;
|
|
Grid.Paint += Grid_Paint;
|
|
Grid.VerticalScrollOverflow = ScrollOverflowBehavior.AlwaysVisible;
|
|
AllowSorting = false;
|
|
|
|
_fixedTracksTreeView = new FixedTracksTreeView {Dock = DockStyle.Top};
|
|
Controls.Add(_fixedTracksTreeView);
|
|
|
|
_toolbar = TeaBox.ObjectModel.Application.Main.Actions.CreateToolBar("TrackTreeView Toolbar", "Application/TreeViewToolbar/TrackTreeView", this);
|
|
Controls.Add(_toolbar);
|
|
|
|
TeaBox.ObjectModel.Application.Main.Actions.CreateContextMenu("Application/TrackTreeView/ContextMenu", this).AssignOnControl(this);
|
|
|
|
_trackView.TrackSizeChanged += _trackView_TrackSizeChanged;
|
|
_trackView.ScrollValueChanged += _trackView_ScrollValueChanged;
|
|
_trackView.CollectionChanged += _updateTracksTask.ModelChanged;
|
|
_trackView.MouseDown += trackView_MouseDown;
|
|
UpdateTracks();
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_updateTracksTask.Dispose();
|
|
_trackView.TrackSizeChanged -= _trackView_TrackSizeChanged;
|
|
_trackView.ScrollValueChanged -= _trackView_ScrollValueChanged;
|
|
_trackView.CollectionChanged -= _updateTracksTask.ModelChanged;
|
|
_trackView.MouseDown -= trackView_MouseDown;
|
|
_fixedTracksTreeView.Items.OfType<TrackTreeItem>().ForEach(x => x.Dispose());
|
|
Items.OfType<TrackTreeItem>().ForEach(x => x.Dispose());
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public override bool UseVirtualRowTechnique => true;
|
|
|
|
private IEnumerable<Track> FixedTracks
|
|
{
|
|
get { return _trackView.GetTracks().Where(track => track.Fixed); }
|
|
}
|
|
|
|
public ClipTrackTreeItem this[TapeTrackWidget track]
|
|
{
|
|
get
|
|
{
|
|
if (track != null)
|
|
return (ClipTrackTreeItem)(Items.Where(item =>
|
|
((ClipTrackTreeItem)item).Track == track).First());
|
|
else
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
protected override void OnSelectedItemsChanged(EventArgs e)
|
|
{
|
|
if (!IsUpdating && !_isUpdatingSelection.InScope)
|
|
{
|
|
using (_trackView.Selection.BeginScopedUpdate())
|
|
{
|
|
var tracks = SelectedItems.OfType<TrackTreeItem>();
|
|
if (tracks.Any())
|
|
{
|
|
var trackSelected = tracks.First();
|
|
if (trackSelected.SelectItemsOnClick)
|
|
{
|
|
_trackView.Selection.Clear();
|
|
tracks.ForEach(track => _trackView.Selection.AddRange(track.Track.Children));
|
|
}
|
|
_trackView.ActiveTrack = tracks.First().Track;
|
|
}
|
|
}
|
|
}
|
|
base.OnSelectedItemsChanged(e);
|
|
}
|
|
|
|
private void UpdateTracks()
|
|
{
|
|
UpdateFixedPanel();
|
|
|
|
using (_isUpdatingSelection.Begin())
|
|
using (KeepTreeState())
|
|
using (BeginUpdate())
|
|
{
|
|
_fixedTracksTreeView.Items.OfType<TrackTreeItem>().ForEach(x => x.Dispose());
|
|
_fixedTracksTreeView.Items.Clear();
|
|
Items.OfType<TrackTreeItem>().ForEach(x => x.Dispose());
|
|
Items.Clear();
|
|
|
|
// Maps the row to its track so that we can build the track hierarchy
|
|
var newRowsByTrack = new Dictionary<object, TrackTreeItem>();
|
|
foreach (var track in _trackView.GetTracks())
|
|
{
|
|
var item = TrackTreeItem.FromTrack(track);
|
|
newRowsByTrack.Add(item.Key, item);
|
|
}
|
|
|
|
foreach (var treeViewItem in newRowsByTrack.Values)
|
|
{
|
|
var parentCollection = !treeViewItem.Track.Fixed ? Items : _fixedTracksTreeView.Items;
|
|
|
|
TrackTreeItem parentItem;
|
|
if (treeViewItem.ParentGroupKey != null && newRowsByTrack.TryGetValue(treeViewItem.ParentGroupKey, out parentItem))
|
|
{
|
|
parentCollection = parentItem.Items;
|
|
}
|
|
|
|
parentCollection.Add(treeViewItem);
|
|
}
|
|
}
|
|
|
|
// Refresh the height after the creation of the grid, because the height
|
|
// is not considered until the grid is fully built.
|
|
RefreshRowHeights();
|
|
|
|
UpdateScoreTreeItems();
|
|
}
|
|
|
|
private void UpdateScoreTreeItems()
|
|
{
|
|
Items.OfType<ScoreTrackTreeItem>().ForEach(scoreTrackTreeItem =>
|
|
{
|
|
var view = TeaBox.ObjectModel.Application.Main.Views.SelectedItem as DanceTapeDocumentView;
|
|
if (view == null) return;
|
|
|
|
ScoreTrack scoreTrack = scoreTrackTreeItem.TrackView.Track as ScoreTrack;
|
|
|
|
if (scoreTrack == null)
|
|
return;
|
|
|
|
scoreTrackTreeItem.UnregisterItem();
|
|
|
|
if(view.ScoreViewManager.CurrentScoreViewState != ScoreViewState.AverageTrackOnly /*|| (scoreTrack.Name == scoreTrack.ReferenceTrack.Name + " - Average Score")*/)
|
|
{
|
|
var referencedTapeTrackWidget = view.ClipView[scoreTrack.ReferenceTrack];
|
|
|
|
if (this[referencedTapeTrackWidget] != null)
|
|
this[referencedTapeTrackWidget].Items.Add(scoreTrackTreeItem);
|
|
}
|
|
});
|
|
}
|
|
|
|
private IDisposable KeepTreeState()
|
|
{
|
|
var memento = new Memento(this);
|
|
return new DisposableHelper(() => memento.Apply(this));
|
|
}
|
|
|
|
private void UpdateFixedPanel()
|
|
{
|
|
int fixedHeaderHeight = FixedTracks.Sum(track => track.Bounds.Height);
|
|
fixedHeaderHeight += _trackView.Bounds.Top - _toolbar.Height - 1;
|
|
if (_fixedTracksTreeView.Height != fixedHeaderHeight)
|
|
{
|
|
_fixedTracksTreeView.Height = fixedHeaderHeight;
|
|
_fixedTracksTreeView.FixedHeaderHeight = _trackView.Bounds.Top - _toolbar.Height - 1;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
void Grid_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
if (Grid.VScrollOffset != GetScrollValueInPixels())
|
|
{
|
|
RefreshScrollbars();
|
|
}
|
|
}
|
|
|
|
private int GetScrollValueInPixels()
|
|
{
|
|
return _trackView.GetTracks()
|
|
.Where(track => !track.Fixed)
|
|
.Take(_trackView.ScrollValue).Sum(track => track.Height);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synchronizes the scroll bar of the treeview with the scrollbar of the clip view.
|
|
/// </summary>
|
|
private void RefreshScrollbars()
|
|
{
|
|
RefreshRowHeights();
|
|
|
|
// Find the best scroll value for the clipview using the current treeview scroll value.
|
|
int y = 0;
|
|
int index = 0;
|
|
foreach (var item in AllVisibleItems)
|
|
{
|
|
if (new Range<int>(y, y + item.Height - 1).Contains(Grid.VScrollOffset))
|
|
{
|
|
_trackView.ScrollValue = index;
|
|
Grid.VScrollOffset = GetScrollValueInPixels();
|
|
break;
|
|
}
|
|
index++;
|
|
y += item.Height;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This forces a resynchronization between the grid and the tree view.
|
|
/// </summary>
|
|
private void RefreshRowHeights()
|
|
{
|
|
foreach (var item in AllVisibleItems.OfType<ClipTrackTreeItem>())
|
|
{
|
|
item.Height = item.TrackView.Height;
|
|
}
|
|
}
|
|
|
|
private void _trackView_ScrollValueChanged(object sender, EventArgs e)
|
|
{
|
|
Grid.VScrollOffset = _trackView.ScrollValueInPixel;
|
|
}
|
|
|
|
void _trackView_TrackSizeChanged(object sender, EventArgs e)
|
|
{
|
|
Grid.Invalidate();
|
|
UpdateFixedPanel();
|
|
}
|
|
|
|
void trackView_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
using (_isUpdatingSelection.Begin())
|
|
{
|
|
ClearSelection();
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static string ToString<T>(T value)
|
|
{
|
|
return value.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Action Handlers
|
|
|
|
[ActionUpdateHandler(EditActions.Delete)]
|
|
private void UpdateDelete(ActionInfo info)
|
|
{
|
|
info.Enabled = SelectedItems.OfType<ClipTrackTreeItem>().Any(item => item.TrackView.CanDelete);
|
|
}
|
|
|
|
[ActionHandler(EditActions.Delete)]
|
|
private void HandleDelete()
|
|
{
|
|
var selectedEventTracks = SelectedItems.OfType<ClipTrackTreeItem>().Where(item => item.TrackView.CanDelete).Select(item => item.TrackView.Track);
|
|
foreach (var track in selectedEventTracks)
|
|
{
|
|
_tape.RemoveTrackWithClips(track);
|
|
}
|
|
}
|
|
|
|
[ActionHandler(TimelineActions.AddTrack)]
|
|
[ActionHandler(TimelineActions.InsertTrack)]
|
|
private void HandleInsertTrack()
|
|
{
|
|
if (SelectedItems == null || SelectedItems.IsEmpty())
|
|
{
|
|
_tape.CreateTrack();
|
|
return;
|
|
}
|
|
|
|
int index = SelectedItems.Select(item => item.AbsoluteRowIndex).Min();
|
|
_tape.InsertTrack(index > 0 ? index : 0);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Nested
|
|
|
|
private class Memento : UIHierarchyMemento
|
|
{
|
|
public Memento(UIHierarchy hierarchy)
|
|
: base(hierarchy)
|
|
{
|
|
}
|
|
|
|
protected override string GetItemKey(UIHierarchyItem item)
|
|
{
|
|
var row = item as ClipTrackTreeItem;
|
|
return row == null ? item.Text : row.TrackView.Track.Id.ToString();
|
|
}
|
|
}
|
|
|
|
internal class TrackTreeDataObject
|
|
{
|
|
private readonly uint[] _trackIds;
|
|
|
|
public TrackTreeDataObject(IEnumerable<uint> trackIds)
|
|
{
|
|
_trackIds = trackIds.ToArray();
|
|
}
|
|
|
|
public uint[] TrackIds
|
|
{
|
|
get { return _trackIds; }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private class FixedTracksTreeView : UIHierarchy
|
|
{
|
|
private readonly Panel _fixedHeader;
|
|
|
|
public FixedTracksTreeView()
|
|
{
|
|
MainColumn.AutoSize = ColumnAutoSizeMode.Spring;
|
|
Grid.ShowTreeLines = false;
|
|
Grid.ShowTreeButtons = true;
|
|
Grid.BackColor = StyleSheet.BackgroundColor;
|
|
Grid.HideVerticalScrollbar = true;
|
|
Grid.HideScrollbars = true;
|
|
Grid.RowHighlightType = RowHighlightType.Full;
|
|
Grid.VerticalScrollOverflow = ScrollOverflowBehavior.AlwaysVisible;
|
|
AllowSorting = false;
|
|
|
|
_fixedHeader = new UserDrawnPanel();
|
|
_fixedHeader.Dock = DockStyle.Top;
|
|
_fixedHeader.Paint += _fixedHeader_Paint;
|
|
Controls.Add(_fixedHeader);
|
|
}
|
|
|
|
public override bool UseVirtualRowTechnique
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
void _fixedHeader_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
var rect = new Rectangle(Point.Empty, _fixedHeader.Size);
|
|
|
|
using (var brush = StyleSheet.GetTrackTreeBrush(rect, isAlternate: false, isSelected: false))
|
|
using (var textBrush = new SolidBrush(StyleSheet.TrackForeColor))
|
|
using (var stringFormat = new StringFormat { Trimming = StringTrimming.EllipsisPath, Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
|
|
{
|
|
e.Graphics.FillRectangle(brush, rect);
|
|
}
|
|
}
|
|
|
|
public int FixedHeaderHeight { get { return _fixedHeader.Height; } set { _fixedHeader.Height = value; } }
|
|
|
|
private class UserDrawnPanel : Panel
|
|
{
|
|
public UserDrawnPanel()
|
|
{
|
|
SetStyle(ControlStyles.ResizeRedraw, true);
|
|
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
|
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|