using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using JD.Application.View.Misc; using JD.Application.View.PropertyGrid; using JD.Application.View.Timeline; using JD.Application.View.WindowLess; using JD.Audio.Annotations; using JD.Audio.Sound; using JD.Kernel.Interfaces; using JD.Kernel.StaticHelpers; using JD.Sequencers.Times; using JD.Sequencers.Windowlesses; using TeaBox; namespace JD.Application.View.TrackItems { /// /// A visual representation of an annotation in a track. /// public class AnnotationTrackItem : LocalizedTrackItem, ICloneable { #region Constants private const int MaxTextPixelWidth = 140; private const int PaddingImageText = 0; private const int LabelPadding = 2; // Padding between the label and the text. private static readonly SolidBrush _labelBrush = new SolidBrush(Color.FromArgb(100, Color.Black)); private static readonly SolidBrush _selectedLabelBrush = new SolidBrush(Color.FromArgb(175, Color.WhiteSmoke)); private static readonly SolidBrush _textBrush = new SolidBrush(Color.WhiteSmoke); private static readonly SolidBrush _selectedTextBrush = new SolidBrush(Color.FromArgb(50, 50, 50)); private static readonly StringFormat _stringFormat = new StringFormat() { Trimming = StringTrimming.EllipsisCharacter }; private static readonly Font _font = new Font(FontFamily.GenericSansSerif, 8f); #endregion #region Variables private readonly global::JD.Sequencers.Times.Timeline _timeline; private readonly EditableAnnotation _model; private readonly Size _imageSize; private readonly Image _image; #endregion #region Constructor protected AnnotationTrackItem(EditableAnnotation model, global::JD.Sequencers.Times.Timeline timeline, Image image) { Throw.IfNull(image, "image"); _timeline = timeline; _model = model; IsMoveAllowed = true; Time = model.Time; _image = image; _imageSize = _image.Size; } #endregion #region Properties /// /// The collection containing the list of model (allows to add/remove items). /// public IAnnotationList ParentCollection { get; set; } public EditableAnnotation Model { get { return _model; } } public new BeatUnit Time { get { return _timeline.BeatFromTimeUnit(base.Time); } set { base.Time = _timeline.TimeUnitFromBeat(value); } } public virtual SnappingUnit SnappingUnit { get { return EditionOptions.CurrentSnappingUnit; } } public override string ToolTipText { get { if (Timeline == null) { // Happens when the control has been removed. return string.Empty; } var wave = Timeline.Wave(); var beat = wave.Annotations.Markers[Time]; var time = beat.Time; string measure = string.Concat(beat.MeasurePosition, ".", beat.PositionInMeasure); string comment = Model.Comment; if (!string.IsNullOrEmpty(comment)) { comment = "\n" + comment; } return $"{measure}\n{time.Minutes:00}m {time.Seconds:00}s{comment}"; } } public override string Text { get { return Model.ToString(); } } public override bool SupportAutoLoop { get { return true; } } #endregion #region Layout private Size ImageSize { get { return _imageSize; } } public int RowHeight { get { return ImageSize.Height; } } /// /// Bounds relative to parent track bottom. /// public Rectangle RelativeBounds { get; set; } public bool ShowLabel { get; set; } public void UpdatePosition(Graphics graphics, List unavailableSpace) { var rowHeight = RowHeight; var relativeBounds = new Rectangle { Width = ImageSize.Width + PaddingImageText + (int)graphics.MeasureString(Text, _font, MaxTextPixelWidth).Width + LabelPadding * 2, Height = rowHeight, X = Timeline.TimeToPixel(Time) - ImageSize.Width / 2, Y = -RowHeight, }; while (unavailableSpace.Any(rectangle => rectangle.Contains(relativeBounds.Location))) { relativeBounds.Y -= rowHeight + 1; } RelativeBounds = relativeBounds; } #endregion #region Painting / Layout protected override void OnPaintSelf(PaintEventArgs e) { var graphics = e.Graphics; var text = Text; var drawBounds = DrawMarker(_image, graphics); if (!string.IsNullOrEmpty(text) && ShowLabel) { drawBounds = Rectangle.Union(drawBounds, DrawText(text, graphics)); } if (Selected) { ControlPaint.DrawFocusRectangle(graphics, drawBounds); } } private Rectangle DrawText(string text, Graphics graphics) { // Draw label. Rectangle labelBounds = Bounds; int offset = ImageSize.Width + PaddingImageText; labelBounds.X += offset; labelBounds.Width -= offset; graphics.FillRectangle(Selected ? _selectedLabelBrush : _labelBrush, labelBounds); // Draw text. var textBounds = labelBounds; textBounds.Height = _font.Height; textBounds.X++; textBounds.Y++; // Offset the text otherwise it would appear too close with the edge graphics.DrawString(text, _font, Selected ? _selectedTextBrush : _textBrush, textBounds, _stringFormat); return labelBounds; } protected Rectangle DrawMarker(Image image, Graphics graphics) { var rect = new Rectangle(Bounds.Location, image.Size); graphics.DrawImage(image, rect); return rect; } /// /// Sets the same style as the current one to the text box. /// /// public void CloneDisplayStyle(TextBox textBox) { if (Selected) { textBox.BackColor = _selectedLabelBrush.Color; textBox.ForeColor = _selectedTextBrush.Color; } else { textBox.BackColor = _labelBrush.Color; textBox.ForeColor = _textBrush.Color; } textBox.Font = _font; } #endregion #region Edition /// /// Returns a property grid representation of the current model. /// public virtual object ToGridView(Wave parentWave) { return new AnnotationGridView(Model, parentWave); } /// /// Call the remove method so the remove handle can remove the corresponding object. /// public void Remove() { ParentCollection.Remove(Model); } /// /// Update internal model when the track item is moved. /// protected override void OnTimeChanged(System.EventArgs e) { base.OnTimeChanged(e); Model.Time = Time; } #endregion #region Snap public override void Move(float delta) { base.Time += delta; } public override void Snap_Self(float originalDelta, ref float snappedDelta, ref float diff) { BeatUnit newValue = Timeline.Wave().UnitConverter.Snap(new BeatUnit(Time.Value + originalDelta), SnappingUnit); snappedDelta = newValue.Value - Time.Value; } #endregion #region ICloneable /// /// Implements the clone on drag. /// WindowlessControl ICloneable.Clone() { var clone = Model.Clone(); ParentCollection.Add(clone); return Track.TrackView.ControlsRecursive().OfType().First(control => control.Model == clone); } #endregion } }