From 1cc9d2bd703a44746580b3f2e071ac7f8f68043e Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sun, 25 Aug 2019 17:25:15 -0700 Subject: [PATCH] Update editors to work with local variables The table editor is now editing the table, and the DefSymbol editor now asks for the Width data when editing a local var. This also moves EditDefSymbol closer to proper WPF style, with bound properties for the input fields. No changes yet to serialization or analysis. --- SourceGen/DefSymbol.cs | 21 +++ SourceGen/LocalVariableTable.cs | 75 +++++++-- SourceGen/MainController.cs | 10 +- SourceGen/ProjectProperties.cs | 3 +- SourceGen/WpfGui/EditDefSymbol.xaml | 31 ++-- SourceGen/WpfGui/EditDefSymbol.xaml.cs | 137 ++++++++++++---- SourceGen/WpfGui/EditLocalVariableTable.xaml | 4 +- .../WpfGui/EditLocalVariableTable.xaml.cs | 149 +++++++++++++++++- .../WpfGui/EditProjectProperties.xaml.cs | 20 +-- 9 files changed, 370 insertions(+), 80 deletions(-) diff --git a/SourceGen/DefSymbol.cs b/SourceGen/DefSymbol.cs index b166e8a..1eca7ee 100644 --- a/SourceGen/DefSymbol.cs +++ b/SourceGen/DefSymbol.cs @@ -24,6 +24,8 @@ namespace SourceGen { /// public class DefSymbol : Symbol { public const int NO_WIDTH = -1; + public const int MIN_WIDTH = 1; + public const int MAX_WIDTH = 4; /// /// Data format descriptor. @@ -93,6 +95,25 @@ namespace SourceGen { Tag = tag; } + /// + /// Constructor. + /// + /// Symbol's label. + /// Symbol's value. + /// Symbol source (general point of origin). + /// Symbol type. + /// Format descriptor sub-type, so we know how the + /// user wants the value to be displayed. + /// End-of-line comment. + /// Symbol tag, used for grouping platform symbols. + /// Variable width. + public DefSymbol(string label, int value, Source source, Type type, + FormatDescriptor.SubType formatSubType, string comment, string tag, int width) + : this(label, value, source, type, formatSubType, comment, tag) { + Debug.Assert(width == NO_WIDTH || (width >= MIN_WIDTH && width <= MAX_WIDTH)); + Width = width; + } + /// /// Constructs a DefSymbol from a Symbol and a format descriptor. This is used /// for project symbols. diff --git a/SourceGen/LocalVariableTable.cs b/SourceGen/LocalVariableTable.cs index 99e8d23..f2a49e9 100644 --- a/SourceGen/LocalVariableTable.cs +++ b/SourceGen/LocalVariableTable.cs @@ -15,18 +15,23 @@ */ using System; using System.Collections.Generic; +using System.Diagnostics; namespace SourceGen { /// /// Table of redefinable variables. A project may have several of these, at different /// offsets. The contents of later tables overwrite the contents of earlier tables. + /// + /// The class is mutable, but may only be modified by the LvTable editor (which makes + /// changes to a work object that moves through the undo/redo buffer) or the + /// deserializer. /// public class LocalVariableTable { /// /// List of variables. The symbol's label must be unique within a table, so we sort /// on that. /// - private SortedList mVariables; + public SortedList Variables; /// /// If set, all values from previous VariableTables should be discarded when this @@ -40,25 +45,65 @@ namespace SourceGen { /// public bool ClearPrevious { get; set; } - /// - /// Indexer. - /// - /// Symbol's label. - /// Matching symbol. Throws an exception if not found. - public DefSymbol this[string key] { - get { - return mVariables[key]; - } - set { - mVariables[key] = value; - } - } /// /// Constructs an empty table. /// public LocalVariableTable() { - mVariables = new SortedList(); + Variables = new SortedList(); + } + + /// + /// Copy constructor. + /// + /// Object to clone. + public LocalVariableTable(LocalVariableTable src) : this() { + ClearPrevious = src.ClearPrevious; + + foreach (KeyValuePair kvp in src.Variables) { + Variables[kvp.Key] = kvp.Value; + } + + Debug.Assert(this == src); + } + + public static bool operator ==(LocalVariableTable a, LocalVariableTable b) { + if (ReferenceEquals(a, b)) { + return true; // same object, or both null + } + if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) { + return false; // one is null + } + // All fields must be equal. + if (a.ClearPrevious != b.ClearPrevious) { + return false; + } + if (a.Variables.Count != b.Variables.Count) { + return false; + } + // Compare all list entries. + for (int i = 0; i < a.Variables.Count; i++) { + if (a.Variables.Values[i] != b.Variables.Values[i]) { + return false; + } + } + return true; + } + public static bool operator !=(LocalVariableTable a, LocalVariableTable b) { + return !(a == b); + } + public override bool Equals(object obj) { + return obj is LocalVariableTable && this == (LocalVariableTable)obj; + } + public override int GetHashCode() { + int hashCode = 0; + foreach (KeyValuePair kvp in Variables) { + hashCode ^= kvp.Value.GetHashCode(); + } + if (ClearPrevious) { + hashCode++; + } + return hashCode; } } } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index ef592c9..639a3f2 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -1689,12 +1689,15 @@ namespace SourceGen { return true; // TODO } + private LocalVariableTable lvt = new LocalVariableTable(); public void EditLocalVariableTable() { // TODO - EditLocalVariableTable dlg = new EditLocalVariableTable(mMainWin); + EditLocalVariableTable dlg = new EditLocalVariableTable(mMainWin, mProject.SymbolTable, + mOutputFormatter, lvt); if (dlg.ShowDialog() != true) { return; } + lvt = dlg.NewTable; } public bool CanEditLongComment() { @@ -1937,12 +1940,11 @@ namespace SourceGen { Debug.Assert(origDefSym.SymbolSource == Symbol.Source.Project); EditDefSymbol dlg = new EditDefSymbol(mMainWin, mOutputFormatter, - mProject.ProjectProps.ProjectSyms); - dlg.DefSym = origDefSym; + mProject.ProjectProps.ProjectSyms, origDefSym); if (dlg.ShowDialog() == true) { ProjectProperties newProps = new ProjectProperties(mProject.ProjectProps); newProps.ProjectSyms.Remove(origDefSym.Label); - newProps.ProjectSyms[dlg.DefSym.Label] = dlg.DefSym; + newProps.ProjectSyms[dlg.NewSym.Label] = dlg.NewSym; UndoableChange uc = UndoableChange.CreateProjectPropertiesChange( mProject.ProjectProps, newProps); diff --git a/SourceGen/ProjectProperties.cs b/SourceGen/ProjectProperties.cs index d8f4b80..763430a 100644 --- a/SourceGen/ProjectProperties.cs +++ b/SourceGen/ProjectProperties.cs @@ -20,7 +20,8 @@ namespace SourceGen { /// /// A collection of project properties. /// - /// The class is mutable, but may only be modified by DisasmProject.ApplyChanges or + /// The class is mutable, but may only be modified by the property editor (which updates + /// a work object that gets put into the project by DisasmProject.ApplyChanges) or /// the deserializer. /// /// All fields are explicitly handled by the ProjectFile serializer. diff --git a/SourceGen/WpfGui/EditDefSymbol.xaml b/SourceGen/WpfGui/EditDefSymbol.xaml index b253a49..8a5d35a 100644 --- a/SourceGen/WpfGui/EditDefSymbol.xaml +++ b/SourceGen/WpfGui/EditDefSymbol.xaml @@ -36,40 +36,47 @@ limitations under the License. + - + - + + - + - - - + + + + + + + + - + - public partial class EditDefSymbol : Window, INotifyPropertyChanged { /// - /// Set to previous value before calling; may be null if creating a new symbol. - /// Will be set to new value on OK result. + /// Result; will be set non-null on OK. /// - public DefSymbol DefSym { get; set; } + public DefSymbol NewSym { get; private set; } /// /// Set to true when all fields are valid. Controls whether the OK button is enabled. /// public bool IsValid { get { return mIsValid; } - set { - mIsValid = value; - OnPropertyChanged(); - } + set { mIsValid = value; OnPropertyChanged(); } } private bool mIsValid; + public string Label { + get { return mLabel; } + set { mLabel = value; OnPropertyChanged(); UpdateControls(); } + } + private string mLabel; + + public string Value { + get { return mValue; } + set { mValue = value; OnPropertyChanged(); UpdateControls(); } + } + private string mValue; + + public string VarWidth { + get { return mWidth; } + set { mWidth = value; OnPropertyChanged(); UpdateControls(); } + } + private string mWidth; + + public string Comment { + get { return mComment; } + set { mComment = value; OnPropertyChanged(); } + } + private string mComment; + /// /// Format object to use when formatting addresses and constants. /// private Formatter mNumFormatter; + /// + /// Old symbol value. May be null. + /// + private DefSymbol mOldSym; + /// /// List of existing symbols, for uniqueness check. The list will not be modified. /// private SortedList mDefSymbolList; + /// + /// Full symbol table, for extended uniqueness check. + /// + private SymbolTable mSymbolTable; + // Saved off at dialog load time. private Brush mDefaultLabelColor; @@ -67,26 +97,50 @@ namespace SourceGen.WpfGui { } + /// + /// Constructor, for editing a project symbol. + /// public EditDefSymbol(Window owner, Formatter formatter, - SortedList defList) { + SortedList defList, DefSymbol defSym) { InitializeComponent(); Owner = owner; DataContext = this; mNumFormatter = formatter; mDefSymbolList = defList; + + mOldSym = defSym; + + Label = Value = VarWidth = Comment = string.Empty; + widthEntry1.Visibility = widthEntry2.Visibility = labelUniqueLabel.Visibility = + Visibility.Collapsed; + projectLabelUniqueLabel.Visibility = Visibility.Visible; + } + + /// + /// Constructor, for editing a local variable. + /// + public EditDefSymbol(Window owner, Formatter formatter, + SortedList defList, DefSymbol defSym, + SymbolTable symbolTable) : this(owner, formatter, defList, defSym) { + mSymbolTable = symbolTable; + + widthEntry1.Visibility = widthEntry2.Visibility = labelUniqueLabel.Visibility = + Visibility.Visible; + projectLabelUniqueLabel.Visibility = Visibility.Collapsed; } private void Window_Loaded(object sender, RoutedEventArgs e) { mDefaultLabelColor = labelNotesLabel.Foreground; - if (DefSym != null) { - labelTextBox.Text = DefSym.Label; - valueTextBox.Text = mNumFormatter.FormatValueInBase(DefSym.Value, - DefSym.DataDescriptor.NumBase); - commentTextBox.Text = DefSym.Comment; + if (mOldSym != null) { + Label = mOldSym.Label; + Value = mNumFormatter.FormatValueInBase(mOldSym.Value, + mOldSym.DataDescriptor.NumBase); + VarWidth = mOldSym.Width.ToString(); + Comment = mOldSym.Comment; - if (DefSym.SymbolType == Symbol.Type.Constant) { + if (mOldSym.SymbolType == Symbol.Type.Constant) { constantRadioButton.IsChecked = true; } else { addressRadioButton.IsChecked = true; @@ -100,37 +154,55 @@ namespace SourceGen.WpfGui { } private void UpdateControls() { - bool labelValid, labelUnique, valueValid; + if (!IsLoaded) { + return; + } - // Label must be valid and not already exist in project symbol list. (It's okay - // if it exists elsewhere.) - labelValid = Asm65.Label.ValidateLabel(labelTextBox.Text); + // Label must be valid and not already exist in project symbol list. (For project + // symbols, it's okay if an identical label exists elsewhere.) + bool labelValid = Asm65.Label.ValidateLabel(Label); + bool labelUnique; - if (mDefSymbolList.TryGetValue(labelTextBox.Text, out DefSymbol existing)) { + if (mDefSymbolList.TryGetValue(Label, out DefSymbol existing)) { // It's okay if it's the same object. - labelUnique = (existing == DefSym); + labelUnique = (existing == mOldSym); } else { labelUnique = true; } + // For local variables, do a secondary uniqueness check. + if (labelUnique && mSymbolTable != null) { + labelUnique = !mSymbolTable.TryGetValue(Label, out Symbol sym); + } + // Value must be blank, meaning "erase any earlier definition", or valid value. // (Hmm... don't currently have a way to specify "no symbol" in DefSymbol.) //if (!string.IsNullOrEmpty(valueTextBox.Text)) { - valueValid = ParseValue(out int unused1, out int unused2); + bool valueValid = ParseValue(out int unused1, out int unused2); //} else { // valueValid = true; //} + bool widthValid = true; + if (widthEntry1.Visibility == Visibility.Visible) { + if (!int.TryParse(VarWidth, out int width) || + width < DefSymbol.MIN_WIDTH || width > DefSymbol.MAX_WIDTH) { + widthValid = false; + } + } + // TODO(maybe): do this the XAML way, with properties and Styles labelNotesLabel.Foreground = labelValid ? mDefaultLabelColor : Brushes.Red; - labelUniqueLabel.Foreground = labelUnique ? mDefaultLabelColor : Brushes.Red; + labelUniqueLabel.Foreground = projectLabelUniqueLabel.Foreground = + labelUnique ? mDefaultLabelColor : Brushes.Red; valueNotesLabel.Foreground = valueValid ? mDefaultLabelColor : Brushes.Red; + widthNotesLabel.Foreground = widthValid ? mDefaultLabelColor : Brushes.Red; - IsValid = labelValid && labelUnique && valueValid; + IsValid = labelValid && labelUnique && valueValid && widthValid; } private bool ParseValue(out int value, out int numBase) { - string str = valueTextBox.Text; + string str = Value; if (str.IndexOf('/') >= 0) { // treat as address numBase = 16; @@ -145,19 +217,16 @@ namespace SourceGen.WpfGui { ParseValue(out int value, out int numBase); FormatDescriptor.SubType subType = FormatDescriptor.GetSubTypeForBase(numBase); - DefSym = new DefSymbol(labelTextBox.Text, value, Symbol.Source.Project, + int width = DefSymbol.NO_WIDTH; + if (!string.IsNullOrEmpty(VarWidth)) { + width = int.Parse(VarWidth); + } + + NewSym = new DefSymbol(Label, value, Symbol.Source.Project, isConstant ? Symbol.Type.Constant : Symbol.Type.ExternalAddr, - subType, commentTextBox.Text, string.Empty); + subType, Comment, string.Empty, width); DialogResult = true; } - - private void LabelTextBox_TextChanged(object sender, TextChangedEventArgs e) { - UpdateControls(); - } - - private void ValueTextBox_TextChanged(object sender, TextChangedEventArgs e) { - UpdateControls(); - } } } diff --git a/SourceGen/WpfGui/EditLocalVariableTable.xaml b/SourceGen/WpfGui/EditLocalVariableTable.xaml index e49a31a..7a33862 100644 --- a/SourceGen/WpfGui/EditLocalVariableTable.xaml +++ b/SourceGen/WpfGui/EditLocalVariableTable.xaml @@ -72,11 +72,11 @@ limitations under the License. Click="RemoveSymbolButton_Click"/> - -