using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using JD.Application.Utils; using JD.Application.View.Misc; using JD.Application.View.Timeline; using JD.Audio.Annotations; using JD.Audio.SoundPlayer; using JD.Collections.Extensions; using JD.Maths.Utils; using JD.MessageService; using JD.Timeline.Model.Clips; using JD.Timeline.View.Actions.Refactor; using JD.Timeline.View.ClipView; using TeaBox; using TeaBox.Gui; using JD.Serialization; using JD.WinFormExtensions; using TeaBox.Core.Source.Gui; using TeaBox.Core.Source.Gui.Misc; using FrameConverter = JD.Core.FrameConverter; namespace JD.Timeline.View.Actions { /// /// Provides the different menu actions to create a new clip. /// public class AddNewClipActionHandler : TapeActionHandlerBase { protected virtual IEnumerable ClipTypes() { return Track != null ? Track.ClipTypes() : Enumerable.Empty(); } public AddNewClipActionHandler() { ClipLimit = 500; } protected ClipViewControl CurrentControl { get { var view = CurrentView; return view != null ? view.ClipView : null; } } protected TapeTrack Track { get; set; } public override bool UpdateArray(ActionContext context, ActionArrayInfo info) { if (CurrentView == null || SelectedClips.Any()) { return true; } Point currentLocation = context.Location; if (currentLocation == Point.Empty) { currentLocation = Control.MousePosition; } var dropLocation = CurrentControl.PointToClient(currentLocation); Track = CurrentControl.TrackFromPoint(dropLocation.Y); if (Track != null) { foreach (var clipType in ClipTypes().AsSorted((x1, x2) => System.String.Compare(x1.Name, x2.Name, System.StringComparison.Ordinal))) { var action = new ActionInfoElement("Add " + clipType.GetStyle().DisplayName) { Icon = new ImageOrIcon(clipType.GetStyle().Icon), State = clipType }; info.Add(action); } } return true; } public override void Execute(ActionExecutionContext context) { if (CurrentTape == null) { return; } var type = (System.Type)context.State; // From the shortcut. Clip newClip; if (type == null) { newClip = InstantiateDefaultClip(); } else { var dropLocation = CurrentControl.PointToClient(context.Location); newClip = Instantiate(type, dropLocation); } if (newClip != null) { CurrentTape.Clips.Add(newClip); var newWidget = CurrentControl.ClipWidgets.FirstOrDefault(x => x.Model == newClip); newWidget.OnInserted(); } } private Clip InstantiateDefaultClip() { var trackWidget = CurrentView.ClipView.TrackView.ActiveTrack as TapeTrackWidget; if (trackWidget != null) { var track = trackWidget.Track; var clipType = track.ClipTypes().SingleOrDefault(); var curentBeat = CurrentControl.Timeline.ConvertTo(CurrentControl.Timeline.TimeCursor.Time, TimeUnit.Beat); if (clipType == null) { // When the track is not of a fixed type // we assume the user might want to create the same type of clip he just created before. // so we find a type that is near the actual time cursor on the same track. clipType = (from clip in track.Tape.Clips where clip.Track == track orderby Math.Abs(clip.StartBeat - curentBeat) select clip.GetType()).FirstOrDefault(); } if (clipType != null && ValidateCanAddClip(clipType)) { curentBeat = CurrentTape.TryGetWave().UnitConverter.Snap(new BeatUnit(curentBeat), EditionOptions.CurrentSnappingUnit).Value; var newClip = Clip.Instantiate(clipType); newClip.InitializeDefaultValues(); newClip.StartFrame = FindBestStartFrame(track, FrameConverter.FrameFromTimeInteger(curentBeat)); newClip.TrackId = track.Id; return newClip; } } return null; } protected virtual Clip Instantiate(System.Type clipType, Point dropLocation) { var track = CurrentControl.TrackFromPoint(dropLocation.Y); if (track != null && ValidateCanAddClip(clipType)) { var newEvent = Clip.Instantiate(clipType); newEvent.InitializeDefaultValues(); newEvent.StartFrame = FindBestStartFrame(track, (int) CurrentControl.FrameFromPoint(dropLocation.X)); newEvent.TrackId = track.Id; if (newEvent.DefaultDuration > 0) { newEvent.Duration = newEvent.DefaultDuration; } else { newEvent.Duration = (int)CurrentControl.Timeline.FrameFromPixelDuration(200); } return newEvent; } return null; } /// /// Sets the starting frame to make sure it does not overlap an existing clip /// private int FindBestStartFrame(TapeTrack track, int frame) { Clip collidingClip; while (TryGetCollidingClip(track, frame, out collidingClip)) { frame = collidingClip.StopFrame; } return frame; } private bool TryGetCollidingClip(TapeTrack track, int frame, out Clip collision) { collision = track.Clips().FirstOrDefault(x => frame.IsBetween(x.StartFrame, x.StopFrame - 1)); return collision != null; } protected int ClipLimit { get; set; } private bool ValidateCanAddClip(System.Type type) { var clipCount = CurrentControl.Tape.Clips.Count; if (type != typeof(TapeReferenceClip) && clipCount > ClipLimit) { var dialog = new MessageDialog { MainIcon = MessageDialogIcon.Warning, Content = $"Clip limit reached ({clipCount} clips instead of 300). Are you sure you want to continue?" }; var yes = dialog.AddButton("Add clip anyway"); yes.Content = "We suggest you start to reduce the clip count before adding new clips."; var help = dialog.AddButton("Help"); var cancel = dialog.AddButton("Cancel"); cancel.IsDefault = true; var result = dialog.ShowDialog(); if (result == help) { ClipLimitWarning.OpenHelpFile(); } return result == yes; } return true; } } }