/* * 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.Windows; using Asm65; using CommonUtil; namespace SourceGen.WpfGui { /// /// Edit Address Region dialog. /// public partial class EditAddress : Window, INotifyPropertyChanged { private const string NON_ADDR_STR = "NA"; /// /// Updated address map entry. Will be null if we want to delete the existing entry. /// public AddressMap.AddressMapEntry ResultEntry { get; private set; } /// /// Dialog header. /// public string OperationStr { get { return mOperationStr; } set { mOperationStr = value; OnPropertyChanged(); } } private string mOperationStr; /// /// Offset of first selected byte. (Does not change.) /// public string RegionStartOffsetStr { get { return mFormatter.FormatOffset24(mRegionStartOffset); } } private int mRegionStartOffset; /// /// Offset of last selected byte. (Does not change.) /// public string RegionEndOffsetStr { get { return mFormatter.FormatOffset24(mRegionEndOffset); } } private int mRegionEndOffset; public string RegionLengthStr { get { int count = mRegionEndOffset - mRegionStartOffset + 1; return FormatLength(count); } } /// /// Set to true to show the offset/length stats for a current region. /// public bool ShowExistingRegion { get { return mShowExistingRegion; } set { mShowExistingRegion = value; OnPropertyChanged(); } } private bool mShowExistingRegion; public bool ShowOption1 { get { return mShowOption1; } set { mShowOption1 = value; OnPropertyChanged(); } } private bool mShowOption1; public bool ShowOption2 { get { return mShowOption2; } set { mShowOption2 = value; OnPropertyChanged(); } } private bool mShowOption2; public bool EnableOption1 { get { return mEnableOption1; } set { mEnableOption1 = value; OnPropertyChanged(); } } private bool mEnableOption1; public bool EnableOption2 { get { return mEnableOption2; } set { mEnableOption2 = value; OnPropertyChanged(); } } private bool mEnableOption2; public bool CheckOption1 { get { return mCheckOption1; } set { mCheckOption1 = value; OnPropertyChanged(); } } private bool mCheckOption1; public bool CheckOption2 { get { return mCheckOption2; } set { mCheckOption2 = value; OnPropertyChanged(); } } private bool mCheckOption2; public string Option1Str { get { return mOption1Str; } set { mOption1Str = value; OnPropertyChanged(); } } private string mOption1Str; public string Option2Str { get { return mOption2Str; } set { mOption2Str = value; OnPropertyChanged(); } } private string mOption2Str; /// /// Address at which a pre-label would be placed. This is determined by the parent /// region, so its value is fixed. /// private int mPreLabelAddress; public string PreLabelAddressStr { get { if (mPreLabelAddress == AddressMap.NON_ADDR) { return "NA"; } else { return "$" + mFormatter.FormatAddress(mPreLabelAddress, mShowBank); } } } private bool mShowBank; public bool ShowErrorMessage { get { return mShowErrorMessage; } set { mShowErrorMessage = value; OnPropertyChanged(); } } private bool mShowErrorMessage; public string ErrorMessageStr { get { return mErrorMessageStr; } set { mErrorMessageStr = value; OnPropertyChanged(); } } private string mErrorMessageStr; /// /// Address input TextBox. /// public string AddressText { get { return mAddressText; } set { mAddressText = value; OnPropertyChanged(); UpdateControls(); } } private string mAddressText; public bool UseRelativeAddressing { get { return mUseRelativeAddressing; } set { mUseRelativeAddressing = value; OnPropertyChanged(); UpdateControls(); } } private bool mUseRelativeAddressing; /// /// Pre-label input TextBox. /// public string PreLabelText { get { return mPreLabelText; } set { mPreLabelText = value; OnPropertyChanged(); UpdateControls(); } } private string mPreLabelText; /// /// Set to true when input is valid. Controls whether the OK button is enabled. /// public bool IsValid { get { return mIsValid; } set { mIsValid = value; OnPropertyChanged(); } } private bool mIsValid; /// /// Set to true unless there are no valid options (e.g. invalid new region). /// public bool EnableAttributeControls { get { return mEnableAttributeControls; } set { mEnableAttributeControls = value; OnPropertyChanged(); } } private bool mEnableAttributeControls; /// /// Set to true if the region has a floating end point. /// public bool IsFloating { get { return mIsFloating; } set { mIsFloating = value; OnPropertyChanged(); } } private bool mIsFloating; /// /// Set to true if the region is not new, and thus can be deleted. /// public bool CanDeleteRegion { get { return mCanDeleteRegion; } set { mCanDeleteRegion = value; OnPropertyChanged(); } } private bool mCanDeleteRegion; // INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// /// Result for option #1. /// private AddressMap.AddressMapEntry mResultEntry1; /// /// Result for option #2. /// private AddressMap.AddressMapEntry mResultEntry2; /// /// Maximum allowed address value, based on CPU type. /// private int mMaxAddressValue; /// /// Reference to project. We need the address map and symbol table. /// private DisasmProject mProject; /// /// Reference to text formatter. /// private Formatter mFormatter; /// /// Constructor. /// /// Parent window. /// Current region; will be null for new entries. /// Prototype entry to create. /// Length, in bytes, of the selection. /// True if the selection is a single line. /// Project reference. /// Text formatter object. public EditAddress(Window owner, AddressMap.AddressRegion curRegion, AddressMap.AddressMapEntry newEntry, int selectionLen, bool isSingleLine, DisasmProject project, Formatter formatter) { InitializeComponent(); Owner = owner; DataContext = this; Debug.Assert((curRegion == null) ^ (newEntry == null)); // exactly one must be true mProject = project; mMaxAddressValue = project.CpuDef.MaxAddressValue; mShowBank = !project.CpuDef.HasAddr16; mFormatter = formatter; Configure(curRegion, newEntry, selectionLen, isSingleLine); UpdateControls(); } private void Configure(AddressMap.AddressRegion curRegion, AddressMap.AddressMapEntry newEntry, int selectionLen, bool isSingleLine) { Debug.WriteLine("Configuring AR: reg=" + curRegion + " newEnt=" + newEntry + " selLen=" + selectionLen + " isSingle=" + isSingleLine); ShowOption1 = ShowOption2 = true; EnableOption1 = EnableOption2 = true; CheckOption1 = true; EnableAttributeControls = true; if (curRegion != null) { // Editing an existing region. CanDeleteRegion = true; ShowExistingRegion = true; AddressText = Asm65.Address.AddressToString(curRegion.Address, false); PreLabelText = curRegion.PreLabel; UseRelativeAddressing = curRegion.IsRelative; OperationStr = (string)FindResource("str_HdrEdit"); mRegionStartOffset = curRegion.Offset; mRegionEndOffset = curRegion.Offset + curRegion.ActualLength - 1; mPreLabelAddress = curRegion.PreLabelAddress; if (isSingleLine) { // Only thing selected was .arstart/.arend. First action is to edit // the region properties, second action is to convert floating end // to fixed. mResultEntry1 = new AddressMap.AddressMapEntry(curRegion.Offset, curRegion.Length, curRegion.Address, curRegion.PreLabel, curRegion.IsRelative); Option1Str = (string)FindResource("str_OptEditAsIs"); Option2Str = (string)FindResource("str_OptEditAndFix"); if (curRegion.IsFloating) { mResultEntry2 = new AddressMap.AddressMapEntry(curRegion.Offset, curRegion.ActualLength, curRegion.Address, curRegion.PreLabel, curRegion.IsRelative); } else { mResultEntry2 = null; EnableOption2 = false; // show it, but disabled } } else { // Selection started with .arstart and included multiple lines. First // action is to resize region. Second action is edit without resize. // If resize is illegal (e.g. new region exactly overlaps another), // first action is disabled. mResultEntry1 = new AddressMap.AddressMapEntry(curRegion.Offset, selectionLen, curRegion.Address, curRegion.PreLabel, curRegion.IsRelative); mResultEntry2 = new AddressMap.AddressMapEntry(curRegion.Offset, curRegion.Length, curRegion.Address, curRegion.PreLabel, curRegion.IsRelative); string fmt = (string)FindResource("str_OptResize"); Option1Str = string.Format(fmt, mFormatter.FormatOffset24(curRegion.Offset + selectionLen - 1), FormatLength(selectionLen)); Option2Str = (string)FindResource("str_OptEditAsIs"); Debug.Assert(selectionLen > 0); AddressMap.AddResult ares; TryCreateRegion(curRegion, curRegion.Offset, selectionLen, curRegion.Address, out ares); if (ares != AddressMap.AddResult.Okay) { // Can't create the new region, so disable that option (still visible). EnableOption1 = false; CheckOption2 = true; } } } else { // Creating a new region. Prototype entry specifies offset, length, and address. // First action is to create a fixed-length region, second action is to create // a floating region. Default changes for single-item selections. CanDeleteRegion = false; ShowExistingRegion = false; AddressText = Asm65.Address.AddressToString(newEntry.Address, false); PreLabelText = string.Empty; UseRelativeAddressing = false; OperationStr = (string)FindResource("str_HdrCreate"); AddressMap.AddResult ares1; AddressMap.AddressRegion newRegion1 = TryCreateRegion(null, newEntry.Offset, newEntry.Length, newEntry.Address, out ares1); AddressMap.AddResult ares2; AddressMap.AddressRegion newRegion2 = TryCreateRegion(null, newEntry.Offset, AddressMap.FLOATING_LEN, newEntry.Address, out ares2); if (isSingleLine) { // For single-line selection, create a floating region by default. CheckOption2 = true; } // If it failed, report the error. Most common reason will be a start offset // that overlaps an existing region. You can create a fixed region inside // a fixed region with the same start offset, but can't create a float there. if (ares1 == AddressMap.AddResult.Okay) { mResultEntry1 = new AddressMap.AddressMapEntry(newEntry.Offset, newRegion1.ActualLength, newEntry.Address, string.Empty, false); string fmt = (string)FindResource("str_CreateFixed"); Option1Str = string.Format(fmt, mFormatter.FormatOffset24(newEntry.Offset), FormatLength(newRegion1.ActualLength)); mPreLabelAddress = newRegion1.PreLabelAddress; } else { Option1Str = (string)FindResource("str_CreateFixedFail"); CheckOption2 = true; EnableOption1 = false; } if (ares2 == AddressMap.AddResult.Okay) { mResultEntry2 = new AddressMap.AddressMapEntry(newEntry.Offset, AddressMap.FLOATING_LEN, newEntry.Address, string.Empty, false); string fmt = (string)FindResource("str_CreateFloating"); Option2Str = string.Format(fmt, mFormatter.FormatOffset24(newEntry.Offset), FormatLength(newRegion2.ActualLength)); mPreLabelAddress = newRegion2.PreLabelAddress; } else { Option2Str = (string)FindResource("str_CreateFloatingFail"); CheckOption1 = true; CheckOption2 = false; // required for some reason EnableOption2 = false; } if (ares1 != AddressMap.AddResult.Okay && ares2 != AddressMap.AddResult.Okay) { // Unable to create region here. Explain why not. EnableAttributeControls = false; CheckOption1 = CheckOption2 = false; mPreLabelAddress = AddressMap.NON_ADDR; SetErrorString(ares1); } } } private string FormatLength(int len) { return len + " (" + mFormatter.FormatHexValue(len, 2) + ")"; } private AddressMap.AddressRegion TryCreateRegion(AddressMap.AddressRegion delRegion, int offset, int length, int addr, out AddressMap.AddResult result) { AddressMap tmpMap = mProject.AddrMap.Clone(); if (delRegion != null && !tmpMap.RemoveEntry(delRegion.Offset, delRegion.Length)) { Debug.Assert(false, "Failed to remove existing region"); result = AddressMap.AddResult.InternalError; return null; } result = tmpMap.AddEntry(offset, length, addr); if (result != AddressMap.AddResult.Okay) { return null; } AddressMap.AddressRegion newRegion = tmpMap.FindRegion(offset, length); if (newRegion == null) { // Shouldn't happen. Debug.Assert(false, "Failed to find region we just created"); result = AddressMap.AddResult.InternalError; return null; } return newRegion; } private void SetErrorString(AddressMap.AddResult result) { string rsrc; switch (result) { case AddressMap.AddResult.InternalError: rsrc = "str_ErrInternal"; break; case AddressMap.AddResult.InvalidValue: rsrc = "str_ErrInvalidValue"; break; case AddressMap.AddResult.OverlapExisting: rsrc = "str_ErrOverlapExisting"; break; case AddressMap.AddResult.OverlapFloating: rsrc = "str_ErrOverlapFloating"; break; case AddressMap.AddResult.StraddleExisting: rsrc = "str_ErrStraddelExisting"; break; default: Debug.Assert(false); rsrc = "str_ErrInternal"; break; } ErrorMessageStr = (string)FindResource(rsrc); // throws exception on failure ShowErrorMessage = true; } private void Window_ContentRendered(object sender, EventArgs e) { addrTextBox.SelectAll(); addrTextBox.Focus(); } /// /// Handles a TextChanged event on the address text box. /// /// /// Must have UpdateSourceTrigger=PropertyChanged set for this to work. The default /// for TextBox is LostFocus. /// private void UpdateControls() { IsValid = EnableAttributeControls && ParseAddress(out int unused); // TODO(org): check pre-label syntax } private void OkButton_Click(object sender, RoutedEventArgs e) { bool ok = ParseAddress(out int addr); Debug.Assert(ok); AddressMap.AddressMapEntry baseEntry; if (CheckOption1) { baseEntry = mResultEntry1; } else { baseEntry = mResultEntry2; } // Combine base entry with pre-label string and relative addressing checkbox. ResultEntry = new AddressMap.AddressMapEntry(baseEntry.Offset, baseEntry.Length, addr, PreLabelText, UseRelativeAddressing); Debug.WriteLine("Dialog result: " + ResultEntry); DialogResult = true; } /// /// Parses the address out of the AddressText text box. /// /// Receives the parsed address. Will be NON_ADDR for "NA". /// True if the string parsed successfully. private bool ParseAddress(out int addr) { // "NA" for non-addressable? string upper = AddressText.ToUpper(); if (upper == NON_ADDR_STR) { addr = AddressMap.NON_ADDR; return true; } // Parse numerically. return Asm65.Address.ParseAddress(AddressText, mMaxAddressValue, out addr); } private void DeleteRegion_Click(object sender, RoutedEventArgs e) { ResultEntry = null; DialogResult = true; } } }