/* * 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.Collections; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using Asm65; using CommonWPF; namespace SourceGen.WpfGui { /// /// Edit a LocalVariableTable. /// public partial class EditLocalVariableTable : Window, INotifyPropertyChanged { /// /// Output. Will be null if the table was deleted, or if cancel was hit while /// creating a new table. /// public LocalVariableTable NewTable { get; private set; } /// /// Output. If the table was moved, the new offset will be different from the old. /// public int NewOffset { get; private set; } // Item for the symbol list view ItemsSource. public class FormattedSymbol { public string Label { get; private set; } public string Value { get; private set; } public string Type { get; private set; } public int Width { get; private set; } public string Comment { get; private set; } public DefSymbol DefSym; public FormattedSymbol(DefSymbol defSym, string label, string value, string type, int width, string comment) { Label = label; Value = value; Type = type; Width = width; Comment = comment; DefSym = defSym; } } // List of items. The Label is guaranteed to be unique. public ObservableCollection Variables { get; private set; } = new ObservableCollection(); /// /// Clear-previous flag. /// public bool ClearPrevious { get { return mWorkTable.ClearPrevious; } set { mWorkTable.ClearPrevious = value; OnPropertyChanged(); } } /// /// True if this is not a new table. (Using "not" because that's the sense we /// need in XAML.) /// public bool IsNotNewTable { get { return mIsNotNewTable; } set { mIsNotNewTable = value; OnPropertyChanged(); } } private bool mIsNotNewTable; /// /// Table header text string, formatted at load time. /// public string TableHeaderText { get { return mTableHeaderText; } set { mTableHeaderText = value; OnPropertyChanged(); } } private string mTableHeaderText; /// /// Working set. Used internally to hold state. /// private LocalVariableTable mWorkTable; /// /// Project reference. /// private DisasmProject mProject; /// /// Format object to use when formatting addresses and constants. /// private Formatter mFormatter; /// /// Symbol table for uniqueness check. /// private SymbolTable mSymbolTable; /// /// Table offset, for move ops. /// private int mOffset; // INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// /// Constructor. lvt will be null when creating a new entry. /// public EditLocalVariableTable(Window owner, DisasmProject project, Formatter formatter, LocalVariableTable lvt, int offset) { InitializeComponent(); Owner = owner; DataContext = this; mProject = project; mFormatter = formatter; mSymbolTable = project.SymbolTable; mOffset = NewOffset = offset; if (lvt != null) { mWorkTable = new LocalVariableTable(lvt); mIsNotNewTable = true; } else { mWorkTable = new LocalVariableTable(); } for (int i = 0; i < mWorkTable.Count; i++) { DefSymbol defSym = mWorkTable[i]; Variables.Add(CreateFormattedSymbol(defSym)); } } public void Window_Loaded(object sender, RoutedEventArgs e) { string fmt = (string)FindResource("str_TableHeaderFmt"); TableHeaderText = string.Format(fmt, mFormatter.FormatOffset24(mOffset)); UpdateControls(); } /// /// Loads entries from the work table into the items source. /// private FormattedSymbol CreateFormattedSymbol(DefSymbol defSym) { string typeStr; if (defSym.IsConstant) { typeStr = Res.Strings.ABBREV_STACK_RELATIVE; } else { typeStr = Res.Strings.ABBREV_ADDRESS; } FormattedSymbol fsym = new FormattedSymbol( defSym, defSym.GenerateDisplayLabel(mFormatter), mFormatter.FormatValueInBase(defSym.Value, defSym.DataDescriptor.NumBase), typeStr, defSym.DataDescriptor.Length, defSym.Comment); return fsym; } /// /// Handles a Sorting event. We need to sort by numeric value on the Value field, but /// we want to use custom-formatted numeric values in multiple bases. /// private void SymbolsList_Sorting(object sender, DataGridSortingEventArgs e) { DataGridColumn col = e.Column; // Set the SortDirection to a specific value. If we don't do this, SortDirection // remains un-set, and the column header doesn't show up/down arrows or change // direction when clicked twice. ListSortDirection direction = (col.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending; col.SortDirection = direction; bool isAscending = direction != ListSortDirection.Descending; IComparer comparer = new SymbolsListComparer(col.DisplayIndex, isAscending); ListCollectionView lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(symbolsList.ItemsSource); lcv.CustomSort = comparer; e.Handled = true; } private class SymbolsListComparer : IComparer { // Must match order of items in DataGrid. DataGrid must not allow columns to be // reordered. We could check col.Header instead, but then we have to assume that // the Header is a string that doesn't get renamed. private enum SortField { Label = 0, Value = 1, Type = 2, Width = 3, Comment = 4 } private SortField mSortField; private bool mIsAscending; public SymbolsListComparer(int displayIndex, bool isAscending) { Debug.Assert(displayIndex >= 0 && displayIndex <= 4); mIsAscending = isAscending; mSortField = (SortField)displayIndex; } // IComparer interface public int Compare(object o1, object o2) { FormattedSymbol fsym1 = (FormattedSymbol)o1; FormattedSymbol fsym2 = (FormattedSymbol)o2; // Sort primarily by specified field, secondarily by label (which should // be unique). int cmp; switch (mSortField) { case SortField.Label: cmp = string.Compare(fsym1.Label, fsym2.Label); break; case SortField.Value: cmp = fsym1.DefSym.Value - fsym2.DefSym.Value; break; case SortField.Type: cmp = string.Compare(fsym1.Type, fsym2.Type); break; case SortField.Width: cmp = fsym1.Width - fsym2.Width; break; case SortField.Comment: cmp = string.Compare(fsym1.Comment, fsym2.Comment); break; default: Debug.Assert(false); return 0; } if (cmp == 0) { cmp = string.Compare(fsym1.Label, fsym2.Label); } if (!mIsAscending) { cmp = -cmp; } return cmp; } } private void UpdateControls() { // Enable or disable the edit/remove buttons based on how many items are selected. // (We're currently configured for single-select, so this is really just a != 0 test.) int symSelCount = symbolsList.SelectedItems.Count; removeSymbolButton.IsEnabled = (symSelCount == 1); editSymbolButton.IsEnabled = (symSelCount == 1); } private void OkButton_Click(object sender, RoutedEventArgs e) { NewTable = mWorkTable; DialogResult = true; } private void Window_KeyEventHandler(object sender, KeyEventArgs e) { if (e.Key == Key.Return && Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) { OkButton_Click(null, null); } } private void DeleteTableButton_Click(object sender, RoutedEventArgs e) { MessageBoxResult result = MessageBox.Show((string)FindResource("str_ConfirmDelete"), (string)FindResource("str_ConfirmDeleteCaption"), MessageBoxButton.YesNo, MessageBoxImage.Question); if (result == MessageBoxResult.Yes) { NewTable = null; DialogResult = true; } } private void MoveTableButton_Click(object sender, RoutedEventArgs e) { EditLvTableLocation dlg = new EditLvTableLocation(this, mProject, mOffset, NewOffset); if (dlg.ShowDialog() == true) { NewOffset = dlg.NewOffset; } } private void SymbolsList_SelectionChanged(object sender, SelectionChangedEventArgs e) { UpdateControls(); } private void SymbolsList_MouseDoubleClick(object sender, MouseButtonEventArgs e) { if (!symbolsList.GetClickRowColItem(e, out int unusedRow, out int unusedCol, out object objItem)) { // Header or empty area; ignore. return; } FormattedSymbol item = (FormattedSymbol)objItem; DoEditSymbol(item.DefSym); } private void NewSymbolButton_Click(object sender, RoutedEventArgs e) { EditDefSymbol dlg = new EditDefSymbol(this, mFormatter, mWorkTable.GetSortedByLabel(), null, null, mSymbolTable, true, false); dlg.ShowDialog(); if (dlg.DialogResult == true) { Debug.WriteLine("ADD: " + dlg.NewSym); mWorkTable.AddOrReplace(dlg.NewSym); // Add it to the display list, select it, and scroll it into view. FormattedSymbol newItem = CreateFormattedSymbol(dlg.NewSym); Variables.Add(newItem); symbolsList.SelectedItem = newItem; symbolsList.ScrollIntoView(newItem); UpdateControls(); } } private void EditSymbolButton_Click(object sender, EventArgs e) { // Single-select list view, button dimmed when no selection. Debug.Assert(symbolsList.SelectedItems.Count == 1); FormattedSymbol item = (FormattedSymbol)symbolsList.SelectedItems[0]; DoEditSymbol(item.DefSym); } private void DoEditSymbol(DefSymbol defSym) { EditDefSymbol dlg = new EditDefSymbol(this, mFormatter, mWorkTable.GetSortedByLabel(), defSym, defSym, mSymbolTable, true, false); dlg.ShowDialog(); if (dlg.DialogResult == true) { // Label might have changed, so remove old before adding new. mWorkTable.RemoveByLabel(defSym.Label); mWorkTable.AddOrReplace(dlg.NewSym); // Replace entry in items source. for (int i = 0; i < Variables.Count; i++) { if (Variables[i].DefSym == defSym) { Variables[i] = CreateFormattedSymbol(dlg.NewSym); break; } } UpdateControls(); } } private void RemoveSymbolButton_Click(object sender, RoutedEventArgs e) { // Single-select list view, button dimmed when no selection. Debug.Assert(symbolsList.SelectedItems.Count == 1); int selectionIndex = symbolsList.SelectedIndex; FormattedSymbol item = (FormattedSymbol)symbolsList.SelectedItems[0]; DefSymbol defSym = item.DefSym; mWorkTable.RemoveByLabel(defSym.Label); for (int i = 0; i < Variables.Count; i++) { if (Variables[i].DefSym == defSym) { Variables.RemoveAt(i); break; } } UpdateControls(); // Restore selection to the item that used to come after the one we just deleted, // so you can hit "Remove" repeatedly to delete multiple items. int newCount = symbolsList.Items.Count; if (selectionIndex >= newCount) { selectionIndex = newCount - 1; } if (selectionIndex >= 0) { symbolsList.SelectedIndex = selectionIndex; removeSymbolButton.Focus(); // so you can keep banging on Enter } } } }