diff --git a/CommonUtil/Container.cs b/CommonUtil/Container.cs index 8370112..d297690 100644 --- a/CommonUtil/Container.cs +++ b/CommonUtil/Container.cs @@ -39,10 +39,13 @@ namespace CommonUtil { /// /// Compares two Dictionaries to see if their contents are equal. Key and value types - /// must have correctly-implemented equality checks. + /// must have correctly-implemented equality checks. (I contend this works incorrectly + /// for float -- 5.0f is equal to the integer 5.) /// /// /// https://stackoverflow.com/q/3804367/294248 + /// + /// TODO: make this work right for float/int comparisons /// /// Dictionary key type. /// Dictionary value type. diff --git a/PluginCommon/Interfaces.cs b/PluginCommon/Interfaces.cs index 3f9f655..9a2a3a4 100644 --- a/PluginCommon/Interfaces.cs +++ b/PluginCommon/Interfaces.cs @@ -29,15 +29,20 @@ namespace PluginCommon { string Identifier { get; } /// - /// Prepares the plugin for action. Called at the start of every code analysis pass. - /// + /// Prepares the plugin for action. Called at the start of every code analysis pass + /// and when generating visualization images. + /// /// In the current implementation, the file data will be the same every time, /// because it doesn't change after the project is opened. However, this could - /// change if we add a descramble feature. + /// change if we add a descramble feature. The IApplication and AddressTranslate + /// references will likely change between invocations. + /// + /// This may be called even when the plugin won't be asked to do anything. Avoid + /// performing expensive operations here. /// /// Reference to application interface. /// 65xx code and data. - /// Mapping between offsets and addresses. + /// Mapping between offsets and addresses. void Prepare(IApplication appRef, byte[] fileData, AddressTranslate addrTrans); /// @@ -236,12 +241,13 @@ namespace PluginCommon { /// /// Interfaces provided by the application for use by plugins. An IApplication instance - /// is passed to the plugin as an argument Prepare(). + /// is passed to the plugin as an argument to Prepare(). /// public interface IApplication { /// /// Sends a debug message to the application. This can be useful when debugging scripts. - /// (Use DEBUG > Show Analyzer Output to view it.) + /// (For example, DEBUG > Show Analyzer Output shows output generated while performing + /// code analysis.) /// /// Message to send. void DebugLog(string msg); diff --git a/SourceGen/App.xaml b/SourceGen/App.xaml index debd7bd..0066d93 100644 --- a/SourceGen/App.xaml +++ b/SourceGen/App.xaml @@ -22,6 +22,18 @@ limitations under the License. Consolas + + + + + + + + + + + + diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 77c065c..91ff6dd 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -2204,11 +2204,11 @@ namespace SourceGen { } break; case UndoableChange.ChangeType.SetProjectProperties: { - bool needExternalFileReload = !CommonUtil.Container.StringListEquals( + bool needPlatformSymReload = !CommonUtil.Container.StringListEquals( ((ProjectProperties)oldValue).PlatformSymbolFileIdentifiers, ((ProjectProperties)newValue).PlatformSymbolFileIdentifiers, null /*StringComparer.InvariantCulture*/); - needExternalFileReload |= !CommonUtil.Container.StringListEquals( + bool needExtScriptReload = !CommonUtil.Container.StringListEquals( ((ProjectProperties)oldValue).ExtensionScriptFileIdentifiers, ((ProjectProperties)newValue).ExtensionScriptFileIdentifiers, null); @@ -2226,7 +2226,7 @@ namespace SourceGen { Debug.WriteLine("Replacing CPU def object"); UpdateCpuDef(); - if (needExternalFileReload) { + if (needPlatformSymReload || needExtScriptReload) { string errMsgs = LoadExternalFiles(); // TODO(someday): if the plugin failed to compile, we will have @@ -2237,6 +2237,14 @@ namespace SourceGen { // "reload all external files and plugins" command, which might // run through here.) } + if (needExtScriptReload) { + // Clear all the Visualization thumbnails. We don't do GUI + // stuff in here, so this just sets a flag. + foreach (KeyValuePair kvp + in VisualizationSets) { + kvp.Value.RefreshNeeded(); + } + } } // ignore affectedOffsets Debug.Assert(uc.ReanalysisRequired == diff --git a/SourceGen/DisplayList.cs b/SourceGen/DisplayList.cs index d56c96e..06eb3da 100644 --- a/SourceGen/DisplayList.cs +++ b/SourceGen/DisplayList.cs @@ -20,6 +20,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.ComponentModel; using System.Windows.Media; +using System.Windows.Media.Imaging; namespace SourceGen { /// @@ -354,10 +355,13 @@ namespace SourceGen { public string Operand { get; private set; } public string Comment { get; private set; } public bool IsLongComment { get; private set; } - public bool IsVisualizationSet { get; private set; } + public bool HasBackgroundColor { get; private set; } public Brush BackgroundBrush { get; private set; } + public bool IsVisualizationSet { get; private set; } + public Visualization[] VisualizationSet { get; private set; } + // Set to true if we want to highlight the address and label fields. public bool HasAddrLabelHighlight { get; private set; } @@ -452,8 +456,9 @@ namespace SourceGen { public static FormattedParts CreateVisualizationSet(VisualizationSet visSet) { FormattedParts parts = new FormattedParts(); if (visSet.Count == 0) { - // not expected - parts.Comment = "!EMPTY!"; + // should not happen + parts.Comment = "!EMPTY VSET!"; + parts.IsLongComment = true; } else { string fmt; if (visSet.Count == 1) { @@ -461,10 +466,10 @@ namespace SourceGen { } else { fmt = Res.Strings.VIS_SET_MULTIPLE_FMT; } - parts.Comment = string.Format(fmt, visSet[0].Tag, visSet.Count - 1); + parts.Comment = string.Format(fmt, "Bitmap", visSet[0].Tag, visSet.Count - 1); + parts.VisualizationSet = visSet.ToArray(); + parts.IsVisualizationSet = true; } - // TODO(xyzzy): show image thumbnails - parts.IsVisualizationSet = true; return parts; } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index ca43677..f1bcab3 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -983,6 +983,10 @@ namespace SourceGen { mReanalysisTimer.StartTask("Generate DisplayList"); CodeLineList.GenerateAll(); mReanalysisTimer.EndTask("Generate DisplayList"); + + mReanalysisTimer.StartTask("Refresh Visualization thumbnails"); + VisualizationSet.RefreshAllThumbnails(mProject); + mReanalysisTimer.EndTask("Refresh Visualization thumbnails"); } #endregion Project management diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml index bffbc17..ff33f3a 100644 --- a/SourceGen/Res/Strings.xaml +++ b/SourceGen/Res/Strings.xaml @@ -175,6 +175,6 @@ limitations under the License. [new project] *READ-ONLY* [unset] - Vis: {0} (+{1} more) - Vis: {0} + {0}: {1} (+{2} more) + {0}: {1} \ No newline at end of file diff --git a/SourceGen/RuntimeData/Apple/VisHiRes.cs b/SourceGen/RuntimeData/Apple/VisHiRes.cs index 6b01b49..b9434d8 100644 --- a/SourceGen/RuntimeData/Apple/VisHiRes.cs +++ b/SourceGen/RuntimeData/Apple/VisHiRes.cs @@ -121,8 +121,8 @@ namespace RuntimeData.Apple { bool isColor, isFirstOdd; offset = Util.GetFromObjDict(parms, P_OFFSET, 0); - byteWidth = Util.GetFromObjDict(parms, P_BYTE_WIDTH, 0); // width ignoring colStride - height = Util.GetFromObjDict(parms, P_HEIGHT, 0); + byteWidth = Util.GetFromObjDict(parms, P_BYTE_WIDTH, 1); // width ignoring colStride + height = Util.GetFromObjDict(parms, P_HEIGHT, 1); colStride = Util.GetFromObjDict(parms, P_COL_STRIDE, 0); rowStride = Util.GetFromObjDict(parms, P_ROW_STRIDE, 0); isColor = Util.GetFromObjDict(parms, P_IS_COLOR, true); diff --git a/SourceGen/Visualization.cs b/SourceGen/Visualization.cs index 2ba1693..1653a37 100644 --- a/SourceGen/Visualization.cs +++ b/SourceGen/Visualization.cs @@ -18,10 +18,17 @@ using System.Collections.Generic; using System.Text; using System.Windows.Media; using System.Windows.Media.Imaging; + using CommonUtil; using PluginCommon; namespace SourceGen { + /// + /// Graphical visualization object. Useful for displaying 2D bitmaps and 3D objects. + /// + /// Treat this as generally immutable, i.e. don't modify VisGenParams. The CachedImage + /// field is mutable. + /// public class Visualization { /// /// Unique user-specified tag. Contents are arbitrary, but may not be empty. @@ -39,13 +46,19 @@ namespace SourceGen { public Dictionary VisGenParams { get; private set; } /// - /// Cached reference to thumbnail. + /// Cached reference to 2D image, useful for thumbnails. Not serialized. /// /// /// Because the underlying data never changes, we only need to regenerate the - /// thumbnail if the set of active plugins changes. + /// image if the set of active plugins changes. /// - private BitmapSource Thumbnail { get; set; } // TODO - 64x64(?) bitmap + public BitmapSource CachedImage { get; set; } + + /// + /// Image to show when things are broken. + /// + public static readonly BitmapImage BROKEN_IMAGE = + new BitmapImage(new Uri("pack://application:,,,/Res/RedX.png")); /// @@ -59,6 +72,19 @@ namespace SourceGen { Tag = tag; VisGenIdent = visGenIdent; VisGenParams = visGenParams; + CachedImage = BROKEN_IMAGE; + } + + /// + /// Updates the cached thumbnail image. + /// + /// Visualization, or null to clear the thumbnail. + public void SetThumbnail(IVisualization2d vis2d) { + if (vis2d == null) { + CachedImage = BROKEN_IMAGE; + } else { + CachedImage = ConvertToBitmapSource(vis2d); + } } /// @@ -110,31 +136,6 @@ namespace SourceGen { return image; } - /// - /// Finds a plugin that provides the named visualization generator. - /// - /// Project with script manager. - /// Visualization generator identifier. - /// A plugin that matches, or null if none found. - public static IPlugin_Visualizer FindPluginByVisGenIdent(DisasmProject proj, - string visGenIdent, out VisDescr visDescr) { - List plugins = proj.GetActivePlugins(); - foreach (IPlugin chkPlug in plugins) { - if (!(chkPlug is IPlugin_Visualizer)) { - continue; - } - IPlugin_Visualizer vplug = (IPlugin_Visualizer)chkPlug; - foreach (VisDescr descr in vplug.GetVisGenDescrs()) { - if (descr.Ident == visGenIdent) { - visDescr = descr; - return vplug; - } - } - } - visDescr = null; - return null; - } - public override string ToString() { return "[Vis: " + Tag + " (" + VisGenIdent + ") count=" + VisGenParams.Count + "]"; @@ -147,8 +148,8 @@ namespace SourceGen { if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) { return false; // one is null } - // All fields must be equal. - if (a.Tag != b.Tag || a.VisGenIdent != b.VisGenIdent || a.Thumbnail != b.Thumbnail) { + // All fields must be equal (but we ignore CachedImage). + if (a.Tag != b.Tag || a.VisGenIdent != b.VisGenIdent) { return false; } // Compare the vis gen parameter lists. @@ -167,7 +168,7 @@ namespace SourceGen { return obj is Visualization && this == (Visualization)obj; } public override int GetHashCode() { - // TODO(maybe): hash code should include up VisGenParams items + // TODO(maybe): hash code should factor in VisGenParams items return Tag.GetHashCode() ^ VisGenIdent.GetHashCode() ^ VisGenParams.Count; } } diff --git a/SourceGen/VisualizationSet.cs b/SourceGen/VisualizationSet.cs index 34068bc..0dfeaf1 100644 --- a/SourceGen/VisualizationSet.cs +++ b/SourceGen/VisualizationSet.cs @@ -16,13 +16,16 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; + +using PluginCommon; namespace SourceGen { /// /// Ordered list of visualization objects. /// /// - /// Right now the only thing separating this from a plain List<> is the operator== stuff. + /// There's not much separating this from a plain List<>, except perhaps the operator== stuff. /// public class VisualizationSet : IEnumerable { /// @@ -74,6 +77,122 @@ namespace SourceGen { mList.Remove(vis); } + public Visualization[] ToArray() { + Visualization[] arr = new Visualization[mList.Count]; + for (int i = 0; i < mList.Count; i++) { + arr[i] = mList[i]; + } + return arr; + } + + #region Image generation + + private class ScriptSupport : MarshalByRefObject, PluginCommon.IApplication { + public ScriptSupport() { } + + public void DebugLog(string msg) { + Debug.WriteLine("Vis plugin: " + msg); + } + + public bool SetOperandFormat(int offset, DataSubType subType, string label) { + throw new InvalidOperationException(); + } + public bool SetInlineDataFormat(int offset, int length, DataType type, + DataSubType subType, string label) { + throw new InvalidOperationException(); + } + } + + /// + /// Informs all list elements that a refresh is needed. Call this when the set of active + /// plugins changes. The actual refresh will happen later. + /// + public void RefreshNeeded() { + foreach (Visualization vis in mList) { + vis.SetThumbnail(null); + } + } + + /// + /// Attempts to refresh broken thumbnails across all visualization sets in the project. + /// + /// Project reference. + public static void RefreshAllThumbnails(DisasmProject project) { + ScriptSupport iapp = null; + List plugins = null; + + foreach (KeyValuePair kvp in project.VisualizationSets) { + VisualizationSet visSet = kvp.Value; + foreach (Visualization vis in visSet) { + if (vis.CachedImage != Visualization.BROKEN_IMAGE) { + continue; + } + Debug.WriteLine("Vis needs refresh: " + vis.Tag); + + if (plugins == null) { + plugins = project.GetActivePlugins(); + } + IPlugin_Visualizer vplug = FindPluginByVisGenIdent(plugins, + vis.VisGenIdent, out VisDescr visDescr); + if (vplug == null) { + Debug.WriteLine("Unable to referesh " + vis.Tag + ": plugin not found"); + continue; + } + + if (iapp == null) { + // Prep the plugins on first need. + iapp = new ScriptSupport(); + project.PrepareScripts(iapp); + } + + IVisualization2d vis2d; + try { + vis2d = vplug.Generate2d(visDescr, vis.VisGenParams); + if (vis2d == null) { + Debug.WriteLine("Vis generator returned null"); + } + } catch (Exception ex) { + Debug.WriteLine("Vis generation failed: " + ex); + vis2d = null; + } + if (vis2d != null) { + Debug.WriteLine(" Rendered thumbnail: " + vis.Tag); + vis.SetThumbnail(vis2d); + } + } + } + + if (iapp != null) { + project.UnprepareScripts(); + } + } + + /// + /// Finds a plugin that provides the named visualization generator. + /// + /// List of plugins, from project ScriptManager. + /// Visualization generator identifier. + /// A plugin that matches, or null if none found. + private static IPlugin_Visualizer FindPluginByVisGenIdent(List plugins, + string visGenIdent, out VisDescr visDescr) { + foreach (IPlugin chkPlug in plugins) { + if (!(chkPlug is IPlugin_Visualizer)) { + continue; + } + IPlugin_Visualizer vplug = (IPlugin_Visualizer)chkPlug; + foreach (VisDescr descr in vplug.GetVisGenDescrs()) { + if (descr.Ident == visGenIdent) { + visDescr = descr; + return vplug; + } + } + } + visDescr = null; + return null; + } + + #endregion Image generation + public override string ToString() { return "[VS: " + mList.Count + " items]"; diff --git a/SourceGen/WpfGui/CodeListItemStyle.xaml b/SourceGen/WpfGui/CodeListItemStyle.xaml index cd32ada..4185a6c 100644 --- a/SourceGen/WpfGui/CodeListItemStyle.xaml +++ b/SourceGen/WpfGui/CodeListItemStyle.xaml @@ -92,16 +92,28 @@ See also https://github.com/fadden/DisasmUiTest - + -