Mesen2/UI/Debugger/ViewModels/LabelListViewModel.cs
Sour d208fdc6a7 UI: Support for NativeAOT
Added Windows & Linux builds using AOT compilation
2024-06-05 20:23:31 +09:00

225 lines
7.9 KiB
C#

using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Selection;
using Avalonia.Media;
using DataBoxControl;
using Mesen.Config;
using Mesen.Debugger.Labels;
using Mesen.Debugger.Utilities;
using Mesen.Debugger.Windows;
using Mesen.Interop;
using Mesen.Utilities;
using Mesen.ViewModels;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Mesen.Debugger.ViewModels
{
public class LabelListViewModel : ViewModelBase
{
[Reactive] public MesenList<LabelViewModel> Labels { get; private set; } = new();
[Reactive] public SelectionModel<LabelViewModel?> Selection { get; set; } = new() { SingleSelect = false };
[Reactive] public SortState SortState { get; set; } = new();
public List<int> ColumnWidths { get; } = ConfigManager.Config.Debug.Debugger.LabelListColumnWidths;
public CpuType CpuType { get; }
public DebuggerWindowViewModel Debugger { get; }
[Obsolete("For designer only")]
public LabelListViewModel() : this(CpuType.Snes, new()) { }
public LabelListViewModel(CpuType cpuType, DebuggerWindowViewModel debugger)
{
CpuType = cpuType;
Debugger = debugger;
SortState.SetColumnSort("Label", ListSortDirection.Ascending, true);
}
public void Sort(object? param)
{
UpdateLabelList();
}
private Dictionary<string, Func<LabelViewModel, LabelViewModel, int>> _comparers = new() {
{ "Label", (a, b) => string.Compare(a.Label.Label, b.Label.Label, StringComparison.OrdinalIgnoreCase) },
{ "RelAddr", (a, b) => a.RelAddress.CompareTo(b.RelAddress) },
{ "AbsAddr", (a, b) => a.Label.Address.CompareTo(b.Label.Address) },
{ "Comment", (a, b) => string.Compare(a.Label.Comment, b.Label.Comment, StringComparison.OrdinalIgnoreCase) },
};
public void UpdateLabelList()
{
List<int> selectedIndexes = Selection.SelectedIndexes.ToList();
List<LabelViewModel> sortedLabels = LabelManager.GetLabels(CpuType).Select(l => new LabelViewModel(l, CpuType)).ToList();
SortHelper.SortList(sortedLabels, SortState.SortOrder, _comparers, "Label");
Labels.Replace(sortedLabels);
Selection.SelectIndexes(selectedIndexes, Labels.Count);
}
public void RefreshLabelList()
{
foreach(LabelViewModel label in Labels) {
label.Refresh();
}
}
public void InitContextMenu(Control parent)
{
DebugShortcutManager.CreateContextMenu(parent, new object[] {
new ContextMenuAction() {
ActionType = ActionType.Add,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.LabelList_Add),
OnClick = () => LabelEditWindow.EditLabel(CpuType, parent, new CodeLabel())
},
new ContextMenuAction() {
ActionType = ActionType.Edit,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.LabelList_Edit),
IsEnabled = () => Selection.SelectedItems.Count == 1,
OnClick = () => {
if(Selection.SelectedItem is LabelViewModel vm) {
LabelEditWindow.EditLabel(CpuType, parent, vm.Label);
}
}
},
new ContextMenuAction() {
ActionType = ActionType.Delete,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.LabelList_Delete),
IsEnabled = () => Selection.SelectedItems.Count > 0,
OnClick = () => {
IEnumerable<CodeLabel> labels = Selection.SelectedItems.Cast<LabelViewModel>().Select(vm => vm.Label).ToList();
LabelManager.DeleteLabels(labels);
}
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.ToggleBreakpoint,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.LabelList_ToggleBreakpoint),
IsEnabled = () => Selection.SelectedItems.Count == 1,
OnClick = () => {
if(Selection.SelectedItem is LabelViewModel vm) {
AddressInfo addr = vm.Label.GetAbsoluteAddress();
BreakpointManager.ToggleBreakpoint(addr, CpuType, false);
}
}
},
new ContextMenuAction() {
ActionType = ActionType.AddWatch,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.LabelList_AddToWatch),
IsEnabled = () => Selection.SelectedItems.Count == 1 && Selection.SelectedItem is LabelViewModel vm && vm.RelAddress >= 0,
OnClick = () => {
if(Selection.SelectedItem is LabelViewModel vm) {
if(vm.Label?.Label.Length > 0) {
WatchManager.GetWatchManager(CpuType).AddWatch("[" + vm.Label.Label + "]");
} else {
WatchManager.GetWatchManager(CpuType).AddWatch("[" + vm.RelAddressDisplay + "]");
}
}
}
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.FindOccurrences,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.LabelList_FindOccurrences),
IsEnabled = () => Selection.SelectedItems.Count == 1 && Selection.SelectedItem is LabelViewModel vm && vm.Label.GetRelativeAddress(CpuType).Address >= 0,
OnClick = () => {
if(Selection.SelectedItem is LabelViewModel vm) {
DisassemblySearchOptions options = new() { MatchCase = true, MatchWholeWord = true };
Debugger.FindAllOccurrences(vm.Label.Label, options);
}
}
},
new ContextMenuAction() {
ActionType = ActionType.GoToLocation,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.LabelList_GoToLocation),
IsEnabled = () => Selection.SelectedItems.Count == 1 && Selection.SelectedItem is LabelViewModel vm && vm.Label.GetRelativeAddress(CpuType).Address >= 0,
OnClick = () => {
if(Selection.SelectedItem is LabelViewModel vm) {
AddressInfo addr = vm.Label.GetRelativeAddress(CpuType);
if(addr.Address >= 0) {
Debugger.ScrollToAddress(addr.Address);
}
}
}
},
new ContextMenuAction() {
ActionType = ActionType.ViewInMemoryViewer,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.LabelList_ViewInMemoryViewer),
IsEnabled = () => Selection.SelectedItems.Count == 1,
OnClick = () => {
if(Selection.SelectedItem is LabelViewModel vm) {
AddressInfo addr = vm.Label.GetRelativeAddress(CpuType);
if(addr.Address < 0) {
addr = vm.Label.GetAbsoluteAddress();
}
MemoryToolsWindow.ShowInMemoryTools(addr.Type, addr.Address);
}
}
},
});
}
}
public class LabelViewModel : INotifyPropertyChanged
{
private string _format;
public CodeLabel Label { get; set; }
public CpuType CpuType { get; }
public string AbsAddressDisplay { get; }
public string LabelText { get; private set; }
public string LabelComment { get; private set; }
public int RelAddress { get; private set; }
public string RelAddressDisplay => RelAddress >= 0 ? ("$" + RelAddress.ToString(_format)) : "<unavailable>";
public object RowBrush => RelAddress >= 0 ? AvaloniaProperty.UnsetValue : Brushes.Gray;
public FontStyle RowStyle => RelAddress >= 0 ? FontStyle.Normal : FontStyle.Italic;
public event PropertyChangedEventHandler? PropertyChanged;
public void Refresh()
{
int addr = Label.GetRelativeAddress(CpuType).Address;
if(addr != RelAddress) {
RelAddress = addr;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RowBrush)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RowStyle)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RelAddressDisplay)));
}
}
public LabelViewModel(CodeLabel label, CpuType cpuType)
{
Label = label;
LabelText = label.Label;
LabelComment = label.Comment;
CpuType = cpuType;
RelAddress = Label.GetRelativeAddress(CpuType).Address;
_format = "X" + cpuType.GetAddressSize();
if(Label.MemoryType.IsRelativeMemory()) {
AbsAddressDisplay = "";
} else {
AbsAddressDisplay = "$" + label.Address.ToString(label.MemoryType.GetFormatString()) + " [" + label.MemoryType.GetShortName() + "]";
}
}
}
}