mirror of
https://github.com/fadden/6502bench.git
synced 2024-11-19 21:31:30 +00:00
7c506dceb6
When editing local variables, the data grid generally has the input focus, so hitting Enter doesn't close the dialog. Rather than play games with the focus, just take Ctrl+Enter as a shortcut to close the dialog (same as notes and long comments). (I found myself hitting Ctrl+Enter automatically, and being annoyed when it didn't work, so I figured I'd make it official.)
389 lines
15 KiB
C#
389 lines
15 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.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 {
|
|
/// <summary>
|
|
/// Edit a LocalVariableTable.
|
|
/// </summary>
|
|
public partial class EditLocalVariableTable : Window, INotifyPropertyChanged {
|
|
/// <summary>
|
|
/// Output. Will be null if the table was deleted, or if cancel was hit while
|
|
/// creating a new table.
|
|
/// </summary>
|
|
public LocalVariableTable NewTable { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Output. If the table was moved, the new offset will be different from the old.
|
|
/// </summary>
|
|
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<FormattedSymbol> Variables { get; private set; } =
|
|
new ObservableCollection<FormattedSymbol>();
|
|
|
|
/// <summary>
|
|
/// Clear-previous flag.
|
|
/// </summary>
|
|
public bool ClearPrevious {
|
|
get { return mWorkTable.ClearPrevious; }
|
|
set { mWorkTable.ClearPrevious = value; OnPropertyChanged(); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if this is not a new table. (Using "not" because that's the sense we
|
|
/// need in XAML.)
|
|
/// </summary>
|
|
public bool IsNotNewTable {
|
|
get { return mIsNotNewTable; }
|
|
set { mIsNotNewTable = value; OnPropertyChanged(); }
|
|
}
|
|
private bool mIsNotNewTable;
|
|
|
|
/// <summary>
|
|
/// Table header text string, formatted at load time.
|
|
/// </summary>
|
|
public string TableHeaderText {
|
|
get { return mTableHeaderText; }
|
|
set { mTableHeaderText = value; OnPropertyChanged(); }
|
|
}
|
|
private string mTableHeaderText;
|
|
|
|
/// <summary>
|
|
/// Working set. Used internally to hold state.
|
|
/// </summary>
|
|
private LocalVariableTable mWorkTable;
|
|
|
|
/// <summary>
|
|
/// Project reference.
|
|
/// </summary>
|
|
private DisasmProject mProject;
|
|
|
|
/// <summary>
|
|
/// Format object to use when formatting addresses and constants.
|
|
/// </summary>
|
|
private Formatter mFormatter;
|
|
|
|
/// <summary>
|
|
/// Symbol table for uniqueness check.
|
|
/// </summary>
|
|
private SymbolTable mSymbolTable;
|
|
|
|
/// <summary>
|
|
/// Table offset, for move ops.
|
|
/// </summary>
|
|
private int mOffset;
|
|
|
|
// INotifyPropertyChanged implementation
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Constructor. lvt will be null when creating a new entry.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads entries from the work table into the items source.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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, 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, 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
|
|
}
|
|
}
|
|
}
|
|
}
|