Skip to content

Commit 1ac729d

Browse files
authored
Merge pull request #810 from jeffu231/VIX-3853
VIX-3853 Fix bug with copy and paste of effects
2 parents 8ccc016 + be0b3e6 commit 1ac729d

File tree

4 files changed

+140
-102
lines changed

4 files changed

+140
-102
lines changed

src/Vixen.Modules/Editor/TimedSequenceEditor/EffectModelCandidate.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.IO;
22
using System.Runtime.Serialization;
3+
using System.Text.Json.Serialization;
34
using System.Xml;
4-
using Vixen.Annotations;
55
using Vixen.Module;
66
using Vixen.Module.Effect;
77

@@ -12,11 +12,24 @@ namespace VixenModules.Editor.TimedSequenceEditor
1212
/// <summary>
1313
/// Class to hold effect data to allow it to be placed on the clipboard and be reconstructed when later pasted
1414
/// </summary>
15-
[Serializable]
1615
public class EffectModelCandidate
1716
{
18-
private readonly string? _moduleDataClass;
19-
private readonly byte[]? _effectData;
17+
[JsonInclude]
18+
private string? _moduleDataClass;
19+
[JsonInclude]
20+
private byte[]? _effectData;
21+
22+
/// <summary>
23+
/// Initializes a new instance of the EffectModelCandidate class. This parameterless constructor is intended for use
24+
/// during deserialization. Do not use in code!
25+
/// </summary>
26+
/// <remarks>Some serialization frameworks require a public parameterless constructor to instantiate objects
27+
/// during the deserialization process. This constructor should not typically be used directly in application
28+
/// code.</remarks>
29+
public EffectModelCandidate()
30+
{
31+
32+
}
2033

2134
public EffectModelCandidate(IEffectModuleInstance effect)
2235
{
@@ -48,6 +61,8 @@ public EffectModelCandidate(IEffectModuleInstance effect)
4861

4962
public TimeSpan StartTime { get; set; }
5063
public TimeSpan Duration { get; set; }
64+
65+
[JsonInclude]
5166
public Guid TypeId { get; private set; }
5267
public Guid LayerId { get; set; }
5368
public Guid LayerTypeId { get; set; }

src/Vixen.Modules/Editor/TimedSequenceEditor/TimedSequenceEditorForm.cs

Lines changed: 117 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
using System.Collections;
2-
using System.Collections.Specialized;
3-
using System.ComponentModel;
4-
using System.Diagnostics;
5-
using System.Globalization;
6-
using System.IO;
7-
using System.Runtime.Serialization;
8-
using System.Timers;
9-
using System.Windows.Forms.Integration;
10-
using System.Xml;
11-
using Common.AudioPlayer;
1+
using Common.AudioPlayer;
2+
using Common.Broadcast;
123
using Common.Controls;
134
using Common.Controls.Scaling;
145
using Common.Controls.Theme;
@@ -18,19 +9,25 @@
189
using Common.Resources;
1910
using Common.Resources.Properties;
2011
using NLog;
12+
using System.Collections;
13+
using System.Collections.Specialized;
14+
using System.ComponentModel;
15+
using System.Diagnostics;
16+
using System.Globalization;
17+
using System.IO;
18+
using System.Reflection.Metadata;
19+
using System.Runtime.Serialization;
20+
using System.Text.Json;
21+
using System.Text.Json.Serialization;
22+
using System.Timers;
23+
using System.Windows.Forms.Integration;
24+
using System.Xml;
2125
using Vixen;
2226
using Vixen.Execution;
2327
using Vixen.Execution.Context;
2428
using Vixen.Marks;
2529
using Vixen.Module;
2630
using Vixen.Module.App;
27-
using VixenModules.App.Curves;
28-
using VixenModules.App.LipSyncApp;
29-
using VixenModules.Effect.Effect;
30-
using VixenModules.Effect.Picture;
31-
using VixenModules.Effect.Shapes;
32-
using VixenModules.Media.Audio;
33-
using VixenModules.Effect.LipSync;
3431
using Vixen.Module.Editor;
3532
using Vixen.Module.Effect;
3633
using Vixen.Module.Media;
@@ -39,25 +36,31 @@
3936
using Vixen.Sys;
4037
using Vixen.Sys.LayerMixing;
4138
using VixenModules.App.ColorGradients;
39+
using VixenModules.App.Curves;
40+
using VixenModules.App.LipSyncApp;
4241
using VixenModules.App.Marks;
43-
using VixenModules.Editor.EffectEditor;
4442
using VixenModules.App.TimedSequenceMapper.SequenceElementMapper.ViewModels;
4543
using VixenModules.App.TimedSequenceMapper.SequenceElementMapper.Views;
44+
using VixenModules.Editor.EffectEditor;
4645
using VixenModules.Editor.TimedSequenceEditor.Undo;
46+
using VixenModules.Effect.Effect;
47+
using VixenModules.Effect.LipSync;
48+
using VixenModules.Effect.Picture;
49+
using VixenModules.Effect.Shapes;
50+
using VixenModules.Media.Audio;
51+
using VixenModules.Property.Color;
4752
using VixenModules.Sequence.Timed;
4853
using WeifenLuo.WinFormsUI.Docking;
49-
using Element = Common.Controls.Timeline.Element;
50-
using Timer = System.Windows.Forms.Timer;
51-
using VixenModules.Property.Color;
54+
using Cursors = System.Windows.Forms.Cursors;
5255
using DockPanel = WeifenLuo.WinFormsUI.Docking.DockPanel;
56+
using Element = Common.Controls.Timeline.Element;
5357
using ListView = System.Windows.Forms.ListView;
5458
using ListViewItem = System.Windows.Forms.ListViewItem;
5559
using MarkCollection = VixenModules.App.Marks.MarkCollection;
56-
using Cursors = System.Windows.Forms.Cursors;
5760
using MouseEventArgs = System.Windows.Forms.MouseEventArgs;
5861
using PropertyDescriptor = System.ComponentModel.PropertyDescriptor;
5962
using Size = System.Drawing.Size;
60-
using Common.Broadcast;
63+
using Timer = System.Windows.Forms.Timer;
6164

6265
namespace VixenModules.Editor.TimedSequenceEditor
6366
{
@@ -2150,7 +2153,7 @@ protected override void WndProc(ref Message m)
21502153

21512154
Debug.WriteLine("WindowProc DRAWCLIPBOARD: " + m.Msg, "WndProc");
21522155

2153-
if (ClipboardHasData())
2156+
if (ClipboardHasTimelineElementData())
21542157
{
21552158
_TimeLineSequenceClipboardContentsChanged(EventArgs.Empty);
21562159
}
@@ -3463,33 +3466,6 @@ private void CursorMovedHandler(object sender, EventArgs e)
34633466
}
34643467
}
34653468

3466-
private void UpdatePasteMenuStates()
3467-
{
3468-
editToolStripButton_Paste.Enabled = toolStripMenuItem_Paste.Enabled = GetClipboardCount() > 0;
3469-
editToolStripButton_PasteVisibleMarks.Visible = toolStripMenuItem_PasteToMarks.Enabled = GetMarksPresent() && GetClipboardCount() > 0;
3470-
editToolStripButton_PasteInvert.Visible = toolStripMenuItem_PasteInvert.Enabled = GetClipboardCount() > 1;
3471-
editToolStripButton_PasteDropDown.Enabled = toolStripMenuItem_PasteSpecial.Enabled =
3472-
toolStripMenuItem_PasteToMarks.Enabled || toolStripMenuItem_PasteInvert.Enabled;
3473-
}
3474-
3475-
private bool ClipboardHasData()
3476-
{
3477-
IDataObject dataObject = Clipboard.GetDataObject();
3478-
return dataObject != null && dataObject.GetDataPresent(ClipboardFormatName.Name);
3479-
}
3480-
3481-
private int GetClipboardCount()
3482-
{
3483-
// Gets number of Effects on the clipboard, used to determine which paste options will be enabled.
3484-
IDataObject dataObject = Clipboard.GetDataObject();
3485-
if (dataObject.GetDataPresent(ClipboardFormatName.Name))
3486-
{
3487-
if (dataObject.GetData(ClipboardFormatName.Name) is TimelineElementsClipboardData data)
3488-
return data.EffectModelCandidates.Count;
3489-
}
3490-
return 0;
3491-
}
3492-
34933469
private bool GetMarksPresent()
34943470
{
34953471
// Checks if there are any visible marks that are past the mouse click position.
@@ -4965,6 +4941,72 @@ protected override void OnFormClosed(FormClosedEventArgs e)
49654941

49664942
#region Clipboard
49674943

4944+
private void UpdatePasteMenuStates()
4945+
{
4946+
var count = GetTimelineElementsClipboardEffectCount();
4947+
editToolStripButton_Paste.Enabled = toolStripMenuItem_Paste.Enabled = count > 0;
4948+
editToolStripButton_PasteVisibleMarks.Visible = toolStripMenuItem_PasteToMarks.Enabled = GetMarksPresent() && count > 0;
4949+
editToolStripButton_PasteInvert.Visible = toolStripMenuItem_PasteInvert.Enabled = count > 1;
4950+
editToolStripButton_PasteDropDown.Enabled = toolStripMenuItem_PasteSpecial.Enabled =
4951+
toolStripMenuItem_PasteToMarks.Enabled || toolStripMenuItem_PasteInvert.Enabled;
4952+
}
4953+
4954+
/// <summary>
4955+
/// Determines whether the clipboard contains data in the timeline element format.
4956+
/// </summary>
4957+
/// <returns>true if the clipboard contains data in the timeline element format; otherwise, false.</returns>
4958+
private bool ClipboardHasTimelineElementData()
4959+
{
4960+
return Clipboard.ContainsData(ClipboardFormatName.Name);
4961+
}
4962+
4963+
/// <summary>
4964+
/// Gets the number of effect model candidates available in the clipboard timeline elements.
4965+
/// </summary>
4966+
/// <remarks>Call this method to determine how many effect model candidates are currently available from the
4967+
/// clipboard timeline elements. Ensure that the clipboard contains valid timeline data before relying on the returned
4968+
/// count.</remarks>
4969+
/// <returns>The number of effect model candidates in the clipboard timeline elements. Returns 0 if no clipboard data is
4970+
/// present.</returns>
4971+
private int GetTimelineElementsClipboardEffectCount()
4972+
{
4973+
var timelineData = GetTimelineElementsClipboardData();
4974+
return timelineData != null ? timelineData.EffectModelCandidates.Count : 0;
4975+
}
4976+
4977+
/// <summary>
4978+
/// Retrieves timeline elements data from the clipboard if available in the expected format.
4979+
/// </summary>
4980+
/// <remarks>This method checks whether the clipboard contains data in the specified timeline elements format
4981+
/// before attempting to retrieve it. Callers should verify the return value for <see langword="null"/> to determine
4982+
/// if valid data was retrieved.</remarks>
4983+
/// <returns>A <see cref="TimelineElementsClipboardData"/> instance containing the timeline elements from the clipboard; or
4984+
/// <see langword="null"/> if the clipboard does not contain data in the required format.</returns>
4985+
private TimelineElementsClipboardData GetTimelineElementsClipboardData()
4986+
{
4987+
if (ClipboardHasTimelineElementData())
4988+
{
4989+
if (Clipboard.TryGetData<TimelineElementsClipboardData>(ClipboardFormatName.Name, out var timelineData))
4990+
{
4991+
return timelineData;
4992+
}
4993+
}
4994+
return null;
4995+
}
4996+
4997+
/// <summary>
4998+
/// Sets the clipboard data for timeline elements using the specified data object.
4999+
/// </summary>
5000+
/// <remarks>This method serializes the provided data as JSON and stores it in the clipboard under a specific
5001+
/// format. Ensure that the data object is valid before calling this method.</remarks>
5002+
/// <param name="data">The timeline elements clipboard data to be serialized and placed on the clipboard.</param>
5003+
private void SetTimelineElementsClipboardData(TimelineElementsClipboardData data)
5004+
{
5005+
ArgumentNullException.ThrowIfNull(data);
5006+
Clipboard.SetDataAsJson(ClipboardFormatName.Name, data);
5007+
//_TimeLineSequenceClipboardContentsChanged(EventArgs.Empty);
5008+
}
5009+
49685010
private void ClipboardAddData(bool cutElements)
49695011
{
49705012
if (!TimelineControl.SelectedElements.Any())
@@ -4983,12 +5025,11 @@ private void ClipboardAddData(bool cutElements)
49835025
foreach (Row row in TimelineControl.VisibleRows)
49845026
{
49855027
//Check that the same Row name has not already been processed and will skip if the same Row is found visible in another group.
4986-
if (uniqueStrings.Contains(row.Name)) continue;
4987-
uniqueStrings.Add(row.Name);
5028+
if (!uniqueStrings.Add(row.Name)) continue;
49885029

49895030
// Since removals may happen during enumeration, make a copy with ToArray().
49905031

4991-
//If we already have the elements becasue the same row is duplicated then skip.
5032+
//If we already have the elements because the same row is duplicated then skip.
49925033
if (affectedElements.Intersect(row.SelectedElements).Any())
49935034
{
49945035
rownum++;
@@ -5012,7 +5053,7 @@ private void ClipboardAddData(bool cutElements)
50125053
LayerName = layer.LayerName,
50135054
LayerTypeId = layer.FilterTypeId
50145055
};
5015-
result.EffectModelCandidates.Add(modelCandidate, relativeVisibleRow);
5056+
result.EffectModelCandidates.Add(new EffectModelRecord(modelCandidate, relativeVisibleRow));
50165057

50175058
if (elem.StartTime < result.EarliestStartTime)
50185059
result.EarliestStartTime = elem.StartTime;
@@ -5030,12 +5071,8 @@ private void ClipboardAddData(bool cutElements)
50305071
var act = new EffectsCutUndoAction(this, affectedElements.Select(x => x.EffectNode));
50315072
_undoMgr.AddUndoAction(act);
50325073
}
5033-
50345074

5035-
IDataObject dataObject = new DataObject(ClipboardFormatName);
5036-
dataObject.SetData(result);
5037-
Clipboard.SetDataObject(dataObject, true);
5038-
_TimeLineSequenceClipboardContentsChanged(EventArgs.Empty);
5075+
SetTimelineElementsClipboardData(result);
50395076
}
50405077

50415078
private void ClipboardCut()
@@ -5056,32 +5093,23 @@ private void ClipboardCopy()
50565093
public int ClipboardPaste(TimeSpan pasteTime)
50575094
{
50585095
int result = 0;
5059-
TimelineElementsClipboardData data = null;
5060-
IDataObject dataObject = Clipboard.GetDataObject();
5061-
5062-
if (dataObject == null)
5063-
return result;
5064-
5065-
if (dataObject.GetDataPresent(ClipboardFormatName.Name))
5066-
{
5067-
data = dataObject.GetData(ClipboardFormatName.Name) as TimelineElementsClipboardData;
5068-
}
5069-
5096+
TimelineElementsClipboardData data = GetTimelineElementsClipboardData();
5097+
50705098
if(data == null)
50715099
{
50725100
return result;
50735101
}
50745102

50755103
List<int> index = new List<int>();
50765104
List<TimeSpan> markStartTimes = new List<TimeSpan>();
5077-
List<KeyValuePair<EffectModelCandidate, int>> effects;
5105+
List<EffectModelRecord> effects;
50785106
switch (PastingMode)
50795107
{
50805108
case PastingMode.VisibleMarks:
50815109
{
50825110
// We need to order the effects by Start time as they are currently ordered by Row index first which is
50835111
// no good for pasting to Mark Collection or Visible Marks.
5084-
effects = data.EffectModelCandidates.OrderBy(x => (x.Key.StartTime)).ToList();
5112+
effects = data.EffectModelCandidates.OrderBy(x => (x.EffectModel.StartTime)).ToList();
50855113
for (int i = 0; i < _sequence.LabeledMarkCollections.Count; i++)
50865114
{
50875115
// Only continue process visible Mark collections
@@ -5112,9 +5140,9 @@ public int ClipboardPaste(TimeSpan pasteTime)
51125140
break;
51135141
}
51145142
case PastingMode.Invert:
5115-
foreach (KeyValuePair<EffectModelCandidate, int> order in data.EffectModelCandidates)
5143+
foreach (EffectModelRecord order in data.EffectModelCandidates)
51165144
{
5117-
index.Add(data.EffectModelCandidates.Last().Value - order.Value);
5145+
index.Add(data.EffectModelCandidates.Last().RowOffset - order.RowOffset);
51185146
}
51195147
effects = data.EffectModelCandidates.ToList();
51205148
break;
@@ -5131,10 +5159,10 @@ public int ClipboardPaste(TimeSpan pasteTime)
51315159
List<Row> visibleRows = new List<Row>(TimelineControl.VisibleRows);
51325160
int topTargetRoxIndex = visibleRows.IndexOf(targetRow);
51335161
List<EffectNode> nodesToAdd = new List<EffectNode>();
5134-
foreach (KeyValuePair<EffectModelCandidate, int> kvp in effects)
5162+
foreach (EffectModelRecord effectModelRecord in effects)
51355163
{
5136-
EffectModelCandidate effectModelCandidate = kvp.Key;
5137-
int relativeRow = kvp.Value;
5164+
EffectModelCandidate effectModelCandidate = effectModelRecord.EffectModel;
5165+
int relativeRow = effectModelRecord.RowOffset;
51385166
TimeSpan targetTime = effectModelCandidate.StartTime - offset + pasteTime;
51395167
switch (PastingMode)
51405168
{
@@ -5932,15 +5960,14 @@ private void textConverterHandler(object sender, NewTranslationEventArgs args)
59325960
Duration = data.Duration,
59335961
StartTime = data.StartOffset
59345962
};
5935-
result.EffectModelCandidates.Add(modelCandidate, 0);
5963+
result.EffectModelCandidates.Add(new EffectModelRecord(modelCandidate, 0));
59365964
if (data.StartOffset < result.EarliestStartTime)
59375965
result.EarliestStartTime = data.StartOffset;
59385966
effect.PreRender();
59395967
}
5940-
IDataObject dataObject = new DataObject(ClipboardFormatName);
5941-
dataObject.SetData(result);
5942-
Clipboard.SetDataObject(dataObject, true);
5943-
_TimeLineSequenceClipboardContentsChanged(EventArgs.Empty);
5968+
5969+
SetTimelineElementsClipboardData(result);
5970+
59445971
int pasted = 0;
59455972
if (args.Placement == TranslatePlacement.Cursor)
59465973
{
@@ -6156,20 +6183,19 @@ public TimeSpan GetDefaultEffectDuration(TimeSpan startTime,bool visibleDuration
61566183
}
61576184
}
61586185

6159-
[Serializable]
61606186
internal class TimelineElementsClipboardData
61616187
{
6162-
public TimelineElementsClipboardData()
6163-
{
6164-
EffectModelCandidates = new Dictionary<EffectModelCandidate, int>();
6165-
}
6166-
61676188
// a collection of elements and the number of rows they were below the top visible element when
61686189
// this data was generated and placed on the clipboard.
6169-
public Dictionary<EffectModelCandidate, int> EffectModelCandidates { get; set; }
6190+
public List<EffectModelRecord> EffectModelCandidates { get; init; } = new();
61706191

61716192
public int FirstVisibleRow { get; set; }
61726193

61736194
public TimeSpan EarliestStartTime { get; set; }
61746195
}
6196+
6197+
internal record EffectModelRecord(EffectModelCandidate EffectModel, int RowOffset)
6198+
{
6199+
6200+
}
61756201
}

0 commit comments

Comments
 (0)