diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs
index 3163e4e..3f2f62d 100644
--- a/SourceGenWPF/MainController.cs
+++ b/SourceGenWPF/MainController.cs
@@ -686,8 +686,10 @@ namespace SourceGenWPF {
mMainWin.CodeListView_SetTopIndex(topItemIndex);
mReanalysisTimer.EndTask("Restore selection and top position");
- // Update the Notes list as well.
+ // Update the Notes and Symbols windows.
+ // TODO: references?
PopulateNotesList();
+ PopulateSymbolsList();
mReanalysisTimer.EndTask("ProjectView.ApplyChanges()");
@@ -1304,11 +1306,9 @@ namespace SourceGenWPF {
}
break;
case CodeListColumn.Operand:
-#if false
- if (editOperandToolStripMenuItem.Enabled) {
- EditInstrDataOperand_Click(sender, e);
+ if (CanEditOperand()) {
+ EditOperand();
}
-#endif
break;
case CodeListColumn.Comment:
#if false
@@ -1450,6 +1450,108 @@ namespace SourceGenWPF {
}
}
+ public bool CanEditOperand() {
+ if (SelectionAnalysis.mNumItemsSelected != 1) {
+ return false;
+ }
+ int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex();
+ int selOffset = CodeLineList[selIndex].FileOffset;
+
+ bool editInstr = (CodeLineList[selIndex].LineType == LineListGen.Line.Type.Code &&
+ mProject.GetAnattrib(selOffset).IsInstructionWithOperand);
+ bool editData = (CodeLineList[selIndex].LineType == LineListGen.Line.Type.Data);
+ return editInstr || editData;
+ }
+
+ public void EditOperand() {
+ int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex();
+ int selOffset = CodeLineList[selIndex].FileOffset;
+ if (CodeLineList[selIndex].LineType == LineListGen.Line.Type.Code) {
+ EditInstructionOperand(selOffset);
+ } else {
+ Debug.Assert(CodeLineList[selIndex].LineType == LineListGen.Line.Type.Data);
+ EditDataOperand(selOffset);
+ }
+ }
+
+ private void EditInstructionOperand(int offset) {
+ EditInstructionOperand dlg = new EditInstructionOperand(mMainWin, offset,
+ mProject, mOutputFormatter);
+
+ // We'd really like to pass in an indication of what the "default" format actually
+ // resolved to, but we don't always know. If this offset has a FormatDescriptor,
+ // we might not have auto-generated the label that would have been used otherwise.
+
+ // We're editing the FormatDescriptor from OperandFormats, not Anattribs;
+ // the latter may have auto-generated stuff.
+ if (mProject.OperandFormats.TryGetValue(offset, out FormatDescriptor dfd)) {
+ dlg.FormatDescriptor = dfd;
+ }
+
+ dlg.ShowDialog();
+ if (dlg.DialogResult != true) {
+ return;
+ }
+
+ ChangeSet cs = new ChangeSet(1);
+
+ // Handle shortcut actions.
+
+ if (dlg.FormatDescriptor != dfd && dlg.ShortcutAction !=
+ WpfGui.EditInstructionOperand.SymbolShortcutAction.CreateLabelInstead) {
+ // Note EditOperand returns a null descriptor when the user selects Default.
+ // This is different from how EditData works, since that has to deal with
+ // multiple regions.
+ Debug.WriteLine("Changing " + dfd + " to " + dlg.FormatDescriptor);
+ UndoableChange uc = UndoableChange.CreateOperandFormatChange(offset,
+ dfd, dlg.FormatDescriptor);
+ cs.Add(uc);
+ } else if (dfd != null && dlg.ShortcutAction ==
+ WpfGui.EditInstructionOperand.SymbolShortcutAction.CreateLabelInstead) {
+ Debug.WriteLine("Removing existing label for CreateLabelInstead");
+ UndoableChange uc = UndoableChange.CreateOperandFormatChange(offset,
+ dfd, null);
+ cs.Add(uc);
+ } else {
+ Debug.WriteLine("No change to format descriptor");
+ }
+
+ switch (dlg.ShortcutAction) {
+ case WpfGui.EditInstructionOperand.SymbolShortcutAction.CreateLabelInstead:
+ case WpfGui.EditInstructionOperand.SymbolShortcutAction.CreateLabelAlso:
+ Debug.Assert(!mProject.UserLabels.ContainsKey(dlg.ShortcutArg));
+ Anattrib targetAttr = mProject.GetAnattrib(dlg.ShortcutArg);
+ Symbol newLabel = new Symbol(dlg.FormatDescriptor.SymbolRef.Label,
+ targetAttr.Address, Symbol.Source.User, Symbol.Type.LocalOrGlobalAddr);
+ UndoableChange uc = UndoableChange.CreateLabelChange(dlg.ShortcutArg,
+ null, newLabel);
+ cs.Add(uc);
+ break;
+ case WpfGui.EditInstructionOperand.SymbolShortcutAction.CreateProjectSymbolAlso:
+ Debug.Assert(!mProject.ProjectProps.ProjectSyms.ContainsKey(
+ dlg.FormatDescriptor.SymbolRef.Label));
+ DefSymbol defSym = new DefSymbol(dlg.FormatDescriptor.SymbolRef.Label,
+ dlg.ShortcutArg, Symbol.Source.Project, Symbol.Type.ExternalAddr,
+ FormatDescriptor.SubType.Hex, string.Empty, string.Empty);
+ ProjectProperties newProps = new ProjectProperties(mProject.ProjectProps);
+ newProps.ProjectSyms.Add(defSym.Label, defSym);
+ uc = UndoableChange.CreateProjectPropertiesChange(
+ mProject.ProjectProps, newProps);
+ cs.Add(uc);
+ break;
+ case WpfGui.EditInstructionOperand.SymbolShortcutAction.None:
+ break;
+ }
+
+ if (cs.Count != 0) {
+ ApplyUndoableChanges(cs);
+ }
+ }
+
+ private void EditDataOperand(int offset) {
+ // TODO
+ }
+
public bool CanEditStatusFlags() {
if (SelectionAnalysis.mNumItemsSelected != 1) {
return false;
diff --git a/SourceGenWPF/SourceGenWPF.csproj b/SourceGenWPF/SourceGenWPF.csproj
index 840416d..25d83ed 100644
--- a/SourceGenWPF/SourceGenWPF.csproj
+++ b/SourceGenWPF/SourceGenWPF.csproj
@@ -87,6 +87,9 @@
EditDefSymbol.xaml
+
+ EditInstructionOperand.xaml
+ EditLabel.xaml
@@ -212,6 +215,10 @@
DesignerMSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+ DesignerMSBuild:Compile
diff --git a/SourceGenWPF/WpfGui/EditInstructionOperand.xaml b/SourceGenWPF/WpfGui/EditInstructionOperand.xaml
new file mode 100644
index 0000000..0b1d428
--- /dev/null
+++ b/SourceGenWPF/WpfGui/EditInstructionOperand.xaml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SourceGenWPF/WpfGui/EditInstructionOperand.xaml.cs b/SourceGenWPF/WpfGui/EditInstructionOperand.xaml.cs
new file mode 100644
index 0000000..f142732
--- /dev/null
+++ b/SourceGenWPF/WpfGui/EditInstructionOperand.xaml.cs
@@ -0,0 +1,561 @@
+/*
+ * 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.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using Asm65;
+
+namespace SourceGenWPF.WpfGui {
+ ///
+ /// Instruction operand editor.
+ ///
+ /// This is a pretty direct port from WinForms.
+ ///
+ public partial class EditInstructionOperand : Window, INotifyPropertyChanged {
+ ///
+ /// In/out. May be null on entry if the offset doesn't have a format descriptor
+ /// specified. Will be null on exit if "default" is selected.
+ ///
+ public FormatDescriptor FormatDescriptor { get; set; }
+
+ public enum SymbolShortcutAction {
+ None = 0, CreateLabelInstead, CreateLabelAlso, CreateProjectSymbolAlso
+ }
+
+ ///
+ /// Remember the last option we used.
+ ///
+ private static SymbolShortcutAction sLastAction = SymbolShortcutAction.None;
+
+ ///
+ /// On OK dialog exit, specifies that an additional action should be taken.
+ ///
+ public SymbolShortcutAction ShortcutAction { get; private set; }
+
+ ///
+ /// Additional argument, meaning dependent on ShortcutAction. This will either be
+ /// the target label offset or the project symbol value.
+ ///
+ public int ShortcutArg { get; private set; }
+
+ ///
+ /// Width of full instruction, including opcode.
+ ///
+ private int mInstructionLength;
+
+ ///
+ /// Number of hexadecimal digits to show in the preview. Sometimes you want
+ /// to force this to be longer or shorter than InstructionLength would indicate,
+ /// e.g. "BRA $1000" has a 1-byte operand.
+ ///
+ private int mPreviewHexDigits;
+
+ ///
+ /// Operand value, extracted from file data. For a relative branch, this will be
+ /// an address instead. Only used for preview window.
+ ///
+ private int mOperandValue;
+
+ ///
+ /// Is the operand an immediate value? If so, we enable the symbol part selection.
+ ///
+ private bool mIsExtendedImmediate;
+
+ ///
+ /// Is the operand a PC relative offset?
+ ///
+ private bool mIsPcRelative;
+
+ ///
+ /// Special handling for block move instructions (MVN/MVP).
+ ///
+ private bool mIsBlockMove;
+
+ ///
+ /// If set, show a '#' in the preview indow.
+ ///
+ private bool mShowHashPrefix;
+
+ /////
+ ///// Symbol table to use when resolving symbolic values.
+ /////
+ //private SymbolTable SymbolTable { get; set; }
+
+ ///
+ /// Project reference.
+ ///
+ private DisasmProject mProject;
+
+ ///
+ /// Formatter to use when displaying addresses and hex values.
+ ///
+ private Formatter mFormatter;
+
+ ///
+ /// Copy of operand Anattribs.
+ ///
+ private Anattrib mAttr;
+
+ ///
+ /// Set this during initial control configuration, so we know to ignore the CheckedChanged
+ /// events.
+ ///
+ private bool mIsInitialSetup;
+
+ ///
+ /// Set to true if the user has entered a symbol that matches an auto-generated symbol.
+ ///
+ private bool mIsSymbolAuto;
+
+ // INotifyPropertyChanged implementation
+ public event PropertyChangedEventHandler PropertyChanged;
+ private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+
+ public EditInstructionOperand(Window owner, int offset, DisasmProject project,
+ Asm65.Formatter formatter) {
+ InitializeComponent();
+ Owner = owner;
+ DataContext = this;
+
+ mProject = project;
+ mFormatter = formatter;
+
+ // Configure the appearance.
+ mAttr = mProject.GetAnattrib(offset);
+ OpDef op = mProject.CpuDef.GetOpDef(mProject.FileData[offset]);
+ mInstructionLength = mAttr.Length;
+ mPreviewHexDigits = (mAttr.Length - 1) * 2;
+ if (mAttr.OperandAddress >= 0) {
+ // Use this as the operand value when available. This lets us present
+ // relative branch instructions in the expected form.
+ mOperandValue = mAttr.OperandAddress;
+
+ if (op.AddrMode == OpDef.AddressMode.PCRel) {
+ mPreviewHexDigits = 4;
+ mIsPcRelative = true;
+ } else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
+ op.AddrMode == OpDef.AddressMode.StackPCRelLong) {
+ mIsPcRelative = true;
+ }
+ } else {
+ int opVal = op.GetOperand(mProject.FileData, offset, mAttr.StatusFlags);
+ mOperandValue = opVal;
+ if (op.AddrMode == OpDef.AddressMode.BlockMove) {
+ // MVN and MVP screw things up by having two operands in one instruction.
+ // We deal with this by passing in the value from the second byte
+ // (source bank) as the value, and applying the chosen format to both bytes.
+ mIsBlockMove = true;
+ mOperandValue = opVal >> 8;
+ mPreviewHexDigits = 2;
+ }
+ }
+ mIsExtendedImmediate = op.IsExtendedImmediate; // Imm, PEA, MVN/MVP
+ mShowHashPrefix = op.IsImmediate; // just Imm
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e) {
+ mIsInitialSetup = true;
+
+ // Can this be represented as high or low ASCII?
+ asciiButton.IsEnabled = CommonUtil.TextUtil.IsHiLoAscii(mOperandValue);
+
+ // Configure the dialog from the FormatDescriptor, if one is available.
+ SetControlsFromDescriptor(FormatDescriptor);
+
+ // Do this whether or not symbol is checked -- want to have this set when the
+ // dialog is initially in default format.
+ switch (sLastAction) {
+ case SymbolShortcutAction.CreateLabelInstead:
+ labelInsteadButton.IsChecked = true;
+ break;
+ case SymbolShortcutAction.CreateLabelAlso:
+ operandAndLabelButton.IsChecked = true;
+ break;
+ case SymbolShortcutAction.CreateProjectSymbolAlso:
+ operandAndProjButton.IsChecked = true;
+ break;
+ default:
+ operandOnlyButton.IsChecked = true;
+ break;
+ }
+
+ mIsInitialSetup = false;
+ UpdateControls();
+ }
+
+
+ private void Window_ContentRendered(object sender, EventArgs e) {
+ // Start with the focus in the text box. This way they can start typing
+ // immediately.
+ symbolTextBox.Focus();
+ }
+
+
+ private void SymbolTextBox_TextChanged(object sender, TextChangedEventArgs e) {
+ // Make sure Symbol is checked if they're typing text in.
+ symbolButton.IsChecked = true;
+ UpdateControls();
+ }
+
+ ///
+ /// Handles Checked/Unchecked events for all radio buttons in main group.
+ ///
+ private void MainGroup_CheckedChanged(object sender, RoutedEventArgs e) {
+ // Enable/disable the low/high/bank radio group.
+ // Update preview window.
+ UpdateControls();
+ }
+
+ ///
+ /// Handles Checked/Unchecked events for all radio buttons in symbol-part group.
+ ///
+ private void PartGroup_CheckedChanged(object sender, RoutedEventArgs e) {
+ // Update preview window.
+ UpdateControls();
+ }
+
+ private void OkButton_Click(object sender, RoutedEventArgs e) {
+ FormatDescriptor = CreateDescriptorFromControls();
+
+ //
+ // Extract the current shortcut action. For dialog configuration purposes we
+ // want to capture the current state. For the caller, we force it to "none"
+ // if we're not using a symbol format.
+ //
+ SymbolShortcutAction action = SymbolShortcutAction.None;
+ if (labelInsteadButton.IsChecked == true) {
+ action = SymbolShortcutAction.CreateLabelInstead;
+ } else if (operandAndLabelButton.IsChecked == true) {
+ action = SymbolShortcutAction.CreateLabelAlso;
+ } else if (operandAndProjButton.IsChecked == true) {
+ action = SymbolShortcutAction.CreateProjectSymbolAlso;
+ } else if (operandOnlyButton.IsChecked == true) {
+ action = SymbolShortcutAction.None;
+ } else {
+ Debug.Assert(false);
+ action = SymbolShortcutAction.None;
+ }
+ sLastAction = action;
+
+ if (symbolButton.IsChecked == true && FormatDescriptor != null) {
+ // Only report a shortcut action if they've entered a symbol. If they
+ // checked symbol but left the field blank, they're just trying to delete
+ // the format.
+ ShortcutAction = action;
+ } else {
+ ShortcutAction = SymbolShortcutAction.None;
+ }
+
+ DialogResult = true;
+ }
+
+ ///
+ /// Updates all of the controls to reflect the current internal state.
+ ///
+ private void UpdateControls() {
+ if (mIsInitialSetup) {
+ return;
+ }
+ symbolPartPanel.IsEnabled = (symbolButton.IsChecked == true && mIsExtendedImmediate);
+ symbolShortcutsGroupBox.IsEnabled = symbolButton.IsChecked == true;
+
+ SetPreviewText();
+
+ bool isOk = true;
+ if (symbolButton.IsChecked == true) {
+ // Just check for correct format. References to non-existent labels are allowed.
+ //
+ // We try to block references to auto labels, but it's possible to get around it
+ // (replace auto label with user label, reference non-existent auto label,
+ // remove user label). We could try harder, but currently not necessary.
+ isOk = !mIsSymbolAuto && Asm65.Label.ValidateLabel(symbolTextBox.Text);
+
+ // Allow empty strings as a way to delete the label and return to "default".
+ if (string.IsNullOrEmpty(symbolTextBox.Text)) {
+ isOk = true;
+ }
+
+ ConfigureSymbolShortcuts();
+ }
+ okButton.IsEnabled = isOk;
+ }
+
+ ///
+ /// Sets the text displayed in the "preview" text box.
+ ///
+ private void SetPreviewText() {
+ //symbolValueLabel.Text = string.Empty;
+ mIsSymbolAuto = false;
+
+ FormatDescriptor dfd = CreateDescriptorFromControls();
+ if (dfd == null) {
+ // Default format. We can't actually know what this look like, so just
+ // clear the box.
+ previewTextBox.Text = string.Empty;
+ return;
+ }
+
+ if (dfd.FormatSubType == FormatDescriptor.SubType.Symbol &&
+ string.IsNullOrEmpty(dfd.SymbolRef.Label)) {
+ // no label yet, nothing to show
+ previewTextBox.Text = string.Empty;
+ return;
+ }
+
+ StringBuilder preview = new StringBuilder();
+ if (mShowHashPrefix) {
+ preview.Append('#');
+ }
+
+ switch (dfd.FormatSubType) {
+ case FormatDescriptor.SubType.Hex:
+ preview.Append(mFormatter.FormatHexValue(mOperandValue, mPreviewHexDigits));
+ break;
+ case FormatDescriptor.SubType.Decimal:
+ preview.Append(mFormatter.FormatDecimalValue(mOperandValue));
+ break;
+ case FormatDescriptor.SubType.Binary:
+ preview.Append(mFormatter.FormatBinaryValue(mOperandValue, 8));
+ break;
+ case FormatDescriptor.SubType.Ascii:
+ preview.Append(mFormatter.FormatAsciiOrHex(mOperandValue));
+ break;
+ case FormatDescriptor.SubType.Symbol:
+ if (mProject.SymbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) {
+ if (mIsBlockMove) {
+ // For a 24-bit symbol, we grab the high byte. This is the
+ // expected behavior, according to Eyes & Lichty; see the
+ // explanation of the MVP instruction. For an 8-bit symbol
+ // the assembler just takes the value.
+ // TODO(someday): allow a different symbol for each part of the
+ // operand.
+ if (sym.Value > 0xff) {
+ bankButton.IsChecked = true;
+ } else {
+ lowButton.IsChecked = true;
+ }
+ dfd = CreateDescriptorFromControls();
+ }
+
+ // Hack to make relative branches look right in the preview window.
+ // Otherwise they show up like "