From e7fccfda0358169f33febbb8d10c0af8357cfe58 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Tue, 26 Nov 2019 18:54:42 -0800 Subject: [PATCH] More progress on visualization Got parameter in/out working in EditVisualization dialog. Did some rearranging in PluginCommon interfaces and data structures. Still doesn't do anything useful. --- CommonUtil/Container.cs | 39 +++ PluginCommon/Interfaces.cs | 63 +++- SourceGen/DisasmProject.cs | 5 +- SourceGen/RuntimeData/Apple/VisHiRes.cs | 85 +++--- SourceGen/Sandbox/ScriptManager.cs | 20 +- SourceGen/Visualization.cs | 56 ++-- SourceGen/VisualizationSet.cs | 5 +- SourceGen/WpfGui/EditAppSettings.xaml.cs | 3 + SourceGen/WpfGui/EditLvTableLocation.xaml.cs | 4 +- SourceGen/WpfGui/EditVisualization.xaml | 78 +++-- SourceGen/WpfGui/EditVisualization.xaml.cs | 280 +++++++++++++++--- SourceGen/WpfGui/EditVisualizationSet.xaml.cs | 8 +- 12 files changed, 510 insertions(+), 136 deletions(-) diff --git a/CommonUtil/Container.cs b/CommonUtil/Container.cs index 9974fa5..8370112 100644 --- a/CommonUtil/Container.cs +++ b/CommonUtil/Container.cs @@ -36,5 +36,44 @@ namespace CommonUtil { } return Enumerable.SequenceEqual(l1, l2, comparer); } + + /// + /// Compares two Dictionaries to see if their contents are equal. Key and value types + /// must have correctly-implemented equality checks. + /// + /// + /// https://stackoverflow.com/q/3804367/294248 + /// + /// Dictionary key type. + /// Dictionary value type. + /// Dictionary #1. + /// Dictionary #2. + /// True if equal, false if not. + public static bool CompareDicts( + Dictionary dict1, Dictionary dict2) { + if (dict1 == dict2) { + return true; + } + if (dict1 == null || dict2 == null) { + return false; + } + if (dict1.Count != dict2.Count) { + return false; + } + +#if false + var valueComparer = EqualityComparer.Default; + + foreach (var kvp in dict1) { + TValue value2; + if (!dict2.TryGetValue(kvp.Key, out value2)) return false; + if (!valueComparer.Equals(kvp.Value, value2)) return false; + } + return true; +#else + // Check to see if there are any elements in the first that are not in the second. + return !dict1.Except(dict2).Any(); +#endif + } } } diff --git a/PluginCommon/Interfaces.cs b/PluginCommon/Interfaces.cs index 724f234..ac3aecb 100644 --- a/PluginCommon/Interfaces.cs +++ b/PluginCommon/Interfaces.cs @@ -124,14 +124,58 @@ namespace PluginCommon { } /// - /// Extension scripts that want to generate 2D visualizations must implement this interface. + /// Extension scripts that want to generate visualizations must implement this interface. /// - public interface IPlugin_Visualizer2d { - string[] GetVisGenNames(); + public interface IPlugin_Visualizer { + /// + /// Retrieves a list of descriptors for visualization generators implemented by this + /// plugin. The caller must not modify the contents. + /// + VisDescr[] GetVisGenDescrs(); - List GetVisGenParams(string name); + /// + /// Executes the specified visualization generator with the supplied parameters. + /// + /// VisGen identifier. + /// Parameter set. + /// 2D visualization object reference. + IVisualization2d Generate2d(VisDescr descr, Dictionary parms); + } - IVisualization2d ExecuteVisGen(string name, Dictionary parms); + [Serializable] + public class VisDescr { + /// + /// Unique identifier. This is stored in the project file. + /// + public string Ident { get; private set; } + + /// + /// Human-readable string describing the visualizer. Used for combo boxes and + /// other UI elements. + /// + public string UiName { get; private set; } + + public enum VisType { + Unknown = 0, + Bitmap, // 2D bitmap + } + + /// + /// Visualization type. + /// + public VisType VisualizationType { get; private set; } + + /// + /// Visualization parameter descriptors. + /// + public VisParamDescr[] VisParamDescrs { get; private set; } + + public VisDescr(string ident, string uiName, VisType type, VisParamDescr[] descrs) { + Ident = ident; + UiName = uiName; + VisualizationType = type; + VisParamDescrs = descrs; + } } /// @@ -146,6 +190,10 @@ namespace PluginCommon { public string UiLabel { get; private set; } public string Name { get; private set; } public Type CsType { get; private set; } + + // Min/Max values for ints and floats. Could also be length for strings. + // NOTE: ideally we'd provide a "verify" function that tested individual fields and + // could also see other fields, e.g. to confirm that Stride >= Width. public object Min { get; private set; } public object Max { get; private set; } public SpecialMode Special { get; private set; } @@ -164,12 +212,15 @@ namespace PluginCommon { } /// - /// Rendered 2D visualization object. + /// Rendered 2D visualization. The object represents the "raw" form of the data, + /// without scaling or filtering. /// public interface IVisualization2d { int GetWidth(); int GetHeight(); int GetPixel(int x, int y); // returns ARGB value + + // TODO(maybe): pixel aspect ratio } /// diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index d3f7aeb..35c3fb6 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -2443,11 +2443,10 @@ namespace SourceGen { return bestSym; } - public PluginCommon.IPlugin GetMatchingScript(ScriptManager.CheckMatch check) { - return mScriptManager.GetMatchingScript(check); + public List GetActivePlugins() { + return mScriptManager.GetActivePlugins(); } - /// /// For debugging purposes, get some information about the currently loaded /// extension scripts. diff --git a/SourceGen/RuntimeData/Apple/VisHiRes.cs b/SourceGen/RuntimeData/Apple/VisHiRes.cs index 7b911ff..bc2d205 100644 --- a/SourceGen/RuntimeData/Apple/VisHiRes.cs +++ b/SourceGen/RuntimeData/Apple/VisHiRes.cs @@ -1,5 +1,5 @@ /* - * Copyright 2018 faddenSoft + * 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. @@ -20,56 +20,69 @@ using System.Text; using PluginCommon; namespace RuntimeData.Apple { - public class VisHiRes : MarshalByRefObject, IPlugin, IPlugin_Visualizer2d { - // Visualization identifiers; DO NOT change. - private const string VIS_GEN_BITMAP = "apple2-hi-res-bitmap"; - + public class VisHiRes : MarshalByRefObject, IPlugin, IPlugin_Visualizer { public string Identifier { get { return "Apple II Hi-Res Graphic Visualizer"; } } - private IApplication mAppRef; private byte[] mFileData; private AddressTranslate mAddrTrans; + // Visualization identifiers; DO NOT change or projects will break. + private const string VIS_GEN_BITMAP = "apple2-hi-res-bitmap"; + private const string VIS_GEN_MULTI_MAP = "apple2-hi-res-multi-map"; + + // Visualization descriptors. + private VisDescr[] mDescriptors = new VisDescr[] { + new VisDescr(VIS_GEN_BITMAP, "Apple II Hi-Res Bitmap", VisDescr.VisType.Bitmap, + new VisParamDescr[] { + new VisParamDescr("File offset (hex)", + "offset", typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, + 0x2000), + new VisParamDescr("Width (in bytes)", + "byteWidth", typeof(int), 1, 40, 0, 1), + new VisParamDescr("Height", + "height", typeof(int), 1, 192, 0, 1), + new VisParamDescr("Column stride (bytes)", + "colStride", typeof(int), 0, 256, 0, 0), + new VisParamDescr("Row stride (bytes)", + "rowStride", typeof(int), 0, 256, 0, 0), + new VisParamDescr("Color", + "color", typeof(bool), 0, 0, 0, true), + new VisParamDescr("First byte odd", + "firstOdd", typeof(bool), 0, 0, 0, false), + new VisParamDescr("Test Float", + "floaty", typeof(float), -5.0f, 5.0f, 0, 0.1f), + }), + new VisDescr(VIS_GEN_MULTI_MAP, "Apple II Hi-Res Multi-Map", VisDescr.VisType.Bitmap, + new VisParamDescr[] { + new VisParamDescr("File offset (hex)", + "offset", typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, + 0x1000), + new VisParamDescr("Item width (in bytes)", + "itemByteWidth", typeof(int), 1, 40, 0, 1), + new VisParamDescr("Item height", + "itemHeight", typeof(int), 8, 192, 0, 1), + new VisParamDescr("Number of items", + "count", typeof(int), 1, 256, 0, 1), + }), + }; + + public void Prepare(IApplication appRef, byte[] fileData, AddressTranslate addrTrans) { mAppRef = appRef; mFileData = fileData; mAddrTrans = addrTrans; } - public string[] GetVisGenNames() { - return new string[] { - VIS_GEN_BITMAP, - }; + // IPlugin_Visualizer + public VisDescr[] GetVisGenDescrs() { + return mDescriptors; } - public List GetVisGenParams(string name) { - List parms = new List(); - - switch (name) { - case VIS_GEN_BITMAP: - parms.Add(new VisParamDescr("File Offset", - "offset", typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, - 0x2000)); - parms.Add(new VisParamDescr("Width (bytes)", - "byteWidth", typeof(int), 1, 40, 0, 1)); - parms.Add(new VisParamDescr("Height", - "height", typeof(int), 1, 192, 0, 1)); - parms.Add(new VisParamDescr("Color", - "color", typeof(bool), 0, 0, 0, true)); - parms.Add(new VisParamDescr("Test Float", - "floaty", typeof(float), -5.0f, 5.0f, 0, 0.1f)); - break; - default: - parms = null; - break; - } - - return parms; - } - - public IVisualization2d ExecuteVisGen(string name, Dictionary parms) { + // IPlugin_Visualizer + public IVisualization2d Generate2d(VisDescr descr, + Dictionary parms) { throw new NotImplementedException(); } } diff --git a/SourceGen/Sandbox/ScriptManager.cs b/SourceGen/Sandbox/ScriptManager.cs index c1a87b9..9c29df7 100644 --- a/SourceGen/Sandbox/ScriptManager.cs +++ b/SourceGen/Sandbox/ScriptManager.cs @@ -275,6 +275,22 @@ namespace SourceGen.Sandbox { return null; } + /// + /// Returns a list of loaded plugins. Callers should not retain this list, as the + /// set can change due to user activity. + /// + public List GetActivePlugins() { + if (DomainMgr == null) { + List plist = new List(); + foreach (KeyValuePair kvp in mActivePlugins) { + plist.Add(kvp.Value); + } + return plist; + } else { + return DomainMgr.PluginMgr.GetActivePlugins(); + } + } + /// /// For debugging purposes, get some information about the currently loaded /// extension scripts. @@ -321,8 +337,8 @@ namespace SourceGen.Sandbox { if (plugin is PluginCommon.IPlugin_InlineBrk) { sb.Append(" InlineBrk"); } - if (plugin is PluginCommon.IPlugin_Visualizer2d) { - sb.Append(" Visualizer2d"); + if (plugin is PluginCommon.IPlugin_Visualizer) { + sb.Append(" Visualizer"); } sb.Append("\r\n"); } diff --git a/SourceGen/Visualization.cs b/SourceGen/Visualization.cs index bf451a1..aaed483 100644 --- a/SourceGen/Visualization.cs +++ b/SourceGen/Visualization.cs @@ -17,19 +17,20 @@ using System; using System.Collections.Generic; using System.Text; +using CommonUtil; using PluginCommon; namespace SourceGen { public class Visualization { /// - /// Unique tag. Contents are arbitrary, but may not be empty. + /// Unique user-specified tag. Contents are arbitrary, but may not be empty. /// public string Tag { get; private set; } /// /// Name of visualization generator (extension script function). /// - public string VisGenName { get; private set; } + public string VisGenIdent { get; private set; } /// /// Parameters passed to the visualization generator. @@ -43,12 +44,12 @@ namespace SourceGen { /// Constructor. /// /// - /// + /// /// - public Visualization(string tag, string visGenName, + public Visualization(string tag, string visGenIdent, Dictionary visGenParams) { Tag = tag; - VisGenName = visGenName; + VisGenIdent = visGenIdent; VisGenParams = visGenParams; } @@ -57,29 +58,30 @@ namespace SourceGen { /// Finds a plugin that provides the named visualization generator. /// /// Project with script manager. - /// Visualization generator name. + /// Visualization generator identifier. /// A plugin that matches, or null if none found. - public static IPlugin_Visualizer2d FindPluginByVisGenName(DisasmProject proj, - string visGenName) { - Sandbox.ScriptManager.CheckMatch check = (chkPlug) => { - if (!(chkPlug is IPlugin_Visualizer2d)) { - return false; + 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_Visualizer2d vplug = (IPlugin_Visualizer2d)chkPlug; - string[] names = vplug.GetVisGenNames(); - foreach (string name in names) { - if (name == visGenName) { - return true; + IPlugin_Visualizer vplug = (IPlugin_Visualizer)chkPlug; + foreach (VisDescr descr in vplug.GetVisGenDescrs()) { + if (descr.Ident == visGenIdent) { + visDescr = descr; + return vplug; } } - return false; - }; - return (IPlugin_Visualizer2d)proj.GetMatchingScript(check); + } + visDescr = null; + return null; } public override string ToString() { - return "[Vis: " + Tag + " (" + VisGenName + ")]"; + return "[Vis: " + Tag + " (" + VisGenIdent + ") count=" + VisGenParams.Count + "]"; } public static bool operator ==(Visualization a, Visualization b) { @@ -90,10 +92,17 @@ namespace SourceGen { return false; // one is null } // All fields must be equal. - if (a.Tag != b.Tag || a.VisGenName != b.VisGenName || a.Thumbnail != b.Thumbnail) { + if (a.Tag != b.Tag || a.VisGenIdent != b.VisGenIdent || a.Thumbnail != b.Thumbnail) { return false; } - return a.VisGenParams != b.VisGenParams; // TODO(xyzzy): should be item-by-item + // Compare the vis gen parameter lists. + if (a.VisGenParams == b.VisGenParams) { + return true; + } + if (a.VisGenParams.Count != b.VisGenParams.Count) { + return false; + } + return Container.CompareDicts(a.VisGenParams, b.VisGenParams); } public static bool operator !=(Visualization a, Visualization b) { return !(a == b); @@ -102,7 +111,8 @@ namespace SourceGen { return obj is Visualization && this == (Visualization)obj; } public override int GetHashCode() { - return Tag.GetHashCode() ^ VisGenName.GetHashCode() ^ VisGenParams.Count; + // TODO(maybe): hash code should include up VisGenParams items + return Tag.GetHashCode() ^ VisGenIdent.GetHashCode() ^ VisGenParams.Count; } } } diff --git a/SourceGen/VisualizationSet.cs b/SourceGen/VisualizationSet.cs index 50e9f72..78f82e7 100644 --- a/SourceGen/VisualizationSet.cs +++ b/SourceGen/VisualizationSet.cs @@ -19,9 +19,12 @@ using System.Collections.Generic; using System.Text; namespace SourceGen { + /// + /// Ordered list of visualization objects. + /// public class VisualizationSet : IEnumerable { /// - /// Ordered list of visualization objects. + /// Object list. /// private List mList; diff --git a/SourceGen/WpfGui/EditAppSettings.xaml.cs b/SourceGen/WpfGui/EditAppSettings.xaml.cs index 3864f3a..e0bf0ec 100644 --- a/SourceGen/WpfGui/EditAppSettings.xaml.cs +++ b/SourceGen/WpfGui/EditAppSettings.xaml.cs @@ -89,6 +89,9 @@ namespace SourceGen.WpfGui { /// private AssemblerInfo.Id mInitialAsmId; + /// + /// List of assemblers, for combo boxes. + /// public List AssemblerList { get; private set; } diff --git a/SourceGen/WpfGui/EditLvTableLocation.xaml.cs b/SourceGen/WpfGui/EditLvTableLocation.xaml.cs index 046c001..67972a7 100644 --- a/SourceGen/WpfGui/EditLvTableLocation.xaml.cs +++ b/SourceGen/WpfGui/EditLvTableLocation.xaml.cs @@ -47,8 +47,8 @@ namespace SourceGen.WpfGui { /// private int mCurrentOffset; - // Dialog label text color, saved off at dialog load time. - private Brush mDefaultLabelColor = Brushes.Black; + // Dialog label text color. + private Brush mDefaultLabelColor = SystemColors.WindowTextBrush; private Brush mErrorLabelColor = Brushes.Red; public string OffsetStr { diff --git a/SourceGen/WpfGui/EditVisualization.xaml b/SourceGen/WpfGui/EditVisualization.xaml index 54483d6..7ac24e0 100644 --- a/SourceGen/WpfGui/EditVisualization.xaml +++ b/SourceGen/WpfGui/EditVisualization.xaml @@ -30,29 +30,53 @@ limitations under the License. - - - - - + + + + + + + + + + - - - + + + + + + + - - + + - - - + + + + + + + - - + + public partial class EditVisualization : Window, INotifyPropertyChanged { + private const int MIN_TRIMMED_TAG_LEN = 2; + + /// + /// Dialog result. + /// + public Visualization NewVis { get; private set; } + private DisasmProject mProject; private Formatter mFormatter; private Visualization mOrigVis; + private Brush mDefaultLabelColor = SystemColors.WindowTextBrush; + private Brush mErrorLabelColor = Brushes.Red; + + + /// + /// True if all properties are in valid ranges. Determines whether the OK button + /// is enabled. + /// + public bool IsValid { + get { return mIsValid; } + set { mIsValid = value; OnPropertyChanged(); } + } + private bool mIsValid; + + /// + /// Visualization tag. + /// public string TagString { get { return mTagString; } set { mTagString = value; OnPropertyChanged(); } } private string mTagString; - public IList ParameterList { - get { return mParameterList; } + public Brush TagLabelBrush { + get { return mTagLabelBrush; } + set { mTagLabelBrush = value; OnPropertyChanged(); } } - private List mParameterList; + private Brush mTagLabelBrush; + + /// + /// ItemsSource for the ItemsControl with the generated parameter controls. + /// + public ObservableCollection ParameterList { get; private set; } = + new ObservableCollection(); + + /// + /// List of visualizers, for combo box. + /// + public List VisualizationList { get; private set; } // INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; @@ -54,6 +91,14 @@ namespace SourceGen.WpfGui { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } + + /// + /// Constructor. + /// + /// Owner window. + /// Project reference. + /// Text formatter. + /// Visualization to edit, or null if this is new. public EditVisualization(Window owner, DisasmProject proj, Formatter formatter, Visualization vis) { InitializeComponent(); @@ -64,12 +109,28 @@ namespace SourceGen.WpfGui { mFormatter = formatter; mOrigVis = vis; - // TODO: configure ComboBox from vis arg if non-null, then use current - // combo box selection, updating in selchange event - string visGenName = "apple2-hi-res-bitmap"; + if (vis != null) { + TagString = vis.Tag; + } - mParameterList = new List(); - GenerateParamControls(visGenName); + int visSelection = 0; + VisualizationList = new List(); + 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 (vis != null && vis.VisGenIdent == descr.Ident) { + visSelection = VisualizationList.Count; + } + VisualizationList.Add(descr); + } + } + + // Set the selection. This should cause the sel change event to fire. + visComboBox.SelectedIndex = visSelection; } /// @@ -81,13 +142,11 @@ namespace SourceGen.WpfGui { /// If we don't find a corresponding entry in the Visualization, we use the /// default value. /// - private void GenerateParamControls(string visGenName) { - IPlugin_Visualizer2d plugin = - Visualization.FindPluginByVisGenName(mProject, visGenName); - List descrs = plugin.GetVisGenParams(visGenName); + private void GenerateParamControls(VisDescr descr) { + VisParamDescr[] paramDescrs = descr.VisParamDescrs; - mParameterList.Clear(); - foreach (VisParamDescr vpd in descrs) { + ParameterList.Clear(); + foreach (VisParamDescr vpd in paramDescrs) { string rangeStr = string.Empty; object defaultVal = vpd.DefaultValue; if (mOrigVis.VisGenParams.TryGetValue(vpd.Name, out object val)) { @@ -95,20 +154,25 @@ namespace SourceGen.WpfGui { defaultVal = val; } + // Set up rangeStr, if appropriate. + VisParamDescr altVpd = vpd; if (vpd.CsType == typeof(int) || vpd.CsType == typeof(float)) { if (vpd.Special == VisParamDescr.SpecialMode.Offset) { defaultVal = mFormatter.FormatOffset24((int)defaultVal); rangeStr = "[" + mFormatter.FormatOffset24(0) + "," + mFormatter.FormatOffset24(mProject.FileDataLength - 1) + "]"; + + // Replace the vpd to provide a different min/max. + altVpd = new VisParamDescr(vpd.UiLabel, vpd.Name, vpd.CsType, + 0, mProject.FileDataLength - 1, vpd.Special, vpd.DefaultValue); } else { rangeStr = "[" + vpd.Min + "," + vpd.Max + "]"; } } - ParameterValue pv = new ParameterValue(vpd.UiLabel, vpd.Name, vpd.CsType, - defaultVal, rangeStr); + ParameterValue pv = new ParameterValue(altVpd, defaultVal, rangeStr); - mParameterList.Add(pv); + ParameterList.Add(pv); } } @@ -116,18 +180,140 @@ namespace SourceGen.WpfGui { } private void OkButton_Click(object sender, RoutedEventArgs e) { - Debug.WriteLine("PARAMS:"); - foreach (ParameterValue val in mParameterList) { - Debug.WriteLine(" " + val.Name + ": " + val.Value + - " (" + val.Value.GetType() + ")"); + // Generate value dictionary. + Dictionary valueDict = + new Dictionary(ParameterList.Count); + foreach (ParameterValue pv in ParameterList) { + if (pv.Descr.CsType == typeof(bool)) { + Debug.Assert(pv.Value is bool); + valueDict.Add(pv.Descr.Name, (bool)pv.Value); + } else if (pv.Descr.CsType == typeof(int)) { + int intVal; + if (pv.Value is int) { + intVal = (int)pv.Value; + } else { + bool ok = ParseInt((string)pv.Value, pv.Descr.Special, out intVal); + Debug.Assert(ok); + } + valueDict.Add(pv.Descr.Name, intVal); + } else if (pv.Descr.CsType == typeof(float)) { + float floatVal; + if (pv.Value is float || pv.Value is double) { + floatVal = (float)pv.Value; + } else { + bool ok = float.TryParse((string)pv.Value, out floatVal); + Debug.Assert(ok); + } + valueDict.Add(pv.Descr.Name, floatVal); + } else { + // skip it + Debug.Assert(false); + } } - DialogResult = false; // TODO + + VisDescr item = (VisDescr)visComboBox.SelectedItem; + Debug.Assert(item != null); + + NewVis = new Visualization(TagString, item.Ident, valueDict); + DialogResult = true; + } + + private bool ParseInt(string str, VisParamDescr.SpecialMode special, out int intVal) { + int numBase = (special == VisParamDescr.SpecialMode.Offset) ? 16 : 10; + + string trimStr = str.Trim(); + if (trimStr.Length >= 1 && trimStr[0] == '+') { + // May be present for an offset. Just ignore it. Don't use it as a radix char. + trimStr = trimStr.Remove(0, 1); + } else if (trimStr.Length >= 1 && trimStr[0] == '$') { + numBase = 16; + trimStr = trimStr.Remove(0, 1); + } else if (trimStr.Length >= 2 && trimStr[0] == '0' && + (trimStr[1] == 'x' || trimStr[1] == 'X')) { + numBase = 16; + trimStr = trimStr.Remove(0, 2); + } + if (trimStr.Length == 0) { + intVal = -1; + return false; + } + + try { + intVal = Convert.ToInt32(trimStr, numBase); + return true; + } catch (Exception) { + intVal = -1; + return false; + } + } + + private void CheckValid() { + IsValid = true; + + string trimTag = TagString.Trim(); + if (trimTag.Length < MIN_TRIMMED_TAG_LEN) { + IsValid = false; + TagLabelBrush = mErrorLabelColor; + } else { + TagLabelBrush = mDefaultLabelColor; + } + // TODO: verify tag is unique + + foreach (ParameterValue pv in ParameterList) { + pv.ForegroundBrush = mDefaultLabelColor; + if (pv.Descr.CsType == typeof(bool)) { + // always fine + continue; + } else if (pv.Descr.CsType == typeof(int)) { + // integer, possibly Offset special + bool ok = true; + int intVal; + if (pv.Value is int) { + // happens initially, before the TextBox can futz with it + intVal = (int)pv.Value; + } else if (!ParseInt((string)pv.Value, pv.Descr.Special, out intVal)) { + ok = false; + } + if (ok && (intVal < (int)pv.Descr.Min || intVal > (int)pv.Descr.Max)) { + // TODO(someday): make the range text red instead of the label + ok = false; + } + if (!ok) { + pv.ForegroundBrush = mErrorLabelColor; + IsValid = false; + } + } else if (pv.Descr.CsType == typeof(float)) { + // float + } else { + // unexpected + Debug.Assert(false); + } + } + } + + private void VisComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { + VisDescr item = (VisDescr)visComboBox.SelectedItem; + if (item == null) { + Debug.Assert(false); // not expected + return; + } + Debug.WriteLine("VisComboBox sel change: " + item.Ident); + GenerateParamControls(item); + CheckValid(); } private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { TextBox src = (TextBox)sender; ParameterValue pv = (ParameterValue)src.DataContext; - Debug.WriteLine("TEXT CHANGE " + pv + ": " + src.Text); + //Debug.WriteLine("TEXT CHANGE " + pv + ": " + src.Text); + CheckValid(); + } + + private void CheckBox_Changed(object sender, RoutedEventArgs e) { + CheckBox src = (CheckBox)sender; + ParameterValue pv = (ParameterValue)src.DataContext; + //Debug.WriteLine("CHECK CHANGE" + pv); + CheckValid(); } } @@ -135,26 +321,44 @@ namespace SourceGen.WpfGui { /// Describes a parameter and holds its value while being edited by WPF. /// /// - /// We use an explicit type so that we can format the initial value as hex or whatever. + /// We currently detect updates with change events. We could also tweak the Value setter + /// to fire an event back to the window class when things change. I don't know that there's + /// an advantage to doing so. /// - public class ParameterValue { - public string UiName { get; private set; } - public string Name { get; private set; } - public Type CsType { get; private set; } - public object Value { get; set; } + public class ParameterValue : INotifyPropertyChanged { + public VisParamDescr Descr { get; private set; } + public string UiString { get; private set; } public string RangeText { get; private set; } - public ParameterValue(string uiName, string name, Type csType, object val, - string rangeText) { - UiName = uiName; - Name = name; - CsType = csType; + private object mValue; + public object Value { + get { return mValue; } + set { mValue = value; OnPropertyChanged(); } + } + + private Brush mForegroundBrush; + public Brush ForegroundBrush { + get { return mForegroundBrush; } + set { mForegroundBrush = value; OnPropertyChanged(); } + } + + // INotifyPropertyChanged implementation + public event PropertyChangedEventHandler PropertyChanged; + private void OnPropertyChanged([CallerMemberName] string propertyName = "") { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public ParameterValue(VisParamDescr vpd, object val, string rangeText) { + Descr = vpd; Value = val; RangeText = rangeText; + + char labelSuffix = (vpd.CsType == typeof(bool)) ? '?' : ':'; + UiString = vpd.UiLabel + labelSuffix; } public override string ToString() { - return "[PV: " + Name + "=" + Value + "]"; + return "[PV: " + Descr.Name + "=" + Value + "]"; } } @@ -178,11 +382,11 @@ namespace SourceGen.WpfGui { public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item is ParameterValue) { ParameterValue parm = (ParameterValue)item; - if (parm.CsType == typeof(bool)) { + if (parm.Descr.CsType == typeof(bool)) { return BoolTemplate; - } else if (parm.CsType == typeof(int)) { + } else if (parm.Descr.CsType == typeof(int)) { return IntTemplate; - } else if (parm.CsType == typeof(float)) { + } else if (parm.Descr.CsType == typeof(float)) { return FloatTemplate; } else { Debug.WriteLine("WHA?" + parm.Value.GetType()); diff --git a/SourceGen/WpfGui/EditVisualizationSet.xaml.cs b/SourceGen/WpfGui/EditVisualizationSet.xaml.cs index 51f922a..c6606df 100644 --- a/SourceGen/WpfGui/EditVisualizationSet.xaml.cs +++ b/SourceGen/WpfGui/EditVisualizationSet.xaml.cs @@ -91,6 +91,9 @@ namespace SourceGen.WpfGui { private void NewButton_Click(object sender, RoutedEventArgs e) { VisualizationList.Add(new Visualization("VIS #" + VisualizationList.Count, "apple2-hi-res-bitmap", new Dictionary())); + + // TODO: disable New button if no appropriate vis plugins can be found (or maybe + // MessageBox here) } private void EditButton_Click(object sender, RoutedEventArgs e) { @@ -100,8 +103,11 @@ namespace SourceGen.WpfGui { EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, new Visualization("arbitrary tag", "apple2-hi-res-bitmap", testDict)); if (dlg.ShowDialog() == true) { - // TODO + Visualization newVis = dlg.NewVis; + Debug.WriteLine("New vis: " + newVis); } + + // TODO: disable edit button if matching vis can't be found (or maybe MessageBox) } private void RemoveButton_Click(object sender, RoutedEventArgs e) {