using JD.Application.Properties; using JD.Application.View.Actions; using JD.Application.View.Misc; using JD.Application.View.Timeline; using JD.Application.View.TrackItems; using JD.Application.View.Tracks; using JD.Application.View.WindowLess; using JD.Audio.Annotations; using JD.Audio.Sound; using JD.Audio.SoundPlayer; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using JD.Collections; using JD.Collections.Extensions; using JD.Collections.Interfaces; using JD.Interfaces.Player; using JD.Kernel.StaticHelpers; using JD.Kernel.Structs; using JD.Sequencers.Controls; using JD.Sequencers.Times; using JD.Sequencers.Tools; using JD.Sequencers.Windowlesses; using TeaBox; using TeaBox.Core.Source.Gui; namespace JD.Application.View { using RangeF = Range; /// /// The main control that holds the tracks. /// public partial class WaveControl : SequencerControlBase, IPlayerProvider { #region Variables private readonly BeatsTimeline _timeline; private IPlayer _player; private Cursor _toRestore; private Wave _wave; private readonly AutoLoopBehavior _autoLoopBehavior; private bool _showScrollbars = true; private readonly List _waveTracks = new List(); private readonly SnapPainter _snapPainter = new SnapPainter(); private readonly SongBackground _background; private readonly CommentEditor _commentEditor; #endregion #region Constructor public WaveControl() { InitializeComponent(); _timeline = new BeatsTimeline(this); _timeline.CurrentTimeChanged += _timeline_CurrentTimeChanged; _autoLoopBehavior = new AutoLoopBehavior(TrackView); _background = new SongBackground(_timeline); _commentEditor = new CommentEditor(this) { Enabled = false }; TrackView.Controls.Insert(0, _background); TooltipIntervalDelay = 50; RootContainer.Controls.Add(_timeline); var resizeTool = new ResizeTool(TrackView, Timeline); TrackView.ToolManager.MouseDownTools.Insert(0, resizeTool); TrackView.ToolManager.MouseMoveTools.Insert(0, resizeTool); InitializeContextMenu(); Init(); } void _timeline_CurrentTimeChanged(object sender, EventArgs e) { TimeSpan timeInSeconds = TimeSpan.FromSeconds(_timeline.ConvertTo(_timeline.TimeCursor.Time, TimeUnit.Seconds)); var frame = Math.Round(_timeline.ConvertTo(_timeline.TimeCursor.Time, TimeUnit.Frame)); TimeCursorPositionTextProvider.Instance.Text = timeInSeconds.ToString(@"mm\:ss\.f") + $" | Frame: {frame}"; } protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); // Force to update the range once the window is assigned its final size. global::TeaBox.ObjectModel.Application.Main.Dispatcher.DispatchAsync(() => { UpdateRange(); //Timeline.ZoomExtents(); Timeline.ResizingMode = global::JD.Sequencers.Times.Timeline.ResizeMode.PreserveScale; }, forceAsync: true); } /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); Wave = null; Player = null; _autoLoopBehavior.Dispose(); } base.Dispose(disposing); } #endregion #region Properties protected SongBackground SongBackground { get { return _background; } } public IPlayer Player { get { return _player; } set { if (_player != null) { _player.TimeChanged -= Player_TimeChanged; } _player = value; _timeline.Player = value; if (_player != null) { _player.TimeChanged += Player_TimeChanged; _autoLoopBehavior.Loop = _player.Components.GetOrCreate(); } UpdatePlayerWidgets(); } } private void UpdatePlayerWidgets() { foreach (var widget in TrackView.Controls.Concat(Timeline.Controls).Where(w => w is LoopWidget || w is CheckpointWidget).OfType().ToArray()) { widget.Parent.Controls.Remove(widget); widget.Dispose(); } if (_player != null) { var loop = _player.Components.GetOrCreate(); TrackView.Controls.Insert(1, new LoopWidget(loop, _timeline)); Timeline.Controls.Add(new LoopWidget(loop, _timeline)); var checkpoint = _player.Components.GetOrCreate(); TrackView.Controls.Insert(1, new CheckpointWidget(checkpoint, _timeline)); } } void Player_TimeChanged(object sender, EventArgs e) { if (!Player.Playing) return; if (EditionOptions.ScrollingMode == ScrollingMode.Page) { var changePageRange = new Range(Timeline.VisibleTimeRange.Min + 0.9f * Timeline.VisibleTimeRange.Width(), Timeline.VisibleTimeRange.Max); if (!Timeline.VisibleTimeRange.Contains(Timeline.TimeCursor.Time)) { Timeline.TimeOffset = Timeline.TimeCursor.Time - 0.1f * Timeline.VisibleTimeRange.Width(); } else if (changePageRange.Contains(Timeline.TimeCursor.Time)) { Timeline.TimeOffset = changePageRange.Min - 0.1f * Timeline.VisibleTimeRange.Width(); } } else if (EditionOptions.ScrollingMode == ScrollingMode.Continuous) { Timeline.TimeOffset = Timeline.TimeCursor.Time - Timeline.VisibleTimeRange.Width() / 2; } } public BeatsTimeline Timeline { get { return _timeline; } } /// /// When true, the user is allowed to scroll in the sound. /// public bool ShowScrollBars { get { return _showScrollbars; } set { _showScrollbars = value; _hScrollBar.Visible = value; _vScrollBar.Visible = value; } } public Wave Wave { get { return _wave; } set { Throw.InvalidArgumentIf(value != null && value.Annotations.Markers.Count < 2, "Invalid wave", "wave"); if (_wave == value) { return; } if (_wave != null) { _wave.Annotations.Changed -= Annotations_Changed; _wave.Annotations.PropertyChanged -= Annotations_PropertyChanged; } _wave = value; _background.Wave = value; if (_wave != null) { _wave.Annotations.Changed += Annotations_Changed; _wave.Annotations.PropertyChanged += Annotations_PropertyChanged; Timeline.Wave = value; } UpdateWaveTracks(); } } public Selection Selection { get { return TrackView.Selection; } } /// /// The utillity textbox allowing to edit current comment. /// public CommentEditor CommentEditor { get { return _commentEditor; } } protected bool AllowEditComments { get { return _commentEditor.Enabled; } } #endregion #region General Methods protected virtual void UpdateRange() { if (Wave != null) { var rangeInBeat = ComputeRange(); var rangeInTime = new RangeF(Timeline.TimeUnitFromBeat(rangeInBeat.Min), Timeline.TimeUnitFromBeat(rangeInBeat.Max)); if (Timeline.TimeRange != rangeInTime) { Timeline.TimeRange = rangeInTime; Timeline.ZoomExtents(); } } } protected virtual Range ComputeRange() { return new Range(Wave.Annotations.StartBeat, Wave.Annotations.EndBeat); } protected override global::JD.Sequencers.Times.Timeline CreateTimeline() { return _timeline; } #endregion #region Creation private bool _showWaveComments; /// /// When true, the WaveTrack will display the comments. /// public bool ShowWaveComments { get { return _showWaveComments; } set { if (value != _showWaveComments) { _showWaveComments = value; UpdateWaveTracks(); } } } protected void UpdateWaveTracks() { ClearWaveTracks(); if (_wave != null) { foreach (var track in CreateWaveTracks().Reverse()) { _waveTracks.Add(track); TrackView.InsertTrackAt(0, track); } UpdateRange(); } } private void ClearWaveTracks() { foreach (var track in _waveTracks) { track.Dispose(); TrackView.RemoveTrack(track); } _waveTracks.Clear(); } /// /// Creates the tracks that are used to display/edit the wave. /// protected virtual IEnumerable CreateWaveTracks() { var track = new WaveTrack(Wave, Wave.UnionChannel) { MinimumHeight = 92, MaximumHeight = 150 }; track.ShowSignatures(); if (ShowWaveComments) { track.ShowComments(); } else { track.ShowSongSections(); } track.Fixed = true; foreach (var control in track.Controls) { control.Selectable = false; } yield return track; } protected virtual void InitializeContextMenu() { ContextMenu = null; ContextMenuStrip = new TempoBlasterContextMenu(components, this); } #endregion #region ScrollBars protected override HScrollBar CreateHorizontalScrollbar() { return _hScrollBar; } protected override VScrollBar CreateVerticalScrollbar() { return _vScrollBar; } protected override void UpdateHorizontalScrollbar() { if (ShowScrollBars) { base.UpdateHorizontalScrollbar(); _hScrollBar.Visible = _hScrollBar.Minimum != _hScrollBar.Maximum; } } #endregion #region Cursor public static bool IsStretchKeyPress { get { return Control.ModifierKeys.HasFlag(Keys.Shift); } } private void UpdateMouseCursor(Point cursorLocation) { var currentContainer = RootContainer.FindControl(cursorLocation); var currentTool = currentContainer != null ? currentContainer.ToolManager.CurrentTool : null; if ((currentTool == null && CanResizeHoveredObject()) || currentTool is ResizeTool) { _toRestore = Cursors.Default; if (IsStretchKeyPress) { Cursor = Cursors.SizeWE; } else { Cursor = Cursors.VSplit; } } else if ((currentTool == null && CanMoveHoveredObject())) { _toRestore = Cursors.Default; Cursor = Cursors.Hand; } else if (currentTool is DraggingTool) { _toRestore = Cursors.Default; Cursor = JD.Application.TeaBox.Properties.ResourcesCache.GrabCursor; } else if (_toRestore != null) { Cursor = _toRestore; _toRestore = null; } } protected override Cursor PanCursor(bool west) { return JD.Application.TeaBox.Properties.ResourcesCache.GrabCursor; } protected virtual bool CanMoveHoveredObject() { if (MouseButtons != MouseButtons.None) { return false; } var control = RootContainer.FindSelectableControl(PointToClient(MousePosition)) as IMovable; if (control != null && control.IsMoveAllowed) { return true; } return false; } protected virtual bool CanResizeHoveredObject() { if (MouseButtons != MouseButtons.None) { return false; } var resizeTool = TrackView.ToolManager.MouseMoveTools.FirstOf(); return resizeTool != null && resizeTool.CanStartResize(PointToClient(MousePosition)); } #endregion #region Zoom protected override void HandleZoomOnMouseWheel(MouseEventArgs e) { ZoomOn(e.Delta, Timeline.PixelToTime(e.X)); } public void ZoomOn(float factor, float time) { float zoomFactor = factor > 0.0f ? -0.15f : 0.15f; RangeF currentRange = Timeline.VisibleTimeRange; currentRange = currentRange.Inflate(currentRange.Width() * zoomFactor); float currentRangeWidth = currentRange.Width(); if (currentRangeWidth > 0.1f) { float position = (time - Timeline.VisibleTimeRange.Min) / Timeline.VisibleTimeRange.Width(); float newPosition = (time - currentRange.Min) / currentRangeWidth; float offset = newPosition - position; currentRange = Offset(currentRange, currentRangeWidth * offset); Timeline.VisibleTimeRange = currentRange; } } private RangeF Offset(RangeF range, float value) { return new RangeF(range.Min + value, range.Max + value); } #endregion #region Painting /// /// Overriden. /// /// protected override void OnPaint(PaintEventArgs e) { foreach (var debugTrack in TrackView.Controls.OfType()) { debugTrack.UpdateBounds(TrackView); } base.OnPaint(e); _snapPainter.PaintSnaps(TrackView, e.Graphics); } #endregion #region Events public event EventHandler WaveChanged; /// /// Triggers the WaveChanged event. /// protected virtual void OnWaveChanged(EventArgs e) { WaveChanged?.Invoke(this, e); } #endregion #region Event Handlers protected override void OnKeyDown(KeyEventArgs e) { UpdateMouseCursor(PointToClient(Cursor.Position)); base.OnKeyDown(e); } protected override void OnKeyUp(KeyEventArgs e) { UpdateMouseCursor(PointToClient(Cursor.Position)); base.OnKeyUp(e); } protected override void OnMouseMove(object sender, MouseEventArgs e) { base.OnMouseMove(sender, e); UpdateMouseCursor(e.Location); _snapPainter.Invalidate(TrackView); } protected override void OnDragOver(DragEventArgs e) { base.OnDragOver(e); UpdateMouseCursor(new Point(e.X, e.Y)); _snapPainter.Invalidate(TrackView); } void Annotations_Changed(object sender, EventArgs e) { Invalidate(); } void Annotations_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName.IsIn(AnnotationCollection.PropertyNames.StartBeat, AnnotationCollection.PropertyNames.EndBeat)) { UpdateRange(); } } protected override void OnDoubleClick(EventArgs e) { var cursorPosition = PointToClient(Cursor.Position); var trackItem = TrackItemFromLocation(cursorPosition); if (trackItem != null) { OnDoubleClick(trackItem); trackItem.OnDoubleClick(); } else { var section = SectionFromLocation(cursorPosition); if (section != null) { Timeline.TimeCursor.Time = Timeline.TimeUnitFromBeat(section.Time); } } base.OnDoubleClick(e); } protected virtual void OnDoubleClick(LocalizedTrackItem trackItem) { Timeline.TimeCursor.Time = trackItem.Time; } protected override void OnMouseClick(MouseEventArgs e) { var cursorPosition = PointToClient(Cursor.Position); var trackItem = TrackItemFromLocation(cursorPosition); if (trackItem != null) { trackItem.OnMouseClick(e); } base.OnMouseClick(e); } protected LocalizedTrackItem TrackItemFromLocation(Point location) { return TrackView.ControlsRecursive() .OfType() .FirstOrDefault(item => item.Bounds.Contains(location)); } protected SongSection SectionFromLocation(Point location) { var currentTrack = TrackView.FindControl(location); if (currentTrack != null) { var timeUnit = _timeline.PixelToTime(location.X); BeatUnit time = _timeline.BeatFromTimeUnit(timeUnit); return _wave.UnitConverter.SongSectionFromTime(time); } return null; } #endregion #region Action Handlers public override bool PreProcessMessage(ref System.Windows.Forms.Message msg) { if (msg.Msg == global::TeaBox.Actions.ActionManager.WM_KEYDOWN) { long param = (long)msg.WParam; Keys keys = (Keys)param | Control.ModifierKeys; // Let the action manager handle the arrow keys. if (keys.IsIn(Keys.Left, Keys.Right, Keys.Up, Keys.Down, Keys.Tab | Keys.Shift)) { return false; } } return base.PreProcessMessage(ref msg); } [ActionHandler(EditActions.Delete)] private void HandleDelete() { var selection = Selection.OfType().ToArray(); // In a copy so we can safely modify the list during that time. foreach (var item in selection) { item.Remove(); } } [ActionHandler(EditActions.SelectAll)] private void HandleSelectAll() { var selection = Selection; using (selection.BeginScopedUpdate()) { selection.Clear(); CollectionExtensions.AddRange(selection, TrackView.ControlsRecursive().Where(control => control.Selectable)); } } [ActionHandler("EditorActions.ToggleSnap")] public void ToggleSnap() { EditionOptions.CurrentSnappingUnit = EditionOptions.CurrentSnappingUnit == SnappingUnit.None ? EditionOptions.PreferredSnappingUnit : SnappingUnit.None; } [ActionHandler("EditActions.MoveUp")] protected virtual void HandleMoveUp() { if (AllowEditComments) { MoveComments(-1); } } [ActionHandler("EditActions.MoveDown")] protected virtual void HandleMoveDown() { if (AllowEditComments) { MoveComments(+1); } } private void MoveComments(int trackOffset) { var commentTypes = CommentTypeExtensions.CommentTypes.ToList(); var allowedRange = new Range(0, commentTypes.Count - 1); foreach (var comment in Selection.OfType().Select(widget => widget.Model).ToArray()) { int currentIndex = commentTypes.IndexOf(comment.Type); comment.Type = (CommentType)allowedRange.Clamp(currentIndex + trackOffset); } } [ActionUpdateHandler("EditActions.MoveLeft")] protected virtual void UpdateMoveLeft(ActionInfo info) { info.Enabled = false; } [ActionHandler("EditActions.MoveLeft")] protected virtual void HandleMoveLeft() { } [ActionUpdateHandler("EditActions.MoveRight")] protected virtual void UpdateMoveRight(ActionInfo info) { info.Enabled = false; } [ActionHandler("EditActions.MoveRight")] protected virtual void HandleMoveRight() { } [ActionHandler("EditActions.SelectNext")] protected virtual void HandleSelectNext() { } [ActionHandler("EditActions.SelectPrevious")] protected virtual void HandleSelectPrevious() { } #endregion } }