/*
* 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 {
///
/// 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;
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 "