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 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild: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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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; + } } }