1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-12 08:29:29 +00:00
6502bench/SourceGen/WpfGui/EditLocalVariableTable.xaml.cs
Andy McFadden 4d079c8d14 Label rework, part 1
This adds the concept of label annotations.  The primary driver of
the feature is the desire to note that sometimes you know what a
thing is, but sometimes you're just taking an educated guess.
Instead of writing "high_score_maybe", you can now write "high_score?",
which is more compact and consistent.  The annotations are stripped
off when generating source code, making them similar to Notes.

I also created a "Generated" annotation for the labels that are
synthesized by the address table formatter, but don't modify the
label for them, because there's not much need to remind the user
that "T1234" was generated by algorithm.

This also lays some of the groundwork for non-unique labels.
2019-11-08 21:02:15 -08:00

383 lines
14 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.AnnotatedLabel,
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 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
}
}
}
}