1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-11-13 13:10:51 +00:00
6502bench/SourceGen/WpfGui/EditInstructionOperand.xaml.cs
Andy McFadden 13dca8b78c More tweaks to def sym editing
If you edit an existing symbol, the "is the label unique" test will
always false-positive match itself, so we have to explicitly handle
that case.  Dialogs like Edit Instruction Operand make things a bit
more complicated because they don't flush results to the symbol
table immediately, which means the symbol we pass into the Edit Def
Symbol dialog to edit isn't necessarily the one we need to exclude
from the label uniqueness test.

The dialog was using the initial value as both "original" and
"initial", which caused some issues.  We now pass both values in.

Also, removed some dead code identified by VS.
2022-03-02 08:13:46 -08:00

1259 lines
53 KiB
C#

/*
* 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 Asm65;
namespace SourceGen.WpfGui {
/// <summary>
/// Instruction operand editor.
/// </summary>
public partial class EditInstructionOperand : Window, INotifyPropertyChanged {
/// <summary>
/// Updated format descriptor. Will be null if the user selected "default".
/// </summary>
public FormatDescriptor FormatDescriptorResult { get; private set; }
/// <summary>
/// Updated local variable table. Will be null if no changes were made.
/// </summary>
public LocalVariableTable LocalVariableResult { get; private set; }
/// <summary>
/// Offset of the local variable table we updated in LocalVariableResult.
/// </summary>
public int LocalVariableTableOffsetResult { get; private set; }
/// <summary>
/// Offset of label that was edited. A non-negative value here indicates that an
/// edit has been made.
/// </summary>
public int SymbolEditOffsetResult { get; private set; }
/// <summary>
/// Project symbol for this operand, when this dialog was opened. Will be null if
/// there was no matching project symbol. This is used as the "before" value for
/// updates to the project symbol set.
/// </summary>
public DefSymbol OrigProjectSymbolResult { get; private set; }
/// <summary>
/// Edited project symbol, or null if no changes were made. This is used as the "after"
/// value for updates to the project symbol. This dialog is not allowed to delete
/// project symbols, so if this is null there's nothing to do.
/// </summary>
public DefSymbol ProjectSymbolResult { get; private set; }
/// <summary>
/// Updated label.
/// </summary>
public Symbol SymbolEditResult { get; private set; }
private readonly string SYMBOL_NOT_USED;
private readonly string SYMBOL_UNKNOWN;
private readonly string SYMBOL_INVALID;
private readonly string CREATE_LOCAL_VARIABLE;
private readonly string EDIT_LOCAL_VARIABLE;
private readonly string LV_MATCH_FOUND_ADDRESS;
private readonly string LV_MATCH_FOUND_CONSTANT;
private readonly string CREATE_LABEL;
private readonly string EDIT_LABEL;
private readonly string CREATE_PROJECT_SYMBOL;
private readonly string EDIT_PROJECT_SYMBOL;
private readonly string CURRENT_LABEL;
private readonly string CURRENT_LABEL_ADJUSTED_FMT;
/// <summary>
/// Project reference.
/// </summary>
private DisasmProject mProject;
/// <summary>
/// Offset of instruction being edited.
/// </summary>
private int mOffset;
/// <summary>
/// Format object.
/// </summary>
private Formatter mFormatter;
/// <summary>
/// Operation definition, from file data.
/// </summary>
private OpDef mOpDef;
/// <summary>
/// Status flags at the point where the instruction is defined. This tells us whether
/// an operand is 8-bit or 16-bit.
/// </summary>
private StatusFlags mOpStatusFlags;
/// <summary>
/// Operand value, extracted from file data. For a relative branch, this will be
/// an address instead.
/// </summary>
private int mOperandValue;
/// <summary>
/// True when the input is valid. Controls whether the OK button is enabled.
/// </summary>
public bool IsValid {
get { return mIsValid; }
set { mIsValid = value; OnPropertyChanged(); }
}
private bool mIsValid;
/// <summary>
/// Set when our load-time initialization is complete.
/// </summary>
private bool mLoadDone;
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="owner">Parent window.</param>
/// <param name="project">Project reference.</param>
/// <param name="offset">File offset of instruction start.</param>
/// <param name="formatter">Formatter object, for preview window.</param>
public EditInstructionOperand(Window owner, DisasmProject project, int offset,
Formatter formatter) {
InitializeComponent();
Owner = owner;
DataContext = this;
mProject = project;
mOffset = offset;
mFormatter = formatter;
SYMBOL_NOT_USED = (string)FindResource("str_SymbolNotUsed");
SYMBOL_INVALID = (string)FindResource("str_SymbolNotValid");
SYMBOL_UNKNOWN = (string)FindResource("str_SymbolUnknown");
CREATE_LOCAL_VARIABLE = (string)FindResource("str_CreateLocalVariable");
EDIT_LOCAL_VARIABLE = (string)FindResource("str_EditLocalVariable");
LV_MATCH_FOUND_ADDRESS = (string)FindResource("str_LvMatchFoundAddress");
LV_MATCH_FOUND_CONSTANT = (string)FindResource("str_LvMatchFoundConstant");
CREATE_LABEL = (string)FindResource("str_CreateLabel");
EDIT_LABEL = (string)FindResource("str_EditLabel");
CREATE_PROJECT_SYMBOL = (string)FindResource("str_CreateProjectSymbol");
EDIT_PROJECT_SYMBOL = (string)FindResource("str_EditProjectSymbol");
CURRENT_LABEL = (string)FindResource("str_CurrentLabel");
CURRENT_LABEL_ADJUSTED_FMT = (string)FindResource("str_CurrentLabelAdjustedFmt");
Debug.Assert(offset >= 0 && offset < project.FileDataLength);
mOpDef = project.CpuDef.GetOpDef(project.FileData[offset]);
Anattrib attr = project.GetAnattrib(offset);
mOpStatusFlags = attr.StatusFlags;
Debug.Assert(offset + mOpDef.GetLength(mOpStatusFlags) <= project.FileDataLength);
if (attr.OperandAddress >= 0) {
// Use this as the operand value when available. This lets us present
// relative branch instructions in the expected form.
mOperandValue = attr.OperandAddress;
} else {
// For BlockMove this will have both parts.
mOperandValue = mOpDef.GetOperand(project.FileData, offset, attr.StatusFlags);
}
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
BasicFormat_Loaded();
NumericReferences_Loaded();
LocalVariables_Loaded();
mLoadDone = true;
}
private void Window_ContentRendered(object sender, EventArgs e) {
UpdateControls();
symbolTextBox.SelectAll();
symbolTextBox.Focus();
}
private void OkButton_Click(object sender, RoutedEventArgs e) {
FormatDescriptorResult = CreateDescriptorFromControls();
// Export the updated local variable table if we made changes.
if (mEditedLvTable != null) {
LocalVariableTable lvt = mProject.LvTables[mLvTableOffset];
if (mEditedLvTable != lvt) {
LocalVariableResult = mEditedLvTable;
LocalVariableTableOffsetResult = mLvTableOffset;
Debug.WriteLine("NEW TABLE:");
mEditedLvTable.DebugDump(mLvTableOffset);
} else {
Debug.WriteLine("No change to LvTable, not exporting");
}
}
if (mLabelHasBeenEdited) {
SymbolEditOffsetResult = mEditedLabelOffset;
SymbolEditResult = mEditedLabel;
}
ProjectSymbolResult = mEditedProjectSymbol;
DialogResult = true;
}
/// <summary>
/// Looks up the symbol in the symbol table. If not found there, it checks for a
/// match against the existing or edited project symbol.
/// </summary>
private bool LookupSymbol(string label, bool isNonUnique, out Symbol sym) {
if (isNonUnique) {
// Only applies to labels, so no need to check mEditedProjectSymbol. We
// could check mEditedLabel, but there's no reason to add a symbolic reference
// on top of the numeric reference.
int targetOffset;
Anattrib attr = mProject.GetAnattrib(mOffset);
if (attr.OperandOffset >= 0) {
targetOffset = attr.OperandOffset;
} else {
targetOffset = mOffset;
}
sym = mProject.FindBestNonUniqueLabel(label, targetOffset);
if (sym != null) {
return true;
}
} else {
if (mProject.SymbolTable.TryGetValue(label, out sym)) {
return true;
}
if (mEditedProjectSymbol != null && label.Equals(mEditedProjectSymbol.Label)) {
sym = mEditedProjectSymbol;
return true;
}
}
return false;
}
/// <summary>
/// Updates the state of the UI controls as the user interacts with the dialog.
/// </summary>
private void UpdateControls() {
if (!mLoadDone) {
return;
}
// Parts panel IsEnabled depends directly on formatSymbolButton.IsChecked.
IsValid = true;
IsSymbolAuto = false;
IsSymbolVar = false;
IsPartPanelEnabled = false;
SymbolValueDecimal = string.Empty;
if (FormatSymbol) {
IsPartPanelEnabled = mOpDef.IsExtendedImmediate;
string trimLabel = Symbol.TrimAndValidateLabel(SymbolLabel,
mFormatter.NonUniqueLabelPrefix, out bool isValid, out bool unused1,
out bool unused2, out bool hasNonUniquePrefix,
out Symbol.LabelAnnotation unused3);
if (!isValid) {
SymbolValueHex = SYMBOL_INVALID;
IsValid = false;
} else if (LookupSymbol(trimLabel, hasNonUniquePrefix, out Symbol sym)) {
if (sym.SymbolSource == Symbol.Source.Auto) {
// We try to block references to auto labels, but it's possible to get
// around it because FormatDescriptors are weak references (replace auto
// label with user label, reference non-existent auto label, remove user
// label). We could try harder, but currently not necessary.
//
// Referencing an auto label is unwise because we use weak references
// by name, and auto labels can appear, disappear, or be renamed.
IsValid = false;
IsSymbolAuto = true;
} else if (sym.SymbolSource == Symbol.Source.Variable) {
// Local variables can be de-duplicated and uniquified, so referring to
// them by name doesn't make sense. The numeric operand formatter will
// disregard attempts to use them in this way.
IsValid = false;
IsSymbolVar = true;
}
SymbolValueHex = mFormatter.FormatHexValue(sym.Value, 4);
SymbolValueDecimal = mFormatter.FormatDecimalValue(sym.Value);
} else {
// Valid but unknown symbol. This is fine -- symbols don't have to exist --
// but it's a little weird for non-unique symbols.
SymbolValueHex = SYMBOL_UNKNOWN;
}
} else {
SymbolValueHex = SYMBOL_NOT_USED;
}
// We want to disable the create/edit label button if a symbol has been
// specified, because the label being edited is the one that the numeric
// reference points to, not the one the symbol points to.
// TODO(maybe): leave it enabled if the symbolic ref matches the numeric ref.
IsDiddleLabelEnabled = !FormatSymbol;
UpdatePreview();
UpdateCopyToOperand();
}
/// <summary>
/// Updates the contents of the preview text box.
/// </summary>
private void UpdatePreview() {
// Generate a descriptor from the controls. This isn't strictly necessary, but it
// gets all of the data in one small package.
FormatDescriptor dfd = CreateDescriptorFromControls();
if (dfd == null) {
// Showing the right thing for the default format is surprisingly hard. There
// are a bunch of complicated steps that are performed in sequence, including
// the "nearby label" lookups, the elision of hidden symbols, and other
// obscure bits that may get tweaked from time to time. These things are not
// easy to factor out because we're slicing the data at a different angle: the
// initial pass walks the entire file looking for one thing at a point before
// analysis has completed, while here we're trying to mimic all of the
// steps for a single offset, after analysis has finished. It's a lot of work
// to show text that they'll see as soon as they hit "OK".
PreviewText = string.Empty;
return;
}
StringBuilder sb = new StringBuilder(16);
// Show the opcode. Don't bother trying to figure out width disambiguation here.
sb.Append(mFormatter.FormatOpcode(mOpDef, OpDef.WidthDisambiguation.None));
sb.Append(' ');
bool showHashPrefix = mOpDef.IsImmediate ||
mOpDef.AddrMode == OpDef.AddressMode.BlockMove;
if (showHashPrefix) {
sb.Append('#');
}
Anattrib attr = mProject.GetAnattrib(mOffset);
int previewHexDigits = (attr.Length - 1) * 2;
int operandValue = mOperandValue;
bool isPcRelative = false;
bool isBlockMove = false;
if (attr.OperandAddress >= 0) {
if (mOpDef.AddrMode == OpDef.AddressMode.PCRel ||
mOpDef.AddrMode == OpDef.AddressMode.DPPCRel) {
previewHexDigits = 4; // show branches as $xxxx even when on zero page
isPcRelative = true;
} else if (mOpDef.AddrMode == OpDef.AddressMode.PCRelLong ||
mOpDef.AddrMode == OpDef.AddressMode.StackPCRelLong) {
isPcRelative = true;
}
} else {
if (mOpDef.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.
isBlockMove = true;
operandValue = mOperandValue >> 8;
previewHexDigits = 2;
}
}
switch (dfd.FormatSubType) {
case FormatDescriptor.SubType.Hex:
sb.Append(mFormatter.FormatHexValue(operandValue, previewHexDigits));
break;
case FormatDescriptor.SubType.Decimal:
sb.Append(mFormatter.FormatDecimalValue(operandValue));
break;
case FormatDescriptor.SubType.Binary:
sb.Append(mFormatter.FormatBinaryValue(operandValue, 8));
break;
case FormatDescriptor.SubType.Ascii:
case FormatDescriptor.SubType.HighAscii:
case FormatDescriptor.SubType.C64Petscii:
case FormatDescriptor.SubType.C64Screen:
CharEncoding.Encoding enc = PseudoOp.SubTypeToEnc(dfd.FormatSubType);
sb.Append(mFormatter.FormatCharacterValue(operandValue, enc));
break;
case FormatDescriptor.SubType.Symbol:
string trimLabel = Symbol.TrimAndValidateLabel(SymbolLabel,
mFormatter.NonUniqueLabelPrefix, out bool isValid, out bool unused1,
out bool unused2, out bool hasNonUniquePrefix,
out Symbol.LabelAnnotation unused3);
if (string.IsNullOrEmpty(trimLabel)) {
sb.Append("?");
} else if (LookupSymbol(trimLabel, hasNonUniquePrefix, out Symbol sym)) {
// Block move is a little weird. "MVN label1,label2" is supposed to use
// the bank byte, while "MVN #const1,#const2" uses the entire symbol.
// The easiest thing to do is require the user to specify the "bank"
// part for 24-bit symbols, and always generate this as an immediate.
//
// MVN/MVP are also the only instructions with two operands, something
// we don't really handle.
// TODO(someday): allow a different symbol for each part of the operand.
// Hack to make relative branches look right in the preview window.
// Otherwise they show up like "<LABEL" because they appear to be
// only 8 bits.
int operandLen = dfd.Length - 1;
if (operandLen == 1 && isPcRelative) {
operandLen = 2;
}
// Set the operand length to 1 for block move so we use the part
// operators (<, >, ^) rather than bit-shifting.
if (isBlockMove) {
operandLen = 1;
}
PseudoOp.FormatNumericOpFlags flags;
if (isPcRelative) {
flags = PseudoOp.FormatNumericOpFlags.IsPcRel;
} else if (showHashPrefix) {
flags = PseudoOp.FormatNumericOpFlags.HasHashPrefix;
} else {
flags = PseudoOp.FormatNumericOpFlags.None;
}
string str = PseudoOp.FormatNumericOperand(mFormatter,
mProject.SymbolTable, null, dfd,
operandValue, operandLen, flags);
sb.Append(str);
if (sym.SymbolSource == Symbol.Source.Auto) {
mIsSymbolAuto = true;
}
} else {
sb.Append(dfd.SymbolRef.Label + " (?)");
//symbolValueLabel.Text = Properties.Resources.MSG_SYMBOL_NOT_FOUND;
}
break;
default:
Debug.Assert(false);
sb.Append("BUG");
break;
}
if (isBlockMove) {
sb.Append(",#<dest>");
}
PreviewText = sb.ToString();
}
#region Basic Format
public bool FormatDefault {
get { return mFormatDefault; }
set { mFormatDefault = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatDefault;
public bool FormatHex {
get { return mFormatHex; }
set { mFormatHex = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatHex;
public bool FormatDecimal {
get { return mFormatDecimal; }
set { mFormatDecimal = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatDecimal;
public bool FormatBinary {
get { return mFormatBinary; }
set { mFormatBinary = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatBinary;
public bool IsFormatAsciiAllowed {
get { return mIsFormatAsciiAllowed; }
set { mIsFormatAsciiAllowed = value; OnPropertyChanged(); }
}
private bool mIsFormatAsciiAllowed;
public bool FormatAscii {
get { return mFormatAscii; }
set { mFormatAscii = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatAscii;
public bool IsFormatPetsciiAllowed {
get { return mIsFormatPetsciiAllowed; }
set { mIsFormatPetsciiAllowed = value; OnPropertyChanged(); }
}
private bool mIsFormatPetsciiAllowed;
public bool FormatPetscii {
get { return mFormatPetscii; }
set { mFormatPetscii = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatPetscii;
public bool IsFormatScreenCodeAllowed {
get { return mIsFormatScreenCodeAllowed; }
set { mIsFormatScreenCodeAllowed = value; OnPropertyChanged(); }
}
private bool mIsFormatScreenCodeAllowed;
public bool FormatScreenCode {
get { return mFormatScreenCode; }
set { mFormatScreenCode = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatScreenCode;
public bool FormatSymbol {
get { return mFormatSymbol; }
set { mFormatSymbol = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatSymbol;
public string SymbolLabel {
get { return mSymbolLabel; }
set {
mSymbolLabel = value;
OnPropertyChanged();
// Set the radio button when the user starts typing.
if (mLoadDone) {
FormatSymbol = true;
// this calls UpdateControls; don't do it twice
} else {
UpdateControls();
}
}
}
private string mSymbolLabel;
public bool IsSymbolAuto {
get { return mIsSymbolAuto; }
set { mIsSymbolAuto = value; OnPropertyChanged(); }
}
private bool mIsSymbolAuto;
public bool IsSymbolVar{
get { return mIsSymbolVar; }
set { mIsSymbolVar = value; OnPropertyChanged(); }
}
private bool mIsSymbolVar;
public string SymbolValueHex {
get { return mSymbolValueHex; }
set { mSymbolValueHex = value; OnPropertyChanged(); }
}
private string mSymbolValueHex;
public string SymbolValueDecimal {
get { return mSymbolValueDecimal; }
set { mSymbolValueDecimal = value; OnPropertyChanged(); }
}
private string mSymbolValueDecimal;
public bool IsPartPanelEnabled {
get { return mIsPartPanelEnabled; }
set { mIsPartPanelEnabled = value; OnPropertyChanged(); }
}
private bool mIsPartPanelEnabled;
public bool FormatPartLow {
get { return mFormatPartLow; }
set { mFormatPartLow = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatPartLow;
public bool FormatPartHigh {
get { return mFormatPartHigh; }
set { mFormatPartHigh = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatPartHigh;
public bool FormatPartBank {
get { return mFormatPartBank; }
set { mFormatPartBank = value; OnPropertyChanged(); UpdateControls(); }
}
private bool mFormatPartBank;
public string PreviewText {
get { return mPreviewText; }
set { mPreviewText = value; OnPropertyChanged(); }
}
private string mPreviewText;
/// <summary>
/// Configures the basic formatting options, based on the existing format descriptor.
/// </summary>
private void BasicFormat_Loaded() {
// Can this be represented as a character? We only allow the printable set
// here, not the extended set (which includes control characters).
if (mOperandValue == (byte) mOperandValue) {
IsFormatAsciiAllowed =
CharEncoding.IsPrintableLowOrHighAscii((byte)mOperandValue);
IsFormatPetsciiAllowed =
CharEncoding.IsPrintableC64Petscii((byte)mOperandValue);
IsFormatScreenCodeAllowed =
CharEncoding.IsPrintableC64ScreenCode((byte)mOperandValue);
} else {
IsFormatAsciiAllowed = IsFormatPetsciiAllowed = IsFormatScreenCodeAllowed =
false;
}
SymbolLabel = string.Empty;
FormatPartLow = true; // could default to high for MVN/MVP
FormatDefault = true; // if nothing better comes along
// Is there an operand format at this location? If not, we're done.
if (!mProject.OperandFormats.TryGetValue(mOffset, out FormatDescriptor dfd)) {
return;
}
// NOTE: it's entirely possible to have a weird format (e.g. string) if the
// instruction used to be tagged as code-stop. Handle it gracefully.
switch (dfd.FormatType) {
case FormatDescriptor.Type.NumericLE:
switch (dfd.FormatSubType) {
case FormatDescriptor.SubType.Hex:
FormatHex = true;
break;
case FormatDescriptor.SubType.Decimal:
FormatDecimal = true;
break;
case FormatDescriptor.SubType.Binary:
FormatBinary = true;
break;
case FormatDescriptor.SubType.Ascii:
case FormatDescriptor.SubType.HighAscii:
if (IsFormatAsciiAllowed) {
FormatAscii = true;
}
break;
case FormatDescriptor.SubType.C64Petscii:
if (IsFormatPetsciiAllowed) {
FormatPetscii = true;
}
break;
case FormatDescriptor.SubType.C64Screen:
if (IsFormatScreenCodeAllowed) {
FormatScreenCode = true;
}
break;
case FormatDescriptor.SubType.Symbol:
Debug.Assert(dfd.HasSymbol);
FormatSymbol = true;
switch (dfd.SymbolRef.ValuePart) {
case WeakSymbolRef.Part.Low:
FormatPartLow = true;
break;
case WeakSymbolRef.Part.High:
FormatPartHigh = true;
break;
case WeakSymbolRef.Part.Bank:
FormatPartBank = true;
break;
default:
Debug.Assert(false);
break;
}
SymbolLabel = Symbol.ConvertLabelForDisplay(dfd.SymbolRef.Label,
Symbol.LabelAnnotation.None, true, mFormatter);
break;
case FormatDescriptor.SubType.None:
default:
break;
}
break;
case FormatDescriptor.Type.NumericBE:
case FormatDescriptor.Type.StringGeneric:
case FormatDescriptor.Type.StringReverse:
case FormatDescriptor.Type.StringNullTerm:
case FormatDescriptor.Type.StringL8:
case FormatDescriptor.Type.StringL16:
case FormatDescriptor.Type.StringDci:
case FormatDescriptor.Type.Dense:
case FormatDescriptor.Type.Fill:
case FormatDescriptor.Type.Uninit:
case FormatDescriptor.Type.Junk:
default:
// Unexpected; used to be data?
break;
}
// In theory, if FormatDefault is still checked, we failed to find a useful match
// for the format descriptor. In practice, the radio button checkification stuff
// happens later. If we want to tell the user that there's a bad descriptor present,
// we'll need to track it locally, or test all known radio buttons for True.
}
/// <summary>
/// Creates a FormatDescriptor from the current state of the dialog controls.
/// </summary>
/// <returns>New FormatDescriptor. Will return null if the default format is
/// selected, or symbol is selected with an empty label.</returns>
private FormatDescriptor CreateDescriptorFromControls() {
int instructionLength = mProject.GetAnattrib(mOffset).Length;
if (FormatSymbol) {
if (string.IsNullOrEmpty(SymbolLabel)) {
// empty symbol --> default format (intuitive way to delete label reference)
return null;
}
WeakSymbolRef.Part part;
if (FormatPartLow) {
part = WeakSymbolRef.Part.Low;
} else if (FormatPartHigh) {
part = WeakSymbolRef.Part.High;
} else if (FormatPartBank) {
part = WeakSymbolRef.Part.Bank;
} else {
Debug.Assert(false);
part = WeakSymbolRef.Part.Low;
}
// Deal with non-unique labels. If the label refers to an existing
// symbol, use its label, which will have the tag. If the label doesn't
// have a match, discard it -- we don't support weak refs to ambiguous
// non-unique symbols.
string trimLabel = Symbol.TrimAndValidateLabel(SymbolLabel,
mFormatter.NonUniqueLabelPrefix, out bool isValid, out bool unused1,
out bool unused2, out bool hasNonUniquePrefix,
out Symbol.LabelAnnotation unused3);
if (isValid && hasNonUniquePrefix) {
if (LookupSymbol(trimLabel, hasNonUniquePrefix, out Symbol sym)) {
trimLabel = sym.Label;
} else {
Debug.WriteLine("Attempt to create ref to non-existant non-unique sym");
return null;
}
}
return FormatDescriptor.Create(instructionLength,
new WeakSymbolRef(trimLabel, part), false);
}
FormatDescriptor.SubType subType;
if (FormatDefault) {
return null;
} else if (FormatHex) {
subType = FormatDescriptor.SubType.Hex;
} else if (FormatDecimal) {
subType = FormatDescriptor.SubType.Decimal;
} else if (FormatBinary) {
subType = FormatDescriptor.SubType.Binary;
} else if (FormatAscii) {
if (mOperandValue > 0x7f) {
subType = FormatDescriptor.SubType.HighAscii;
} else {
subType = FormatDescriptor.SubType.Ascii;
}
} else if (FormatPetscii) {
subType = FormatDescriptor.SubType.C64Petscii;
} else if (FormatScreenCode) {
subType = FormatDescriptor.SubType.C64Screen;
} else {
Debug.Assert(false);
subType = FormatDescriptor.SubType.None;
}
return FormatDescriptor.Create(instructionLength,
FormatDescriptor.Type.NumericLE, subType);
}
#endregion Basic Format
#region Numeric References
public bool ShowNarNotAddress {
get { return mShowNarNotAddress; }
set { mShowNarNotAddress = value; OnPropertyChanged(); }
}
private bool mShowNarNotAddress;
public bool ShowNarEditLabel {
get { return mShowNarEditLabel; }
set { mShowNarEditLabel = value; OnPropertyChanged(); }
}
private bool mShowNarEditLabel;
public bool ShowNarCurrentLabel {
get { return mShowNarCurrentLabel; }
set { mShowNarCurrentLabel = value; OnPropertyChanged(); }
}
private bool mShowNarCurrentLabel;
public string NarLabelOffsetText {
get { return mNarLabelOffsetText; }
set { mNarLabelOffsetText = value; OnPropertyChanged(); }
}
private string mNarLabelOffsetText;
public string NarTargetLabel {
get { return mNarTargetLabel; }
set { mNarTargetLabel = value; OnPropertyChanged(); }
}
private string mNarTargetLabel;
public string CreateEditLabelText {
get { return mCreateEditLabelText; }
set { mCreateEditLabelText = value; OnPropertyChanged(); }
}
private string mCreateEditLabelText;
public bool ShowNarExternalSymbol {
get { return mShowNarExternalSymbol; }
set { mShowNarExternalSymbol = value; OnPropertyChanged(); }
}
private bool mShowNarExternalSymbol;
public bool ShowNarPlatformSymbol {
get { return mShowNarPlatformSymbol; }
set { mShowNarPlatformSymbol = value; OnPropertyChanged(); }
}
private bool mShowNarPlatformSymbol;
public string NarPlatformSymbol {
get { return mNarPlatformSymbol; }
set { mNarPlatformSymbol = value; OnPropertyChanged(); }
}
private string mNarPlatformSymbol;
public bool ShowNarNoProjectMatch {
get { return mShowNarNoProjectMatch; }
set { mShowNarNoProjectMatch = value; OnPropertyChanged(); }
}
private bool mShowNarNoProjectMatch;
public bool ShowNarProjectSymbol {
get { return mShowNarProjectSymbol; }
set { mShowNarProjectSymbol = value; OnPropertyChanged(); }
}
private bool mShowNarProjectSymbol;
public string NarProjectSymbol {
get { return mNarProjectSymbol; }
set { mNarProjectSymbol = value; OnPropertyChanged(); }
}
private string mNarProjectSymbol;
public string CreateEditProjectSymbolText {
get { return mCreateEditProjectSymbolText; }
set { mCreateEditProjectSymbolText = value; OnPropertyChanged(); }
}
private string mCreateEditProjectSymbolText;
public bool IsCopyToOperandEnabled {
get { return mIsCopyToOperandEnabled; }
set { mIsCopyToOperandEnabled = value; OnPropertyChanged(); }
}
private bool mIsCopyToOperandEnabled;
public bool IsDiddleLabelEnabled {
get { return mIsDiddleLabelEnabled; }
set { mIsDiddleLabelEnabled = value; OnPropertyChanged(); }
}
private bool mIsDiddleLabelEnabled;
/// <summary>
/// Edited label value. Will be null if the label hasn't been created, or has been
/// deleted (by entering a blank string in the label edit box).
/// </summary>
private Symbol mEditedLabel;
/// <summary>
/// Set to true if the label has been edited.
/// </summary>
private bool mLabelHasBeenEdited;
/// <summary>
/// Address associated with the label (for the Symbol's value).
/// </summary>
private int mLabelTargetAddress = -1;
/// <summary>
/// Offset of edited label.
/// </summary>
private int mEditedLabelOffset = -1;
/// <summary>
/// Edited project symbol. If a symbol already exists, this will be initialized to the
/// existing value. Otherwise this will be null.
/// </summary>
/// <remarks>
/// Project symbols can't be deleted from here, so a null reference always means that
/// there's no symbol and we haven't made an edit.
/// </remarks>
private DefSymbol mEditedProjectSymbol;
/// <summary>
/// Configures the UI in the Numeric Address Reference box at load time.
/// </summary>
private void NumericReferences_Loaded() {
SymbolEditOffsetResult = -1;
Anattrib attr = mProject.GetAnattrib(mOffset);
if (attr.OperandOffset >= 0) {
// Operand target is inside the file.
ShowNarEditLabel = true;
// Seek back to the start of the instruction or data item if the operand points
// into the middle of one. This is *not* the same as the "nearby" search,
// which will traverse multiple items to find a match.
// TODO: this can create a situation where the code list shows FUBAR-1 but we
// edit an earlier label, if the earlier label has a multi-byte format that
// includes the target address. (An example can be found in 20200-ui-edge-cases.)
mEditedLabelOffset =
DataAnalysis.GetBaseOperandOffset(mProject, attr.OperandOffset);
mLabelTargetAddress = mProject.GetAnattrib(mEditedLabelOffset).Address;
if (mProject.UserLabels.TryGetValue(mEditedLabelOffset, out Symbol sym)) {
// Has a label.
ShowNarCurrentLabel = true;
if (mEditedLabelOffset != attr.OperandOffset) {
NarLabelOffsetText = string.Format(CURRENT_LABEL_ADJUSTED_FMT,
mFormatter.FormatAdjustment(attr.OperandOffset - mEditedLabelOffset));
} else {
NarLabelOffsetText = CURRENT_LABEL;
}
NarTargetLabel = sym.GenerateDisplayLabel(mFormatter);
mEditedLabel = sym;
CreateEditLabelText = EDIT_LABEL;
} else {
NarLabelOffsetText = CURRENT_LABEL;
CreateEditLabelText = CREATE_LABEL;
}
} else if (attr.OperandAddress >= 0) {
ShowNarExternalSymbol = true;
// There can be multiple symbols with the same value, so we walk through the
// list and identify the first matching platform and project symbols. We're
// only interested in address symbols, not constants.
Symbol firstPlatform = null;
Symbol firstProject = null;
foreach (Symbol sym in mProject.SymbolTable) {
if (sym.Value == attr.OperandAddress && !sym.IsConstant) {
if (firstPlatform == null && sym.SymbolSource == Symbol.Source.Platform) {
firstPlatform = sym;
} else if (firstProject == null &&
sym.SymbolSource == Symbol.Source.Project) {
firstProject = sym;
}
if (firstPlatform != null && firstProject != null) {
break;
}
}
}
if (firstPlatform != null) {
ShowNarPlatformSymbol = true;
NarPlatformSymbol = firstPlatform.Label;
}
if (firstProject != null) {
ShowNarProjectSymbol = true;
NarProjectSymbol = firstProject.Label;
CreateEditProjectSymbolText = EDIT_PROJECT_SYMBOL;
mEditedProjectSymbol = (DefSymbol)firstProject;
OrigProjectSymbolResult = mEditedProjectSymbol;
} else {
ShowNarNoProjectMatch = true;
CreateEditProjectSymbolText = CREATE_PROJECT_SYMBOL;
}
} else {
// Probably an immediate operand.
ShowNarNotAddress = true;
}
}
private void UpdateCopyToOperand() {
IsCopyToOperandEnabled = false;
if (mEditedProjectSymbol != null) {
// We have a pre-existing or recently-edited symbol. See if the current
// operand configuration already matches.
if (!FormatSymbol || !mEditedProjectSymbol.Label.Equals(SymbolLabel)) {
IsCopyToOperandEnabled = true;
}
}
}
private void EditLabel_Click(object sender, RoutedEventArgs e) {
EditLabel dlg = new EditLabel(this, mEditedLabel, mLabelTargetAddress,
mEditedLabelOffset, mProject.SymbolTable, mFormatter);
if (dlg.ShowDialog() != true || mEditedLabel == dlg.LabelSym) {
Debug.WriteLine("No change to label, ignoring edit");
return;
}
mEditedLabel = dlg.LabelSym;
mLabelHasBeenEdited = true;
// Update UI to match current state.
if (mEditedLabel == null) {
ShowNarCurrentLabel = false;
CreateEditLabelText = CREATE_LABEL;
} else {
ShowNarCurrentLabel = true;
CreateEditLabelText = EDIT_LABEL;
NarTargetLabel = mEditedLabel.GenerateDisplayLabel(mFormatter);
}
// Sort of nice to just hit return twice after entering a label, so move the focus
// to the OK button.
okButton.Focus();
}
private void EditProjectSymbol_Click(object sender, RoutedEventArgs e) {
DefSymbol initVals = mEditedProjectSymbol;
if (initVals == null) {
// Need to start with a symbol so we can set the value field.
string symName = "SYM";
if (!string.IsNullOrEmpty(SymbolLabel) &&
Asm65.Label.ValidateLabel(SymbolLabel)) {
symName = SymbolLabel;
}
initVals = new DefSymbol(symName, mOperandValue, Symbol.Source.Project,
Symbol.Type.ExternalAddr, FormatDescriptor.SubType.None);
}
// Edit the symbol, locking the value so it can only apply to this operand.
// We want to pass in the symbol as it was before we made any edits, so that
// the user can always change the label back to what it was when this dialog
// was first opened.
EditDefSymbol dlg = new EditDefSymbol(this, mFormatter,
mProject.ProjectProps.ProjectSyms, OrigProjectSymbolResult, initVals, null,
false, true);
if (dlg.ShowDialog() != true) {
return;
}
Debug.Assert(dlg.NewSym != null); // can't delete a symbol from dialog
if (mEditedProjectSymbol == dlg.NewSym) {
Debug.WriteLine("No change to project symbol, ignoring edit");
return;
}
mEditedProjectSymbol = dlg.NewSym;
ShowNarProjectSymbol = true;
ShowNarNoProjectMatch = false;
NarProjectSymbol = mEditedProjectSymbol.Label;
CreateEditProjectSymbolText = EDIT_PROJECT_SYMBOL;
// The preview and symbol value display will use mEditedProjectSymbol if it's the
// only place the symbol exists, so we want to keep the other controls updated.
UpdateControls();
// Move the focus to the OK button.
okButton.Focus();
}
private void CopyToOperandButton_Click(object sender, RoutedEventArgs e) {
FormatSymbol = true;
SymbolLabel = mEditedProjectSymbol.Label;
IsCopyToOperandEnabled = false;
// changes to controls will call UpdateControls() for us
}
#endregion Numeric References
#region Local Variables
public bool ShowLvNotApplicable {
get { return mShowLvNotApplicable; }
set { mShowLvNotApplicable = value; OnPropertyChanged(); }
}
private bool mShowLvNotApplicable;
public bool ShowLvTableNotFound {
get { return mShowLvTableNotFound; }
set { mShowLvTableNotFound = value; OnPropertyChanged(); }
}
private bool mShowLvTableNotFound;
public bool ShowLvNoMatchFound {
get { return mShowLvNoMatchFound; }
set { mShowLvNoMatchFound = value; OnPropertyChanged(); }
}
private bool mShowLvNoMatchFound;
public bool ShowLvMatchFound {
get { return mShowLvMatchFound; }
set { mShowLvMatchFound = value; OnPropertyChanged(); }
}
private bool mShowLvMatchFound;
public string LvMatchFoundText {
get { return mLvMatchFoundText; }
set { mLvMatchFoundText = value; OnPropertyChanged(); }
}
private string mLvMatchFoundText;
public string LocalVariableLabel {
get { return mLocalVariableLabel; }
set { mLocalVariableLabel = value; OnPropertyChanged(); }
}
private string mLocalVariableLabel;
public bool ShowLvCreateEditButton {
get { return mShowLvCreateEditButton; }
set { mShowLvCreateEditButton = value; OnPropertyChanged(); }
}
private bool mShowLvCreateEditButton;
public string CreateEditLocalVariableText {
get { return mCreateEditLocalVariableText; }
set { mCreateEditLocalVariableText = value; OnPropertyChanged(); }
}
private string mCreateEditLocalVariableText;
/// <summary>
/// Offset of LocalVariableTable we're going to modify.
/// </summary>
private int mLvTableOffset = -1;
/// <summary>
/// Local variable value. If there's already a definition, this will be pre-filled
/// with the current contents. Otherwise it will be null.
/// </summary>
private DefSymbol mEditedLocalVar;
/// <summary>
/// Clone of original table, with local edits.
/// </summary>
private LocalVariableTable mEditedLvTable;
/// <summary>
/// Configures the UI in the Local Variable box at load time.
/// </summary>
private void LocalVariables_Loaded() {
if (!mOpDef.IsDirectPageInstruction && !mOpDef.IsStackRelInstruction) {
ShowLvNotApplicable = true;
return;
}
LvMatchFoundText = mOpDef.IsDirectPageInstruction ?
LV_MATCH_FOUND_ADDRESS : LV_MATCH_FOUND_CONSTANT;
LocalVariableLookup lvLookup =
new LocalVariableLookup(mProject.LvTables, mProject, null, false, false);
// If the operand is already a local variable, use whichever one the
// analyzer found.
Anattrib attr = mProject.GetAnattrib(mOffset);
if (attr.DataDescriptor != null && attr.DataDescriptor.HasSymbol &&
attr.DataDescriptor.SymbolRef.IsVariable) {
// Select the table that defines the local variable that's currently
// associated with this operand.
mLvTableOffset = lvLookup.GetDefiningTableOffset(mOffset,
attr.DataDescriptor.SymbolRef);
Debug.Assert(mLvTableOffset >= 0);
Debug.WriteLine("Symbol " + attr.DataDescriptor.SymbolRef +
" from var table at +" + mLvTableOffset.ToString("x6"));
} else {
// Operand is not a local variable. Find the closest table.
mLvTableOffset = lvLookup.GetNearestTableOffset(mOffset);
Debug.WriteLine("Closest table is at +" + mLvTableOffset.ToString("x6"));
}
if (mLvTableOffset < 0) {
ShowLvTableNotFound = true;
} else {
// Found a table. Do we have a matching symbol?
ShowLvCreateEditButton = true;
DefSymbol existingVar = lvLookup.GetSymbol(mOffset, mOperandValue,
mOpDef.IsDirectPageInstruction ?
Symbol.Type.ExternalAddr : Symbol.Type.Constant);
if (existingVar == null) {
ShowLvNoMatchFound = true;
CreateEditLocalVariableText = CREATE_LOCAL_VARIABLE;
} else {
mEditedLocalVar = lvLookup.GetOriginalForm(existingVar);
ShowLvMatchFound = true;
CreateEditLocalVariableText = EDIT_LOCAL_VARIABLE;
LocalVariableLabel = mEditedLocalVar.Label;
}
// We need to update the symbol table while we work to make the uniqueness
// check come out right. Otherwise if you edit, rename FOO to BAR,
// then edit again, you won't be able to rename BAR back to FOO because
// it's already in the list and it's not self.
//
// We don't need the full LVT, just the list of symbols, but we'll want
// to hand the modified table to the caller when we exit.
LocalVariableTable lvt = mProject.LvTables[mLvTableOffset];
mEditedLvTable = new LocalVariableTable(lvt);
}
}
private void EditLocalVariableButton_Click(object sender, RoutedEventArgs e) {
Debug.Assert(mOpDef.IsDirectPageInstruction || mOpDef.IsStackRelInstruction);
Debug.Assert(mLvTableOffset >= 0);
DefSymbol initialVar = mEditedLocalVar;
if (initialVar == null) {
Symbol.Type symType;
if (mOpDef.IsDirectPageInstruction) {
symType = Symbol.Type.ExternalAddr;
} else {
symType = Symbol.Type.Constant;
}
// We need to pre-load the value and type, but we can't create a symbol with
// an empty name. We don't really need to create something unique since the
// dialog will handle it.
initialVar = new DefSymbol("VAR", mOperandValue,
Symbol.Source.Variable, symType, Symbol.LabelAnnotation.None,
FormatDescriptor.SubType.None, 1, true, string.Empty,
DefSymbol.DirectionFlags.ReadWrite, null, string.Empty);
}
// Unlike project symbols, we don't pass the original symbol value in. This is
// because we update a local copy of the LV table immediately, and pass that in
// for the uniqueness checks.
EditDefSymbol dlg = new EditDefSymbol(this, mFormatter,
mEditedLvTable.GetSortedByLabel(), mEditedLocalVar, initialVar,
mProject.SymbolTable, true, true);
if (dlg.ShowDialog() == true) {
if (mEditedLocalVar != dlg.NewSym) {
// Integrate result. Future edits will start with this.
// We can't delete a symbol, just create or modify.
Debug.Assert(dlg.NewSym != null);
mEditedLocalVar = dlg.NewSym;
mEditedLvTable.AddOrReplace(dlg.NewSym);
LocalVariableLabel = mEditedLocalVar.Label;
CreateEditLocalVariableText = EDIT_LOCAL_VARIABLE;
ShowLvNoMatchFound = false;
ShowLvMatchFound = true;
} else {
Debug.WriteLine("No change to def symbol, ignoring edit");
}
okButton.Focus();
}
}
#endregion Local Variables
}
}