diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs
index fbc64c6..caa7be4 100644
--- a/SourceGen/MainController.cs
+++ b/SourceGen/MainController.cs
@@ -2173,11 +2173,16 @@ namespace SourceGen {
if (dlg.ShowDialog() != true) {
return;
}
- if (curVisSet != dlg.NewVisSet) {
+ VisualizationSet newSet = dlg.NewVisSet;
+ if (newSet.Count == 0) {
+ // empty sets are deleted
+ newSet = null;
+ }
+ if (curVisSet != newSet) {
// New table, edited in place, or deleted.
UndoableChange uc = UndoableChange.CreateVisualizationSetChange(offset,
- curVisSet, dlg.NewVisSet);
- Debug.WriteLine("Change " + curVisSet + " to " + dlg.NewVisSet);
+ curVisSet, newSet);
+ //Debug.WriteLine("Change " + curVisSet + " to " + newSet);
ChangeSet cs = new ChangeSet(uc);
ApplyUndoableChanges(cs);
} else {
diff --git a/SourceGen/RuntimeData/Apple/VisHiRes.cs b/SourceGen/RuntimeData/Apple/VisHiRes.cs
index 2c20e57..657e743 100644
--- a/SourceGen/RuntimeData/Apple/VisHiRes.cs
+++ b/SourceGen/RuntimeData/Apple/VisHiRes.cs
@@ -279,6 +279,9 @@ namespace RuntimeData.Apple {
int high = ((row & 0x07) << 2) | ((row & 0x30) >> 4);
int rowAddr = baseAddr + ((high << 8) | low);
+ // Not expecting the data to wrap around, but it's possible.
+ rowAddr = (baseAddr & 0xff0000) | (rowAddr & 0xffff);
+
for (int col = 0; col < HR_BYTE_WIDTH; col++) {
int srcOffset = mAddrTrans.AddressToOffset(offset, rowAddr + col);
if (srcOffset < 0) {
diff --git a/SourceGen/RuntimeData/Help/advanced.html b/SourceGen/RuntimeData/Help/advanced.html
index edd23ba..1a76acb 100644
--- a/SourceGen/RuntimeData/Help/advanced.html
+++ b/SourceGen/RuntimeData/Help/advanced.html
@@ -177,6 +177,10 @@ invoked is not defined.
Known Issues and Limitations
+
Scripts are currently limited to C# version 5, because the compiler
+built into .NET only handles that. C# 6 and later require installing an
+additional package (Roslyn).
+
When a project is opened, any errors encountered by the script compiler
are reported to the user. If the project is already open, and a script
is added to the project through the Project Properties editor, compiler
diff --git a/SourceGen/Sandbox/PluginDllCache.cs b/SourceGen/Sandbox/PluginDllCache.cs
index e36fd40..2d9ec7f 100644
--- a/SourceGen/Sandbox/PluginDllCache.cs
+++ b/SourceGen/Sandbox/PluginDllCache.cs
@@ -185,6 +185,10 @@ namespace SourceGen.Sandbox {
out FileLoadReport report) {
report = new FileLoadReport(scriptPathName);
+ // To get C#6 (and later) features, a NuGet package must be installed, and
+ // some "black magic" must be invoked.
+ // See https://stackoverflow.com/a/40311406/294248 and nearby answers.
+
Microsoft.CSharp.CSharpCodeProvider csProvider =
new Microsoft.CSharp.CSharpCodeProvider();
diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj
index d15603c..e0b3755 100644
--- a/SourceGen/SourceGen.csproj
+++ b/SourceGen/SourceGen.csproj
@@ -97,6 +97,7 @@
+ AboutBox.xaml
@@ -104,6 +105,9 @@
EditAppSettings.xaml
+
+ EditBitmapAnimation.xaml
+ EditComment.xaml
@@ -278,6 +282,10 @@
DesignerMSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+ DesignerMSBuild:Compile
diff --git a/SourceGen/Visualization.cs b/SourceGen/Visualization.cs
index 3ece55d..d67db5b 100644
--- a/SourceGen/Visualization.cs
+++ b/SourceGen/Visualization.cs
@@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Diagnostics;
using System.Text;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@@ -29,6 +30,11 @@ namespace SourceGen {
///
/// This is generally immutable, except for the CachedImage field.
///
+ ///
+ /// Immutability is useful here because the undo/redo mechanism operates at VisualizationSet
+ /// granularity. We want to know that the undo/redo operations are operating on objects
+ /// that weren't changed while sitting in the undo buffer.
+ ///
public class Visualization {
///
/// Unique user-specified tag. This may be any valid string that is at least two
@@ -44,15 +50,25 @@ namespace SourceGen {
///
/// Parameters to be passed to the visualization generator.
///
+ ///
+ /// We use a read-only dictionary to reinforce the idea that the plugin shouldn't be
+ /// modifying the parameter dictionary.
+ ///
public ReadOnlyDictionary VisGenParams { get; private set; }
///
- /// Cached reference to 2D image, useful for thumbnails. Not serialized. This always
- /// has an image reference; in times of trouble it will point at BROKEN_IMAGE.
+ /// Cached reference to 2D image, useful for thumbnails that we display in the
+ /// code listing. Not serialized. This always has an image reference; in times
+ /// of trouble it will point at BROKEN_IMAGE.
///
///
/// Because the underlying data never changes, we only need to regenerate the
/// image if the set of active plugins changes.
+ ///
+ /// For 2D bitmaps this should be close to a 1:1 representation of the original,
+ /// subject to the limitations of the visualization generator. For other types of
+ /// data (vector line art, 3D meshes) this is a "snapshot" to help the user identify
+ /// the data.
///
public BitmapSource CachedImage { get; set; }
@@ -62,19 +78,60 @@ namespace SourceGen {
public static readonly BitmapImage BROKEN_IMAGE =
new BitmapImage(new Uri("pack://application:,,,/Res/RedX.png"));
+ ///
+ /// Serial number, for reference from other Visualization objects. Not serialized.
+ ///
+ ///
+ /// This value is only valid in the current session. It exists because animations
+ /// need to refer to other Visualization objects, and doing so by Tag gets sticky
+ /// if a tag gets renamed. We need a way to uniquely identify a reference to a
+ /// Visualization that persists across Tag renames and other edits. When the objects
+ /// are serialized to the project file we just output the tags.
+ ///
+ public int SerialNumber { get; private set; }
///
- /// Constructor.
+ /// Serial number source.
+ ///
+ private static int sNextSerial = 1000;
+
+
+ ///
+ /// Constructor for a new Visualization.
///
/// Unique identifier.
/// Visualization generator identifier.
/// Parameters for visualization generator.
public Visualization(string tag, string visGenIdent,
- ReadOnlyDictionary visGenParams) {
+ ReadOnlyDictionary visGenParams)
+ :this(tag, visGenIdent, visGenParams, null) { }
+
+ ///
+ /// Constructor for a replacement Visualization.
+ ///
+ /// Unique identifier.
+ /// Visualization generator identifier.
+ /// Parameters for visualization generator.
+ /// Visualization being replaced, or null if this is new.
+ public Visualization(string tag, string visGenIdent,
+ ReadOnlyDictionary visGenParams, Visualization oldObj) {
+ Debug.Assert(!string.IsNullOrEmpty(tag));
+ Debug.Assert(!string.IsNullOrEmpty(visGenIdent));
+ Debug.Assert(visGenParams != null);
+
Tag = tag;
VisGenIdent = visGenIdent;
VisGenParams = visGenParams;
CachedImage = BROKEN_IMAGE;
+
+ if (oldObj == null) {
+ // not worried about multiple threads
+ SerialNumber = sNextSerial++;
+ } else {
+ Debug.Assert(oldObj.SerialNumber >= 0 && oldObj.SerialNumber < sNextSerial);
+ SerialNumber = oldObj.SerialNumber;
+ }
+ Debug.WriteLine("NEW VIS: Serial=" + SerialNumber);
}
///
diff --git a/SourceGen/VisualizationAnimation.cs b/SourceGen/VisualizationAnimation.cs
new file mode 100644
index 0000000..8f47b95
--- /dev/null
+++ b/SourceGen/VisualizationAnimation.cs
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2019 faddenSoft
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Text;
+
+namespace SourceGen {
+ ///
+ /// A visualization with animated contents.
+ ///
+ ///
+ /// References to Visualization objects (such as a 3D mesh or list of bitmaps) are held
+ /// here. The VisGenParams property holds animation properties, such as frame rate and
+ /// view angles.
+ ///
+ public class VisualizationAnimation : Visualization {
+ ///
+ /// Serial numbers of visualizations, e.g. bitmap frames.
+ ///
+ ///
+ /// We don't reference the Visualization objects directly because they might get
+ /// edited (e.g. the tag gets renamed), which replaces them with a new object with
+ /// the same serial number. We don't do things like renames in place because that
+ /// makes undo/redo harder.
+ ///
+ /// (We could reference the Visualization objects and then do a serial number lookup
+ /// before using it. Some opportunities for optimization should the need arise. This
+ /// might also allow us to avoid exposing the serial number as a public property, though
+ /// there's not much advantage to that.)
+ ///
+ private List mSerialNumbers;
+
+
+ ///
+ /// Constructor.
+ ///
+ /// Unique identifier.
+ /// Visualization generator identifier.
+ /// Parameters for visualization generator.
+ /// Serial numbers of referenced Visualizations.
+ public VisualizationAnimation(string tag, string visGenIdent,
+ ReadOnlyDictionary visGenParams, List visSerialNumbers)
+ : base(tag, visGenIdent, visGenParams) {
+ Debug.Assert(visSerialNumbers != null);
+
+ mSerialNumbers = visSerialNumbers;
+ }
+ }
+}
diff --git a/SourceGen/VisualizationSet.cs b/SourceGen/VisualizationSet.cs
index 2355548..999d9db 100644
--- a/SourceGen/VisualizationSet.cs
+++ b/SourceGen/VisualizationSet.cs
@@ -132,7 +132,7 @@ namespace SourceGen {
if (vis.CachedImage != Visualization.BROKEN_IMAGE) {
continue;
}
- Debug.WriteLine("Vis needs refresh: " + vis.Tag);
+ //Debug.WriteLine("Vis needs refresh: " + vis.Tag);
if (iapp == null) {
// Prep the plugins on first need.
@@ -162,7 +162,7 @@ namespace SourceGen {
vis2d = null;
}
if (vis2d != null) {
- Debug.WriteLine(" Rendered thumbnail: " + vis.Tag);
+ //Debug.WriteLine(" Rendered thumbnail: " + vis.Tag);
vis.SetThumbnail(vis2d);
}
}
diff --git a/SourceGen/WpfGui/EditBitmapAnimation.xaml b/SourceGen/WpfGui/EditBitmapAnimation.xaml
new file mode 100644
index 0000000..96a50a2
--- /dev/null
+++ b/SourceGen/WpfGui/EditBitmapAnimation.xaml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SourceGen/WpfGui/EditBitmapAnimation.xaml.cs b/SourceGen/WpfGui/EditBitmapAnimation.xaml.cs
new file mode 100644
index 0000000..946d79a
--- /dev/null
+++ b/SourceGen/WpfGui/EditBitmapAnimation.xaml.cs
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2019 faddenSoft
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace SourceGen.WpfGui {
+ ///
+ /// Bitmap animation visualization editor.
+ ///
+ public partial class EditBitmapAnimation : Window, INotifyPropertyChanged {
+ ///
+ /// True if current contents represent a valid visualization animation. Determines
+ /// whether the OK button is enabled.
+ ///
+ public bool IsValid {
+ get { return mIsValid; }
+ set { mIsValid = value; OnPropertyChanged(); }
+ }
+ private bool mIsValid;
+
+ public ObservableCollection VisSourceItems { get; private set; } =
+ new ObservableCollection();
+
+ public ObservableCollection VisAnimItems { get; private set; } =
+ new ObservableCollection();
+
+ ///
+ /// Time between frames, in milliseconds.
+ ///
+ public int FrameDelayTimeMsec {
+ get { return mFrameDelayTimeMsec; }
+ set { mFrameDelayTimeMsec = value; OnPropertyChanged(); }
+ }
+ private int mFrameDelayTimeMsec;
+
+ // INotifyPropertyChanged implementation
+ public event PropertyChangedEventHandler PropertyChanged;
+ private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ public EditBitmapAnimation(Window owner) {
+ InitializeComponent();
+ Owner = owner;
+ DataContext = this;
+ }
+
+ private void OkButton_Click(object sender, RoutedEventArgs e) {
+
+ }
+
+ private void VisSourceGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
+
+ }
+
+ private void VisAnimGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
+
+ }
+ }
+}
diff --git a/SourceGen/WpfGui/EditVisualization.xaml.cs b/SourceGen/WpfGui/EditVisualization.xaml.cs
index 4622ba1..d506d8f 100644
--- a/SourceGen/WpfGui/EditVisualization.xaml.cs
+++ b/SourceGen/WpfGui/EditVisualization.xaml.cs
@@ -40,8 +40,8 @@ namespace SourceGen.WpfGui {
private DisasmProject mProject;
private Formatter mFormatter;
private int mSetOffset;
+ private SortedList mEditedList;
private Visualization mOrigVis;
- private string mOrigTag;
///
/// Identifier for last visualizer we used, for the benefit of "new".
@@ -173,7 +173,7 @@ namespace SourceGen.WpfGui {
/// Text formatter.
/// Visualization to edit, or null if this is new.
public EditVisualization(Window owner, DisasmProject proj, Formatter formatter,
- int setOffset, Visualization vis) {
+ int setOffset, SortedList editedList, Visualization vis) {
InitializeComponent();
Owner = owner;
DataContext = this;
@@ -181,8 +181,8 @@ namespace SourceGen.WpfGui {
mProject = proj;
mFormatter = formatter;
mSetOffset = setOffset;
+ mEditedList = editedList;
mOrigVis = vis;
- mOrigTag = (vis == null) ? string.Empty : vis.Tag;
mScriptSupport = new ScriptSupport(this);
mProject.PrepareScripts(mScriptSupport);
@@ -302,7 +302,7 @@ namespace SourceGen.WpfGui {
ReadOnlyDictionary valueDict = CreateVisGenParams();
string trimTag = Visualization.TrimAndValidateTag(TagString, out bool isTagValid);
Debug.Assert(isTagValid);
- NewVis = new Visualization(trimTag, item.VisDescriptor.Ident, valueDict);
+ NewVis = new Visualization(trimTag, item.VisDescriptor.Ident, valueDict, mOrigVis);
NewVis.CachedImage = (BitmapSource)previewImage.Source;
sLastVisIdent = NewVis.VisGenIdent;
@@ -465,15 +465,9 @@ namespace SourceGen.WpfGui {
string trimTag = Visualization.TrimAndValidateTag(TagString, out bool tagOk);
Visualization match = FindVisualizationByTag(trimTag);
- if (match != null && trimTag != mOrigTag) {
- // Another vis already has this tag.
- //
- // TODO: this is wrong. If I edit the set, edit a Vis, change it's tag, then
- // immediately edit it again, I can't change the tag back to what it originally
- // was, because the original version of the Vis is in the VisSet and I no longer
- // have a way to know that that Vis and this Vis are the same. To make this work
- // correctly we need to track renames, which I think we may want to do later on
- // for animations, so not dealing with this yet.
+ if (match != null && (mOrigVis == null || trimTag != mOrigVis.Tag)) {
+ // Another vis already has this tag. We're checking the edited list, so we'll
+ // be current with edits to this or other Visualizations in the same set.
tagOk = false;
}
if (!tagOk) {
@@ -485,12 +479,13 @@ namespace SourceGen.WpfGui {
}
///
- /// Finds a Visualization with a matching tag, searching across all sets.
+ /// Finds a Visualization with a matching tag, searching across all sets in the
+ /// edited list.
///
/// Tag to search for.
/// Matching Visualization, or null if not found.
private Visualization FindVisualizationByTag(string tag) {
- foreach (KeyValuePair kvp in mProject.VisualizationSets) {
+ foreach (KeyValuePair kvp in mEditedList) {
foreach (Visualization vis in kvp.Value) {
if (vis.Tag == tag) {
return vis;
diff --git a/SourceGen/WpfGui/EditVisualizationSet.xaml b/SourceGen/WpfGui/EditVisualizationSet.xaml
index 6c9ceec..3a06f8e 100644
--- a/SourceGen/WpfGui/EditVisualizationSet.xaml
+++ b/SourceGen/WpfGui/EditVisualizationSet.xaml
@@ -88,7 +88,9 @@ limitations under the License.
+ IsEnabled="{Binding HasVisPlugins}" Click="NewBitmapButton_Click"/>
+
diff --git a/SourceGen/WpfGui/EditVisualizationSet.xaml.cs b/SourceGen/WpfGui/EditVisualizationSet.xaml.cs
index 333c5a3..2403516 100644
--- a/SourceGen/WpfGui/EditVisualizationSet.xaml.cs
+++ b/SourceGen/WpfGui/EditVisualizationSet.xaml.cs
@@ -31,6 +31,9 @@ namespace SourceGen.WpfGui {
/// Visualization set editor.
///
public partial class EditVisualizationSet : Window, INotifyPropertyChanged {
+ ///
+ /// Modified visualization set. Only valid after OK is hit.
+ ///
public VisualizationSet NewVisSet { get; private set; }
private DisasmProject mProject;
@@ -139,17 +142,6 @@ namespace SourceGen.WpfGui {
}
}
- private VisualizationSet MakeVisSet() {
- if (VisualizationList.Count == 0) {
- return null;
- }
- VisualizationSet newSet = new VisualizationSet(VisualizationList.Count);
- foreach (Visualization vis in VisualizationList) {
- newSet.Add(vis);
- }
- return newSet;
- }
-
private void VisualizationList_SelectionChanged(object sender,
SelectionChangedEventArgs e) {
bool isItemSelected = (visualizationGrid.SelectedItem != null);
@@ -164,9 +156,9 @@ namespace SourceGen.WpfGui {
EditSelectedItem();
}
- private void NewButton_Click(object sender, RoutedEventArgs e) {
+ private void NewBitmapButton_Click(object sender, RoutedEventArgs e) {
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
- null);
+ CreateEditedSetList(), null);
if (dlg.ShowDialog() != true) {
return;
}
@@ -174,19 +166,27 @@ namespace SourceGen.WpfGui {
visualizationGrid.SelectedIndex = VisualizationList.Count - 1;
}
+ private void NewBitmapAnimationButton_Click(object sender, RoutedEventArgs e) {
+ EditBitmapAnimation dlg = new EditBitmapAnimation(this);
+ if (dlg.ShowDialog() != true) {
+ return;
+ }
+ // TODO(xyzzy)
+ }
+
private void EditButton_Click(object sender, RoutedEventArgs e) {
EditSelectedItem();
}
private void EditSelectedItem() {
if (!IsEditEnabled) {
- // can happen on a double-click
+ // can get called here by a double-click
return;
}
Visualization item = (Visualization)visualizationGrid.SelectedItem;
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
- item);
+ CreateEditedSetList(), item);
if (dlg.ShowDialog() != true) {
return;
}
@@ -228,5 +228,42 @@ namespace SourceGen.WpfGui {
VisualizationList.Insert(index + 1, item);
visualizationGrid.SelectedIndex = index + 1;
}
+
+ ///
+ /// Creates a VisualizationSet from the current list of Visualizations.
+ ///
+ /// New VisualizationSet.
+ private VisualizationSet MakeVisSet() {
+ VisualizationSet newSet = new VisualizationSet(VisualizationList.Count);
+ foreach (Visualization vis in VisualizationList) {
+ newSet.Add(vis);
+ }
+ return newSet;
+ }
+
+ ///
+ /// Generates a list of VisualizationSet references. This is the list from the
+ /// DisasmProject, but with the set we're editing added or substituted.
+ ///
+ ///
+ /// The editors sometimes need access to the full collection of Visualization objects,
+ /// such as when testing a tag for uniqueness or getting a list of all bitmap
+ /// frames for an animation. The editor needs access to recent edits that have not
+ /// been pushed to the project yet.
+ ///
+ /// List of VisualizationSet.
+ private SortedList CreateEditedSetList() {
+ SortedList mixList =
+ new SortedList(mProject.VisualizationSets.Count);
+
+ mixList[mOffset] = MakeVisSet();
+ foreach (KeyValuePair kvp in mProject.VisualizationSets) {
+ // Skip the entry for mOffset (if it exists).
+ if (kvp.Key != mOffset) {
+ mixList[kvp.Key] = kvp.Value;
+ }
+ }
+ return mixList;
+ }
}
}