1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-25 05:29:31 +00:00

Instruction operand editor rework, part 1

Rearrange the UI elements, and convert the code-behind to a more
XAML-style form.  The basic stuff works, but the old "shortcut"
system is still in the process of being replaced.
This commit is contained in:
Andy McFadden 2019-09-07 13:39:22 -07:00
parent a3b968f025
commit 2633720c82
6 changed files with 613 additions and 465 deletions

View File

@ -718,6 +718,7 @@ namespace SourceGen {
#else
// Select "is printable" test. We use the extended version to include some
// control characters.
// TODO(maybe): require some *actually* printable characters in each string
CharEncoding.InclusionTest testPrintable;
FormatDescriptor.SubType baseSubType;
switch (mAnalysisParams.DefaultTextScanMode) {

View File

@ -1845,6 +1845,7 @@ namespace SourceGen {
}
private void EditInstructionOperand(int offset) {
#if false
EditInstructionOperand dlg = new EditInstructionOperand(mMainWin, offset,
mProject, mOutputFormatter);
@ -1912,7 +1913,21 @@ namespace SourceGen {
case WpfGui.EditInstructionOperand.SymbolShortcutAction.None:
break;
}
#else
EditInstructionOperand dlg = new EditInstructionOperand(mMainWin, mProject,
offset, mOutputFormatter);
if (dlg.ShowDialog() != true) {
return;
}
ChangeSet cs = new ChangeSet(1);
mProject.OperandFormats.TryGetValue(offset, out FormatDescriptor dfd);
if (dlg.FormatDescriptorResult != dfd) {
UndoableChange uc = UndoableChange.CreateOperandFormatChange(offset,
dfd, dlg.FormatDescriptorResult);
cs.Add(uc);
}
#endif
if (cs.Count != 0) {
ApplyUndoableChanges(cs);
}

View File

@ -836,7 +836,9 @@ namespace SourceGen {
}
operandValue = (int)(operandValue & mask);
if (sb.Length != symLabel.Length) {
// If we've added stuff, and we're going to add an adjustment later, stick
// an extra space in between for readability.
if (sb.Length != symLabel.Length && operandValue != symbolValue) {
sb.Append(' ');
}
} else {

View File

@ -109,9 +109,6 @@
<Compile Include="WpfGui\EditInstructionOperand.xaml.cs">
<DependentUpon>EditInstructionOperand.xaml</DependentUpon>
</Compile>
<Compile Include="WpfGui\EditInstructionOperand2.xaml.cs">
<DependentUpon>EditInstructionOperand2.xaml</DependentUpon>
</Compile>
<Compile Include="WpfGui\EditLabel.xaml.cs">
<DependentUpon>EditLabel.xaml</DependentUpon>
</Compile>
@ -269,10 +266,6 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WpfGui\EditInstructionOperand2.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WpfGui\EditLabel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@ -19,6 +19,7 @@ limitations under the License.
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:SourceGen.WpfGui"
mc:Ignorable="d"
Title="Edit Instruction Operand"
@ -26,72 +27,144 @@ limitations under the License.
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Loaded="Window_Loaded"
ContentRendered="Window_ContentRendered">
<StackPanel Margin="8">
<TextBlock Text="Select operand format:"/>
<RadioButton Name="defaultButton" GroupName="Format" Content="Default" Margin="0,2,0,0"
Checked="MainGroup_CheckedChanged"/>
<RadioButton Name="hexButton" GroupName="Format" Content="Hexadecimal" Margin="0,2,0,0"
Checked="MainGroup_CheckedChanged"/>
<RadioButton Name="decimalButton" GroupName="Format" Content="Decimal" Margin="0,2,0,0"
Checked="MainGroup_CheckedChanged"/>
<RadioButton Name="binaryButton" GroupName="Format" Content="Binary" Margin="0,2,0,0"
Checked="MainGroup_CheckedChanged"/>
<RadioButton Name="asciiButton" GroupName="Format" Content="ASCII (low or high) character" Margin="0,2,0,0"
Checked="MainGroup_CheckedChanged"/>
<RadioButton Name="petsciiButton" GroupName="Format" Content="C64 PETSCII character" Margin="0,2,0,0"
Checked="MainGroup_CheckedChanged"/>
<RadioButton Name="screenCodeButton" GroupName="Format" Content="C64 Screen character" Margin="0,2,0,0"
Checked="MainGroup_CheckedChanged"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<RadioButton Name="symbolButton" Grid.Column="0" Grid.Row="0" Margin="0,2,0,0"
GroupName="Format" Content="Symbol"
Checked="MainGroup_CheckedChanged"/>
<TextBox Name="symbolTextBox" Grid.Column="1" Grid.Row="0" Margin="8,1,0,0"
FontFamily="{StaticResource GeneralMonoFont}" TextChanged="SymbolTextBox_TextChanged"/>
<StackPanel Name="symbolPartPanel" Grid.Column="1" Grid.Row="1" Orientation="Horizontal" Margin="8,4,0,0">
<RadioButton Name="lowButton" GroupName="Part" Content="Low"
Checked="MainGroup_CheckedChanged"/>
<RadioButton Name="highButton" GroupName="Part" Content="High" Margin="8,0,0,0"
Checked="MainGroup_CheckedChanged"/>
<RadioButton Name="bankButton" GroupName="Part" Content="Bank" Margin="8,0,0,0"
Checked="MainGroup_CheckedChanged"/>
</StackPanel>
</Grid>
<DockPanel Margin="0,8,0,0">
<TextBlock DockPanel.Dock="Left" Text="Preview:"/>
<TextBox Name="previewTextBox" DockPanel.Dock="Right" IsReadOnly="True" Margin="8,1,0,0"
FontFamily="{StaticResource GeneralMonoFont}"/>
</DockPanel>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<GroupBox Name="symbolShortcutsGroupBox" Header="Symbol Shortcuts" Margin="0,12,0,0">
<system:String x:Key="str_SymbolNotUsed">N/A</system:String>
<system:String x:Key="str_SymbolNotValid">[invalid symbol name]</system:String>
<system:String x:Key="str_SymbolUnknown">?</system:String>
</Window.Resources>
<Grid Margin="8" Width="500">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="0,0,0,8"
Text="Select operand format:"/>
<StackPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="0,0,0,8">
<RadioButton GroupName="Main" Margin="0,2,0,0"
Content="Default (auto-format as hex, local var, or reference to label/platform/project symbol)"
IsChecked="{Binding FormatDefault}"/>
</StackPanel>
<GroupBox Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Padding="2,4" Header="Explicit Format">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<RadioButton GroupName="Main" Content="Hexadecimal"
IsChecked="{Binding FormatHex}"/>
<RadioButton GroupName="Main" Content="Decimal" Margin="0,2,0,0"
IsChecked="{Binding FormatDecimal}"/>
<RadioButton GroupName="Main" Content="Binary" Margin="0,2,0,0"
IsChecked="{Binding FormatBinary}"/>
<RadioButton GroupName="Main" Content="ASCII (low or high) character" Margin="0,2,0,0"
IsEnabled="{Binding IsFormatAsciiAllowed}" IsChecked="{Binding FormatAscii}"/>
<RadioButton GroupName="Main" Content="C64 PETSCII character" Margin="0,2,0,0"
IsEnabled="{Binding IsFormatPetsciiAllowed}" IsChecked="{Binding FormatPetscii}"/>
<RadioButton GroupName="Main" Content="C64 Screen character" Margin="0,2,0,0"
IsEnabled="{Binding IsFormatScreenCodeAllowed}" IsChecked="{Binding FormatScreenCode}"/>
</StackPanel>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<RadioButton Name="formatSymbolButton" Grid.Column="0" Grid.Row="0" GroupName="Main"
Content="Symbol" IsChecked="{Binding FormatSymbol}"/>
<StackPanel Grid.Column="0" Grid.Row="1" Orientation="Horizontal" Margin="18,1,0,0">
<TextBox Name="symbolTextBox" Grid.Column="0" Grid.Row="1" Width="150"
FontFamily="{StaticResource GeneralMonoFont}"
Text="{Binding SymbolLabel, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="(auto)" Foreground="Red" Margin="4,0,0,0"
Visibility="{Binding IsSymbolAuto, Converter={StaticResource BoolToVis}}"/>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="2" Orientation="Horizontal" Margin="18,4,0,0"
IsEnabled="{Binding IsChecked, ElementName=formatSymbolButton}">
<RadioButton GroupName="Part" Content="Low"
IsChecked="{Binding FormatPartLow}"/>
<RadioButton GroupName="Part" Content="High" Margin="10,0,0,0"
IsChecked="{Binding FormatPartHigh}"/>
<RadioButton GroupName="Part" Content="Bank" Margin="10,0,0,0"
IsChecked="{Binding FormatPartBank}"/>
</StackPanel>
<!-- show the symbol's value -->
<Grid Grid.Column="0" Grid.Row="3" Margin="18,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Value:" Margin="0,0,16,0"/>
<TextBlock Grid.Column="1" Grid.Row="0"
Text="{Binding SymbolValueHex, FallbackValue=$112233}"/>
<TextBlock Grid.Column="1" Grid.Row="1"
Text="{Binding SymbolValueDecimal, FallbackValue=16777215}"/>
</Grid>
</Grid>
</Grid>
</GroupBox>
<StackPanel Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="0,8">
<TextBlock Text="Preview:"/>
<TextBox IsReadOnly="True" Margin="8,1,0,0" Width="300"
FontFamily="{StaticResource GeneralMonoFont}" Text="{Binding PreviewText}"/>
</StackPanel>
<GroupBox Grid.Column="0" Grid.Row="4" Margin="0,4,0,0" Padding="2,4" Header="Numeric Address Reference">
<StackPanel>
<RadioButton Name="operandOnlyButton" GroupName="Shortcut" Margin="0,2,0,0"
Content="_Just set the operand (default)"/>
<RadioButton Name="labelInsteadButton" GroupName="Shortcut" Margin="0,2,0,0"
Content="_Create label at target address instead"/>
<TextBlock Text="Address is inside the project"/>
<TextBlock Text="Address is NOT the start of a thing"/>
<TextBlock Text="Label at target address: ______"/>
<Button Width="120" Content="Edit Label"/>
<RadioButton Name="operandAndLabelButton" GroupName="Shortcut" Margin="0,2,0,0"
Content="Set _operand AND create label at target address"/>
<RadioButton Name="operandAndProjButton" GroupName="Shortcut" Margin="0,2,0,0"
Content="Set operand AND create _project symbol"/>
<TextBlock Text="Address is outside the project"/>
<TextBlock Text="No matching platform / project symbol"/>
<TextBlock Text="Found matching {platform|project} symbol"/>
<TextBlock Text="[______] ($value)"/>
<Button Width="120" Content="Create/Edit Project Symbol"/>
</StackPanel>
</GroupBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,8,0,0">
<Button Name="okButton" Content="OK" IsDefault="True" Width="70"
IsEnabled="{Binding IsValid}" Click="OkButton_Click"/>
<Button Name="cancelButton" Content="Cancel" IsCancel="True"
Width="70" Margin="4,0,0,0"/>
</StackPanel>
<GroupBox Grid.Column="1" Grid.Row="4" Margin="0,4,0,0" Padding="2,4" Header="Local Variable">
<StackPanel>
<TextBlock Text="Operand is NOT ZP or stack relative"/>
<TextBlock Text="No local variable tables found before this point" TextWrapping="Wrap"/>
<TextBlock Text="No entry in nearest table"/>
<TextBlock Text="Match found: [VAR]"/>
<Button Width="120" Content="Create/Edit LV Entry"/>
</StackPanel>
</GroupBox>
</StackPanel>
<DockPanel Grid.Column="0" Grid.Row="5" Grid.ColumnSpan="2" Margin="0,16,0,0" LastChildFill="False">
<Button DockPanel.Dock="Right" Content="Cancel" Width="70" IsCancel="True" Margin="8,0,0,0"/>
<Button DockPanel.Dock="Right" Content="OK" Width="70" IsDefault="True"
IsEnabled="{Binding IsValid}" Click="OkButton_Click"/>
</DockPanel>
</Grid>
</Window>

View File

@ -26,78 +26,16 @@ using Asm65;
namespace SourceGen.WpfGui {
/// <summary>
/// Instruction operand editor.
///
/// This is a pretty direct port from WinForms.
/// </summary>
public partial class EditInstructionOperand : Window {
public partial class EditInstructionOperand : Window, INotifyPropertyChanged {
/// <summary>
/// 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.
/// Updated format descriptor. Will be null if the user selected "default".
/// </summary>
public FormatDescriptor FormatDescriptor { get; set; }
public FormatDescriptor FormatDescriptorResult { get; private set; }
public enum SymbolShortcutAction {
None = 0, CreateLabelInstead, CreateLabelAlso, CreateProjectSymbolAlso
}
/// <summary>
/// Remember the last option we used.
/// </summary>
private static SymbolShortcutAction sLastAction = SymbolShortcutAction.None;
/// <summary>
/// On OK dialog exit, specifies that an additional action should be taken.
/// </summary>
public SymbolShortcutAction ShortcutAction { get; private set; }
/// <summary>
/// Additional argument, meaning dependent on ShortcutAction. This will either be
/// the target label offset or the project symbol value.
/// </summary>
public int ShortcutArg { get; private set; }
/// <summary>
/// Width of full instruction, including opcode.
/// </summary>
private int mInstructionLength;
/// <summary>
/// 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.
/// </summary>
private int mPreviewHexDigits;
/// <summary>
/// Operand value, extracted from file data. For a relative branch, this will be
/// an address instead. Only used for preview window.
/// </summary>
private int mOperandValue;
/// <summary>
/// Is the operand an immediate value? If so, we enable the symbol part selection.
/// </summary>
private bool mIsExtendedImmediate;
/// <summary>
/// Is the operand a PC relative offset?
/// </summary>
private bool mIsPcRelative;
/// <summary>
/// Special handling for block move instructions (MVN/MVP).
/// </summary>
private bool mIsBlockMove;
/// <summary>
/// If set, show a '#' in the preview indow.
/// </summary>
private bool mShowHashPrefix;
///// <summary>
///// Symbol table to use when resolving symbolic values.
///// </summary>
//private SymbolTable SymbolTable { get; set; }
private readonly string SYMBOL_NOT_USED;
private readonly string SYMBOL_UNKNOWN;
private readonly string SYMBOL_INVALID;
/// <summary>
/// Project reference.
@ -105,305 +43,564 @@ namespace SourceGen.WpfGui {
private DisasmProject mProject;
/// <summary>
/// Formatter to use when displaying addresses and hex values.
/// Offset of instruction being edited.
/// </summary>
private int mOffset;
/// <summary>
/// Format object.
/// </summary>
private Formatter mFormatter;
/// <summary>
/// Copy of operand Anattribs.
/// Operation definition, from file data.
/// </summary>
private Anattrib mAttr;
private OpDef mOpDef;
/// <summary>
/// Set this during initial control configuration, so we know to ignore the CheckedChanged
/// events.
/// Status flags at the point where the instruction is defined. This tells us whether
/// an operand is 8-bit or 16-bit.
/// </summary>
private bool mIsInitialSetup;
private StatusFlags mOpStatusFlags;
/// <summary>
/// Set to true if the user has entered a symbol that matches an auto-generated symbol.
/// Operand value, extracted from file data. For a relative branch, this will be
/// an address instead.
/// </summary>
private bool mIsSymbolAuto;
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));
}
public EditInstructionOperand(Window owner, int offset, DisasmProject project,
Asm65.Formatter formatter) {
/// <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;
// 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) {
SYMBOL_NOT_USED = (string)FindResource("str_SymbolNotUsed");
SYMBOL_INVALID = (string)FindResource("str_SymbolNotValid");
SYMBOL_UNKNOWN = (string)FindResource("str_SymbolUnknown");
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 = 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;
}
mOperandValue = attr.OperandAddress;
} 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;
}
// For BlockMove this will have both parts.
mOperandValue = mOpDef.GetOperand(project.FileData, offset, attr.StatusFlags);
}
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 a character? We only allow the printable set
// here, not the extended set (which includes control characters).
if (mOperandValue == (byte) mOperandValue) {
asciiButton.IsEnabled =
CharEncoding.IsPrintableLowOrHighAscii((byte)mOperandValue);
petsciiButton.IsEnabled =
CharEncoding.IsPrintableC64Petscii((byte)mOperandValue);
screenCodeButton.IsEnabled =
CharEncoding.IsPrintableC64ScreenCode((byte)mOperandValue);
} else {
asciiButton.IsEnabled = petsciiButton.IsEnabled = screenCodeButton.IsEnabled =
false;
}
// 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();
BasicFormat_Loaded();
mLoadDone = true;
}
private void Window_ContentRendered(object sender, EventArgs e) {
// Start with the focus in the text box. This way they can start typing
// immediately.
UpdateControls();
symbolTextBox.SelectAll();
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();
}
/// <summary>
/// Handles Checked/Unchecked events for all radio buttons in main group.
/// </summary>
private void MainGroup_CheckedChanged(object sender, RoutedEventArgs e) {
// Enable/disable the low/high/bank radio group.
// Update preview window.
UpdateControls();
}
/// <summary>
/// Handles Checked/Unchecked events for all radio buttons in symbol-part group.
/// </summary>
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;
}
FormatDescriptorResult = CreateDescriptorFromControls();
DialogResult = true;
}
/// <summary>
/// Updates all of the controls to reflect the current internal state.
/// Updates the state of the UI controls as the user interacts with the dialog.
/// </summary>
private void UpdateControls() {
if (mIsInitialSetup) {
if (!mLoadDone) {
return;
}
symbolPartPanel.IsEnabled = (symbolButton.IsChecked == true && mIsExtendedImmediate);
symbolShortcutsGroupBox.IsEnabled = symbolButton.IsChecked == true;
SetPreviewText();
// Parts panel IsEnabled depends directly on formatSymbolButton.IsChecked.
IsValid = true;
IsSymbolAuto = false;
SymbolValueDecimal = string.Empty;
if (FormatSymbol) {
if (!Asm65.Label.ValidateLabel(SymbolLabel)) {
SymbolValueHex = SYMBOL_INVALID;
IsValid = false;
} else if (mProject.SymbolTable.TryGetValue(SymbolLabel, 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.
IsValid = false;
IsSymbolAuto = true;
}
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;
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.
SymbolValueHex = SYMBOL_UNKNOWN;
}
ConfigureSymbolShortcuts();
} else {
SymbolValueHex = SYMBOL_NOT_USED;
}
okButton.IsEnabled = isOk;
UpdatePreview();
}
/// <summary>
/// Sets the text displayed in the "preview" text box.
/// Updates the contents of the preview text box.
/// </summary>
private void SetPreviewText() {
//symbolValueLabel.Text = string.Empty;
mIsSymbolAuto = false;
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) {
// Default format. We can't actually know what this look like, so just
// clear the box.
previewTextBox.Text = string.Empty;
// 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;
}
if (dfd.FormatSubType == FormatDescriptor.SubType.Symbol &&
string.IsNullOrEmpty(dfd.SymbolRef.Label)) {
// no label yet, nothing to show
previewTextBox.Text = 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('#');
}
StringBuilder preview = new StringBuilder();
if (mShowHashPrefix) {
preview.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) {
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:
preview.Append(mFormatter.FormatHexValue(mOperandValue, mPreviewHexDigits));
sb.Append(mFormatter.FormatHexValue(operandValue, previewHexDigits));
break;
case FormatDescriptor.SubType.Decimal:
preview.Append(mFormatter.FormatDecimalValue(mOperandValue));
sb.Append(mFormatter.FormatDecimalValue(operandValue));
break;
case FormatDescriptor.SubType.Binary:
preview.Append(mFormatter.FormatBinaryValue(mOperandValue, 8));
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);
preview.Append(mFormatter.FormatCharacterValue(mOperandValue, enc));
sb.Append(mFormatter.FormatCharacterValue(operandValue, enc));
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();
}
// 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 && mIsPcRelative) {
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 (mIsPcRelative) {
if (isPcRelative) {
flags = PseudoOp.FormatNumericOpFlags.IsPcRel;
} else if (mShowHashPrefix) {
} else if (showHashPrefix) {
flags = PseudoOp.FormatNumericOpFlags.HasHashPrefix;
} else {
flags = PseudoOp.FormatNumericOpFlags.None;
}
string str = PseudoOp.FormatNumericOperand(mFormatter,
mProject.SymbolTable, null, dfd,
mOperandValue, operandLen, flags);
preview.Append(str);
operandValue, operandLen, flags);
sb.Append(str);
if (sym.SymbolSource == Symbol.Source.Auto) {
mIsSymbolAuto = true;
}
} else {
preview.Append(dfd.SymbolRef.Label + " (?)");
sb.Append(dfd.SymbolRef.Label + " (?)");
Debug.Assert(!string.IsNullOrEmpty(dfd.SymbolRef.Label));
//symbolValueLabel.Text = Properties.Resources.MSG_SYMBOL_NOT_FOUND;
}
break;
default:
Debug.Assert(false);
preview.Append("BUG");
sb.Append("BUG");
break;
}
previewTextBox.Text = preview.ToString();
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;
}
UpdateControls();
}
}
private string mSymbolLabel;
public bool IsSymbolAuto {
get { return mIsSymbolAuto; }
set { mIsSymbolAuto = value; OnPropertyChanged(); }
}
private bool mIsSymbolAuto;
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 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 hinted as data. 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 = dfd.SymbolRef.Label;
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:
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;
}
return FormatDescriptor.Create(instructionLength,
new WeakSymbolRef(SymbolLabel, 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
#if false
/// <summary>
/// Configures the buttons in the "symbol shortcuts" group box. The entire box is
/// disabled unless "symbol" is selected. Other options are selectively enabled or
@ -456,139 +653,6 @@ namespace SourceGen.WpfGui {
operandOnlyButton.IsChecked = true;
}
}
/// <summary>
/// Configures the dialog controls based on the provided format descriptor.
/// </summary>
/// <param name="dfd">FormatDescriptor to use.</param>
private void SetControlsFromDescriptor(FormatDescriptor dfd) {
Debug.Assert(mIsInitialSetup);
lowButton.IsChecked = true;
if (dfd == null) {
defaultButton.IsChecked = true;
return;
}
// NOTE: it's entirely possible to have a weird format (e.g. string) if the
// instruction used to be hinted as data. Handle it gracefully.
switch (dfd.FormatType) {
case FormatDescriptor.Type.NumericLE:
switch (dfd.FormatSubType) {
case FormatDescriptor.SubType.Hex:
hexButton.IsChecked = true;
break;
case FormatDescriptor.SubType.Decimal:
decimalButton.IsChecked = true;
break;
case FormatDescriptor.SubType.Binary:
binaryButton.IsChecked = true;
break;
case FormatDescriptor.SubType.Ascii:
case FormatDescriptor.SubType.HighAscii:
asciiButton.IsChecked = true;
break;
case FormatDescriptor.SubType.C64Petscii:
petsciiButton.IsChecked = true;
break;
case FormatDescriptor.SubType.C64Screen:
screenCodeButton.IsChecked = true;
break;
case FormatDescriptor.SubType.Symbol:
Debug.Assert(dfd.HasSymbol);
symbolButton.IsChecked = true;
switch (dfd.SymbolRef.ValuePart) {
case WeakSymbolRef.Part.Low:
lowButton.IsChecked = true;
break;
case WeakSymbolRef.Part.High:
highButton.IsChecked = true;
break;
case WeakSymbolRef.Part.Bank:
bankButton.IsChecked = true;
break;
default:
Debug.Assert(false);
break;
}
symbolTextBox.Text = dfd.SymbolRef.Label;
break;
case FormatDescriptor.SubType.None:
default:
// Unexpected; call it hex.
hexButton.IsChecked = true;
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:
default:
// Unexpected; used to be data?
defaultButton.IsChecked = true;
break;
}
}
/// <summary>
/// Creates a FormatDescriptor from the current state of the dialog controls.
/// </summary>
/// <returns>New FormatDescriptor.</returns>
private FormatDescriptor CreateDescriptorFromControls() {
if (symbolButton.IsChecked == true) {
if (string.IsNullOrEmpty(symbolTextBox.Text)) {
// empty symbol --> default format (intuitive way to delete label reference)
return null;
}
WeakSymbolRef.Part part;
if (lowButton.IsChecked == true) {
part = WeakSymbolRef.Part.Low;
} else if (highButton.IsChecked == true) {
part = WeakSymbolRef.Part.High;
} else if (bankButton.IsChecked == true) {
part = WeakSymbolRef.Part.Bank;
} else {
Debug.Assert(false);
part = WeakSymbolRef.Part.Low;
}
return FormatDescriptor.Create(mInstructionLength,
new WeakSymbolRef(symbolTextBox.Text, part), false);
}
FormatDescriptor.SubType subType;
if (defaultButton.IsChecked == true) {
return null;
} else if (hexButton.IsChecked == true) {
subType = FormatDescriptor.SubType.Hex;
} else if (decimalButton.IsChecked == true) {
subType = FormatDescriptor.SubType.Decimal;
} else if (binaryButton.IsChecked == true) {
subType = FormatDescriptor.SubType.Binary;
} else if (asciiButton.IsChecked == true) {
if (mOperandValue > 0x7f) {
subType = FormatDescriptor.SubType.HighAscii;
} else {
subType = FormatDescriptor.SubType.Ascii;
}
} else if (petsciiButton.IsChecked == true) {
subType = FormatDescriptor.SubType.C64Petscii;
} else if (screenCodeButton.IsChecked == true) {
subType = FormatDescriptor.SubType.C64Screen;
} else if (symbolButton.IsChecked == true) {
subType = FormatDescriptor.SubType.Symbol;
} else {
Debug.Assert(false);
subType = FormatDescriptor.SubType.None;
}
return FormatDescriptor.Create(mInstructionLength,
FormatDescriptor.Type.NumericLE, subType);
}
#endif
}
}