1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-11-30 01:50:10 +00:00
6502bench/SourceGen/WpfGui/MainWindow.xaml.cs
Andy McFadden f87ac20f32 Add a string operand cache
String operands used to be simple -- each line had 62 characters
plus two hard-coded non-ASCII delimiters -- but now we're mixing
character and hex data, so we can't use simple math to tell where
the lines will break.  We want to render them and keep the result
around until some dependency changes, e.g. different delimiters
or a change to the pseudo-op table.

Also, cleaned up LineListGen a little.  It had some methods that
were declared static because they were expected to be shared, but
that never happened.

Also, fixed a bug in GatherEntityCounts where multi-line items were
being scanned multiple times.
2019-08-17 17:03:06 -07:00

1599 lines
64 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.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using CommonUtil;
using CommonWPF;
namespace SourceGen.WpfGui {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged {
/// <summary>
/// Disassembled code display list provided to XAML.
/// </summary>
public DisplayList CodeDisplayList { get; private set; }
/// <summary>
/// Version string, for display.
/// </summary>
public string ProgramVersionString {
get { return App.ProgramVersion.ToString(); }
}
/// <summary>
/// Text for status bar at bottom of window.
/// </summary>
public string StatusBarText {
get { return mStatusBarText; }
set { mStatusBarText = value; OnPropertyChanged(); }
}
private string mStatusBarText;
/// <summary>
/// Width of long comment fields.
/// </summary>
/// <remarks>
/// We need this to be the sum of the leftmost four columns. If we don't set it, the
/// text may be cut off, or -- worse -- extend off the side of the window. If it
/// extends off the end, a scrollbar appears that will scroll the GridView contents
/// without scrolling the GridView headers, which looks terrible.
///
/// XAML doesn't do math, so we need to do it here, whenever the column widths change.
/// </remarks>
public double LongCommentWidth {
get { return mLongCommentWidth; }
set { mLongCommentWidth = value; OnPropertyChanged(); }
}
private double mLongCommentWidth;
/// <summary>
/// Set to true if the DEBUG menu should be visible on the main menu strip.
/// </summary>
public bool ShowDebugMenu {
get { return mShowDebugMenu; }
set { mShowDebugMenu = value; OnPropertyChanged(); }
}
bool mShowDebugMenu;
//
// Symbols list filter options.
//
public bool SymFilterUserLabels {
get { return mSymFilterUserLabels; }
set {
mSymFilterUserLabels = value;
AppSettings.Global.SetBool(AppSettings.SYMWIN_SHOW_USER, value);
SymbolsListFilterChanged();
OnPropertyChanged();
}
}
private bool mSymFilterUserLabels;
public bool SymFilterProjectSymbols {
get { return mSymFilterProjectSymbols; }
set {
mSymFilterProjectSymbols = value;
AppSettings.Global.SetBool(AppSettings.SYMWIN_SHOW_PROJECT, value);
SymbolsListFilterChanged();
OnPropertyChanged();
}
}
private bool mSymFilterProjectSymbols;
public bool SymFilterPlatformSymbols {
get { return mSymFilterPlatformSymbols; }
set {
mSymFilterPlatformSymbols = value;
AppSettings.Global.SetBool(AppSettings.SYMWIN_SHOW_PLATFORM, value);
SymbolsListFilterChanged();
OnPropertyChanged();
}
}
private bool mSymFilterPlatformSymbols;
public bool SymFilterAutoLabels {
get { return mSymFilterAutoLabels; }
set {
mSymFilterAutoLabels = value;
AppSettings.Global.SetBool(AppSettings.SYMWIN_SHOW_AUTO, value);
SymbolsListFilterChanged();
OnPropertyChanged();
}
}
private bool mSymFilterAutoLabels;
public bool SymFilterAddresses {
get { return mSymFilterAddresses; }
set {
mSymFilterAddresses = value;
AppSettings.Global.SetBool(AppSettings.SYMWIN_SHOW_ADDR, value);
SymbolsListFilterChanged();
OnPropertyChanged();
}
}
private bool mSymFilterAddresses;
public bool SymFilterConstants {
get { return mSymFilterConstants; }
set {
mSymFilterConstants = value;
AppSettings.Global.SetBool(AppSettings.SYMWIN_SHOW_CONST, value);
SymbolsListFilterChanged();
OnPropertyChanged();
}
}
private bool mSymFilterConstants;
/// <summary>
/// Reference to controller object.
/// </summary>
private MainController mMainCtrl;
// Handle to protected ListView.SetSelectedItems() method
private MethodInfo listViewSetSelectedItems;
public MainWindow() {
Debug.WriteLine("START at " + DateTime.Now.ToLocalTime());
InitializeComponent();
AppDomain.CurrentDomain.UnhandledException +=
new UnhandledExceptionEventHandler(CommonUtil.Misc.CrashReporter);
listViewSetSelectedItems = codeListView.GetType().GetMethod("SetSelectedItems",
BindingFlags.NonPublic | BindingFlags.Instance);
Debug.Assert(listViewSetSelectedItems != null);
this.DataContext = this;
CodeDisplayList = new DisplayList();
codeListView.ItemsSource = CodeDisplayList;
// https://dlaa.me/blog/post/9425496 to re-auto-size after data added (this may
// not work with virtual items)
// Obscure tweak to make the arrow keys work right after a change.
codeListView.ItemContainerGenerator.StatusChanged +=
ItemContainerGenerator_StatusChanged;
mMainCtrl = new MainController(this);
StatusBarText = Res.Strings.STATUS_READY;
AddMultiKeyGestures();
// Get an event when the splitters move. Because of the way things are set up, it's
// actually best to get an event when the grid row/column sizes change.
// https://stackoverflow.com/a/22495586/294248
DependencyPropertyDescriptor widthDesc = DependencyPropertyDescriptor.FromProperty(
ColumnDefinition.WidthProperty, typeof(ItemsControl));
DependencyPropertyDescriptor heightDesc = DependencyPropertyDescriptor.FromProperty(
RowDefinition.HeightProperty, typeof(ItemsControl));
// main window, left/right panels
widthDesc.AddValueChanged(triptychGrid.ColumnDefinitions[0], GridSizeChanged);
widthDesc.AddValueChanged(triptychGrid.ColumnDefinitions[4], GridSizeChanged);
// references vs. notes
heightDesc.AddValueChanged(leftPanel.RowDefinitions[0], GridSizeChanged);
heightDesc.AddValueChanged(rightPanel.RowDefinitions[0], GridSizeChanged);
// Add events that fire when column headers change size. We want this for
// the DataGrids and the main ListView.
PropertyDescriptor pd = DependencyPropertyDescriptor.FromProperty(
DataGridColumn.ActualWidthProperty, typeof(DataGridColumn));
AddColumnWidthChangeCallback(pd, referencesGrid);
AddColumnWidthChangeCallback(pd, notesGrid);
AddColumnWidthChangeCallback(pd, symbolsGrid);
// Same for the ListView. cf. https://stackoverflow.com/a/56694219/294248
pd = DependencyPropertyDescriptor.FromProperty(
GridViewColumn.WidthProperty, typeof(GridViewColumn));
AddColumnWidthChangeCallback(pd, (GridView)codeListView.View);
UpdateLongCommentWidth();
}
private void AddColumnWidthChangeCallback(PropertyDescriptor pd, DataGrid dg) {
foreach (DataGridColumn col in dg.Columns) {
pd.AddValueChanged(col, ColumnWidthChanged);
}
}
private void AddColumnWidthChangeCallback(PropertyDescriptor pd, GridView gv) {
foreach (GridViewColumn col in gv.Columns) {
pd.AddValueChanged(col, ColumnWidthChanged);
}
}
private void AddMultiKeyGestures() {
RoutedUICommand ruic;
ruic = (RoutedUICommand)FindResource("HintAsCodeEntryPointCmd");
ruic.InputGestures.Add(
new MultiKeyInputGesture(new KeyGesture[] {
new KeyGesture(Key.H, ModifierKeys.Control, "Ctrl+H"),
new KeyGesture(Key.C, ModifierKeys.Control, "Ctrl+C")
}));
ruic = (RoutedUICommand)FindResource("HintAsDataStartCmd");
ruic.InputGestures.Add(
new MultiKeyInputGesture(new KeyGesture[] {
new KeyGesture(Key.H, ModifierKeys.Control, "Ctrl+H"),
new KeyGesture(Key.D, ModifierKeys.Control, "Ctrl+D")
}));
ruic = (RoutedUICommand)FindResource("HintAsInlineDataCmd");
ruic.InputGestures.Add(
new MultiKeyInputGesture(new KeyGesture[] {
new KeyGesture(Key.H, ModifierKeys.Control, "Ctrl+H"),
new KeyGesture(Key.I, ModifierKeys.Control, "Ctrl+I")
}));
ruic = (RoutedUICommand)FindResource("RemoveHintsCmd");
ruic.InputGestures.Add(
new MultiKeyInputGesture(new KeyGesture[] {
new KeyGesture(Key.H, ModifierKeys.Control, "Ctrl+H"),
new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R")
}));
}
private void CreateCodeListContextMenu() {
//// Find Actions menu.
//ItemCollection mainItems = this.appMenu.Items;
//foreach (object obj in mainItems) {
// if (!(obj is MenuItem)) {
// continue;
// }
// MenuItem mi = (MenuItem)obj;
// if (mi.Name.Equals("actionsMenu")) {
// actionsMenu = mi;
// break;
// }
//}
//Debug.Assert(actionsMenu != null);
// Clone the Actions menu into the codeListView context menu.
ContextMenu ctxt = this.codeListView.ContextMenu;
foreach (object item in actionsMenu.Items) {
if (item is MenuItem) {
MenuItem oldItem = (MenuItem)item;
MenuItem newItem = new MenuItem();
// I don't see a "clone" method, so just copy the fields we think we care about
newItem.Name = oldItem.Name;
newItem.Header = oldItem.Header;
newItem.Icon = oldItem.Icon;
newItem.InputGestureText = oldItem.InputGestureText;
newItem.Command = oldItem.Command;
ctxt.Items.Add(newItem);
} else if (item is Separator) {
ctxt.Items.Add(new Separator());
} else {
Debug.Assert(false, "Found weird thing in menu: " + item);
}
}
}
/// <summary>
/// INotifyPropertyChanged event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Call this when a notification-worthy property changes value.
///
/// The CallerMemberName attribute puts the calling property's name in the first arg.
/// </summary>
/// <param name="propertyName">Name of property that changed.</param>
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Which panel are we showing, launchPanel or codeListView?
/// </summary>
public bool ShowCodeListView {
get {
return mShowCodeListView;
}
set {
mShowCodeListView = value;
OnPropertyChanged("LaunchPanelVisibility");
OnPropertyChanged("CodeListVisibility");
}
}
private bool mShowCodeListView;
/// <summary>
/// Returns the visibility status of the launch panel.
/// (Intended for use from XAML.)
/// </summary>
public Visibility LaunchPanelVisibility {
get { return mShowCodeListView ? Visibility.Hidden : Visibility.Visible; }
}
/// <summary>
/// Returns the visibility status of the code ListView.
/// (Intended for use from XAML.)
/// </summary>
public Visibility CodeListVisibility {
get { return mShowCodeListView ? Visibility.Visible : Visibility.Hidden; }
}
public FontFamily CodeListFontFamily {
get { return codeListView.FontFamily; }
}
public double CodeListFontSize {
get { return codeListView.FontSize; }
}
public void SetCodeListFont(string familyName, int size) {
FontFamily fam = new FontFamily(familyName);
codeListView.FontFamily = fam;
codeListView.FontSize = size;
}
/// <summary>
/// Handles source-initialized event. This happens before Loaded, before the window
/// is visible, which makes it a good time to set the size and position.
/// </summary>
private void Window_SourceInitialized(object sender, EventArgs e) {
mMainCtrl.WindowSourceInitialized();
}
/// <summary>
/// Handles window-loaded event. Window is ready to go, so we can start doing things
/// that involve user interaction.
/// </summary>
private void Window_Loaded(object sender, RoutedEventArgs e) {
mMainCtrl.WindowLoaded();
CreateCodeListContextMenu();
#if DEBUG
// Get more info on CollectionChanged events that do not agree with current
// state of Items collection.
PresentationTraceSources.SetTraceLevel(codeListView.ItemContainerGenerator,
PresentationTraceLevel.High);
#endif
}
/// <summary>
/// Handles window-close event. The user has an opportunity to cancel.
/// </summary>
private void Window_Closing(object sender, CancelEventArgs e) {
Debug.WriteLine("Main app window closing");
if (mMainCtrl == null) {
// early failure?
return;
}
if (!mMainCtrl.WindowClosing()) {
e.Cancel = true;
return;
}
}
/// <summary>
/// Cleans up state when MainController decides to close the project.
/// </summary>
public void ProjectClosing() {
// Clear this to release the memory.
CodeDisplayList.Clear();
InfoPanelContents = string.Empty;
// If you open a new project while one is already open, the ListView apparently
// doesn't reset certain state, possibly because it's never asked to draw after
// the list is cleared. This results in the new project being open at the same
// line as the previous project. This is a little weird, so we reset it here.
CodeListView_SetTopIndex(0);
}
/// <summary>
/// Catch mouse-down events so we can treat the fourth mouse button as "back".
/// </summary>
private void Window_MouseDown(object sender, MouseButtonEventArgs e) {
if (e.ChangedButton == MouseButton.XButton1) {
if (mMainCtrl.CanNavigateBackward()) {
mMainCtrl.NavigateBackward();
}
}
}
#region Window placement
//
// We record the location and size of the window, the sizes of the panels, and the
// widths of the various columns. These events may fire rapidly while the user is
// resizing them, so we just want to set a flag noting that a change has been made.
//
private void Window_LocationChanged(object sender, EventArgs e) {
//Debug.WriteLine("Main window location changed");
AppSettings.Global.Dirty = true;
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e) {
//Debug.WriteLine("Main window size changed");
AppSettings.Global.Dirty = true;
}
private void GridSizeChanged(object sender, EventArgs e) {
//Debug.WriteLine("Splitter size change");
AppSettings.Global.Dirty = true;
}
private void ColumnWidthChanged(object sender, EventArgs e) {
//Debug.WriteLine("Column width change " + sender);
AppSettings.Global.Dirty = true;
UpdateLongCommentWidth();
}
public double LeftPanelWidth {
get { return triptychGrid.ColumnDefinitions[0].ActualWidth; }
set { triptychGrid.ColumnDefinitions[0].Width = new GridLength(value); }
}
public double RightPanelWidth {
get { return triptychGrid.ColumnDefinitions[4].ActualWidth; }
set { triptychGrid.ColumnDefinitions[4].Width = new GridLength(value); }
}
public double ReferencesPanelHeight {
get { return leftPanel.RowDefinitions[0].ActualHeight; }
set {
// If you set the height to a pixel value, you lose the auto-sizing behavior,
// and the splitter will happily shove the bottom panel off the bottom of the
// main window. The trick is to use "star" units.
// Thanks: https://stackoverflow.com/q/35000893/294248
double totalHeight = leftPanel.RowDefinitions[0].ActualHeight +
leftPanel.RowDefinitions[2].ActualHeight;
leftPanel.RowDefinitions[0].Height = new GridLength(value, GridUnitType.Star);
leftPanel.RowDefinitions[2].Height = new GridLength(totalHeight - value,
GridUnitType.Star);
}
}
public double SymbolsPanelHeight {
get { return rightPanel.RowDefinitions[0].ActualHeight; }
set {
double totalHeight = rightPanel.RowDefinitions[0].ActualHeight +
rightPanel.RowDefinitions[2].ActualHeight;
rightPanel.RowDefinitions[0].Height = new GridLength(value, GridUnitType.Star);
rightPanel.RowDefinitions[2].Height = new GridLength(totalHeight - value,
GridUnitType.Star);
}
}
private void UpdateLongCommentWidth() {
GridView gv = (GridView)(codeListView.View);
double totalWidth = 0;
for (int i = (int)MainController.CodeListColumn.Label; i < gv.Columns.Count; i++) {
totalWidth += gv.Columns[i].ActualWidth;
}
LongCommentWidth = totalWidth;
//Debug.WriteLine("Long comment width: " + LongCommentWidth);
}
#endregion Window placement
#region Column widths
/// <summary>
/// Grabs the widths of the columns of the various grids and saves them in the
/// global AppSettings.
/// </summary>
public void CaptureColumnWidths() {
string widthStr;
widthStr = CaptureColumnWidths((GridView)codeListView.View);
AppSettings.Global.SetString(AppSettings.CDLV_COL_WIDTHS, widthStr);
widthStr = CaptureColumnWidths(referencesGrid);
AppSettings.Global.SetString(AppSettings.REFWIN_COL_WIDTHS, widthStr);
widthStr = CaptureColumnWidths(notesGrid);
AppSettings.Global.SetString(AppSettings.NOTEWIN_COL_WIDTHS, widthStr);
widthStr = CaptureColumnWidths(symbolsGrid);
AppSettings.Global.SetString(AppSettings.SYMWIN_COL_WIDTHS, widthStr);
}
private string CaptureColumnWidths(GridView gv) {
int[] widths = new int[gv.Columns.Count];
for (int i = 0; i < gv.Columns.Count; i++) {
widths[i] = (int)Math.Round(gv.Columns[i].ActualWidth);
}
return TextUtil.SerializeIntArray(widths);
}
private string CaptureColumnWidths(DataGrid dg) {
int[] widths = new int[dg.Columns.Count];
for (int i = 0; i < dg.Columns.Count; i++) {
widths[i] = (int)Math.Round(dg.Columns[i].ActualWidth);
}
return TextUtil.SerializeIntArray(widths);
}
/// <summary>
/// Applies column widths from the global AppSettings to the various grids.
/// </summary>
public void RestoreColumnWidths() {
RestoreColumnWidths((GridView)codeListView.View,
AppSettings.Global.GetString(AppSettings.CDLV_COL_WIDTHS, null));
RestoreColumnWidths(referencesGrid,
AppSettings.Global.GetString(AppSettings.REFWIN_COL_WIDTHS, null));
RestoreColumnWidths(notesGrid,
AppSettings.Global.GetString(AppSettings.NOTEWIN_COL_WIDTHS, null));
RestoreColumnWidths(symbolsGrid,
AppSettings.Global.GetString(AppSettings.SYMWIN_COL_WIDTHS, null));
}
private void RestoreColumnWidths(GridView gv, string str) {
int[] widths = null;
try {
widths = TextUtil.DeserializeIntArray(str);
} catch (Exception ex) {
Debug.WriteLine("Unable to deserialize widths for GridView: " + ex.Message);
return;
}
if (widths.Length != gv.Columns.Count) {
Debug.WriteLine("Incorrect column count for GridView");
return;
}
for (int i = 0; i < widths.Length; i++) {
gv.Columns[i].Width = widths[i];
}
}
private void RestoreColumnWidths(DataGrid dg, string str) {
int[] widths = null;
try {
widths = TextUtil.DeserializeIntArray(str);
} catch (Exception ex) {
Debug.WriteLine("Unable to deserialize widths for " + dg.Name + ": " + ex.Message);
return;
}
if (widths.Length != dg.Columns.Count) {
Debug.WriteLine("Incorrect column count for " + dg.Name);
return;
}
for (int i = 0; i < widths.Length; i++) {
dg.Columns[i].Width = widths[i];
}
}
private static string[] sSampleStrings = {
"+000000", // Offset
"00/0000", // Address
"00 00 00 00.", // Bytes (optional spaces or ellipsis, but not both)
"00000000 0", // Flags
"######", // Attributes
"MMMMMMMMM", // Label (9 chars)
"MMMMMMM", // Opcode
"MMMMMMMMMMMMM", // Operand
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" // Comment (50 chars)
};
/// <summary>
/// Computes the default code list column widths, using the currently configured
/// code list font.
/// </summary>
/// <returns></returns>
public int[] GetDefaultCodeListColumnWidths() {
// Fudge factor, in DIPs. This is necessary because the list view style applies
// a margin to the column border.
const double FUDGE = 14.0;
GridView gv = (GridView)codeListView.View;
int[] widths = new int[gv.Columns.Count];
Debug.Assert(widths.Length == (int)MainController.CodeListColumn.COUNT);
Debug.Assert(widths.Length == sSampleStrings.Length);
Typeface typeface = new Typeface(codeListView.FontFamily, codeListView.FontStyle,
codeListView.FontWeight, codeListView.FontStretch);
//Debug.WriteLine("Default column widths (FUDGE=" + FUDGE + "):");
for (int i = 0; i < widths.Length; i++) {
double strLen = Helper.MeasureStringWidth(sSampleStrings[i],
typeface, codeListView.FontSize);
widths[i] = (int)Math.Round(strLen + FUDGE);
//Debug.WriteLine(" " + i + ":" + widths[i] + " " + sSampleStrings[i]);
}
return widths;
}
#endregion Column widths
#region Selection management
private void CodeListView_SelectionChanged(object sender, SelectionChangedEventArgs e) {
//DateTime startWhen = DateTime.Now;
// Update the selected-item bitmap.
CodeDisplayList.SelectedIndices.SelectionChanged(e);
// Notify MainController that the selection has changed.
mMainCtrl.SelectionChanged();
// Don't try to call CodeDisplayList.SelectedIndices.DebugValidateSelectionCount()
// here. Events arrive while pieces are still moving.
//Debug.WriteLine("SelectionChanged took " +
// (DateTime.Now - startWhen).TotalMilliseconds + " ms");
}
/// <summary>
/// Returns the number of selected items.
/// </summary>
/// <returns>
/// The SelectedItems list appears to hold the full set, so we can just return the count.
/// </returns>
public int CodeListView_GetSelectionCount() {
return codeListView.SelectedItems.Count;
}
/// <summary>
/// Returns the index of the first selected item, or -1 if nothing is selected.
/// </summary>
/// <remarks>
/// The ListView.SelectedIndex property returns the index of a selected item, but
/// doesn't make guarantees about first or last.
///
/// This would be easier if the ListView kept SelectedItems in sorted order. However,
/// if you ctrl+click around you can get to a point where entry[0] is not the first
/// and entry[count-1] is not the last selected item.
///
/// So we either have to walk the SelectedItems list or the DisplayListSelection array.
/// For short selections the former will be faster than the later. I'm assuming the
/// common cases will be short selections and select-all, so this should handle both
/// efficiently.
/// </remarks>
public int CodeListView_GetFirstSelectedIndex() {
int count = codeListView.SelectedItems.Count;
if (count == 0) {
return -1;
} else if (count < 500) {
int min = CodeDisplayList.Count;
foreach (DisplayList.FormattedParts parts in codeListView.SelectedItems) {
if (min > parts.ListIndex) {
min = parts.ListIndex;
}
}
Debug.Assert(min < CodeDisplayList.Count);
return min;
} else {
return CodeDisplayList.SelectedIndices.GetFirstSelectedIndex();
}
}
/// <summary>
/// Returns the index of the last selected item, or -1 if nothing is selected.
/// </summary>
/// <remarks>
/// Again, the ListView does not provide what we need.
/// </remarks>
public int CodeListView_GetLastSelectedIndex() {
int count = codeListView.SelectedItems.Count;
if (count == 0) {
return -1;
} else if (count < 500) {
int max = -1;
foreach (DisplayList.FormattedParts parts in codeListView.SelectedItems) {
if (max < parts.ListIndex) {
max = parts.ListIndex;
}
}
Debug.Assert(max >= 0);
return max;
} else {
return CodeDisplayList.SelectedIndices.GetLastSelectedIndex();
}
}
/// <summary>
/// De-selects all items.
/// </summary>
public void CodeListView_DeselectAll() {
codeListView.SelectedItems.Clear();
}
/// <summary>
/// Selects a range of values. Does not clear the previous selection.
/// </summary>
/// <param name="start">First line to select.</param>
/// <param name="count">Number of lines to select.</param>
public void CodeListView_SelectRange(int start, int count) {
Debug.Assert(start >= 0 && start < CodeDisplayList.Count);
Debug.Assert(count > 0 && start + count <= CodeDisplayList.Count);
DisplayList.FormattedParts[] tmpArray = new DisplayList.FormattedParts[count];
for (int index = 0; index < count; index++) {
tmpArray[index] = CodeDisplayList[start + index];
}
listViewSetSelectedItems.Invoke(codeListView, new object[] { tmpArray });
}
/// <summary>
/// Sets the code list selection to match the selection bitmap.
/// </summary>
/// <param name="sel">Selection bitmap.</param>
public void CodeListView_SetSelection(DisplayListSelection sel) {
// Time required increases non-linearly. Quick test:
// 50K: 10 seconds, 20K: 1.6 sec, 10K: 0.6 sec, 5K: 0.2 sec
const int MAX_SEL_COUNT = 5000;
TaskTimer timer = new TaskTimer();
timer.StartTask("TOTAL");
try {
timer.StartTask("Clear");
// The caller will clear the DisplayListSelection before calling here, so we
// need to clear the ListView selection to match, even if we're about to call
// SelectAll. If we don't, the SelectAll() call won't generate the necessary
// events, and our DisplayListSelection will get out of sync.
codeListView.SelectedItems.Clear();
timer.EndTask("Clear");
if (sel.IsAllSelected()) {
Debug.WriteLine("SetSelection: re-selecting all items");
timer.StartTask("SelectAll");
codeListView.SelectAll();
timer.EndTask("SelectAll");
return;
}
if (sel.Count > MAX_SEL_COUNT) {
// Too much for WPF ListView -- only restore the first item.
Debug.WriteLine("SetSelection: not restoring (" + sel.Count + " items)");
codeListView.SelectedItems.Add(CodeDisplayList[sel.GetFirstSelectedIndex()]);
return;
}
Debug.WriteLine("SetSelection: selecting " + sel.Count + " of " +
CodeDisplayList.Count);
// Note: if you refresh the display list with F5, the selection will be lost. This
// appears to be a consequence of hitting a key -- changing from the built-in
// "Refresh" command to a locally defined "Re-analyze" command bound to F6 didn't
// change the behavior. Selecting "re-analyze" from the DEBUG menu doesn't lose
// the selection.
timer.StartTask("tmpArray " + sel.Count);
DisplayList.FormattedParts[] tmpArray = new DisplayList.FormattedParts[sel.Count];
int ai = 0;
foreach (int listIndex in sel) {
tmpArray[ai++] = CodeDisplayList[listIndex];
}
timer.EndTask("tmpArray " + sel.Count);
// Use a reflection call to provide the full set. This is much faster than
// adding the items one at a time to SelectedItems. (For one thing, it only
// invokes the SelectionChanged method once.)
timer.StartTask("Invoke");
listViewSetSelectedItems.Invoke(codeListView, new object[] { tmpArray });
timer.EndTask("Invoke");
} finally {
timer.EndTask("TOTAL");
//timer.DumpTimes("CodeListView_SetSelection");
}
}
public void CodeListView_DebugValidateSelectionCount() {
Debug.Assert(CodeDisplayList.SelectedIndices.DebugValidateSelectionCount(
codeListView.SelectedItems.Count));
}
/// <summary>
/// Sets the focus to the ListViewItem identified by SelectedIndex. This must be done
/// when the ItemContainerGenerator's StatusChanged event fires.
/// </summary>
/// <remarks>
/// Sample steps to reproduce problem:
/// 1. select note
/// 2. delete note
/// 3. select nearby line
/// 4. edit > undo
/// 5. hit the down-arrow key
///
/// Without this event handler, the list jumps to line zero. Apparently the keyboard
/// navigation is not based on which element(s) are selected.
///
/// The original article was dealing with a different problem, where you'd have to hit
/// the down-arrow twice to make it move the first time, because the focus was on the
/// control rather than an item. The same fix seems to apply for this issue as well.
///
/// From http://cytivrat.blogspot.com/2011/05/selecting-first-item-in-wpf-listview.html
/// </remarks>
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) {
if (codeListView.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) {
int index = codeListView.SelectedIndex;
if (index >= 0) {
ListViewItem item =
(ListViewItem)codeListView.ItemContainerGenerator.ContainerFromIndex(index);
if (item != null) {
item.Focus();
}
}
}
}
/// <summary>
/// Returns the index of the line that's currently at the top of the control.
/// </summary>
public int CodeListView_GetTopIndex() {
int index = codeListView.GetTopItemIndex();
Debug.Assert(index >= 0);
return index;
}
/// <summary>
/// Scrolls the code list so that the specified index is at the top of the control.
/// </summary>
/// <param name="index">Line index.</param>
public void CodeListView_SetTopIndex(int index) {
//Debug.WriteLine("CodeListView_SetTopIndex(" + index + "): " + CodeDisplayList[index]);
// ScrollIntoView does the least amount of scrolling required. This extension
// method scrolls to the bottom, then scrolls back up to the top item.
//
// It looks like scroll-to-bottom (which is done directly on the ScrollViewer)
// happens immediately, while scroll-to-item (which is done via the ListView)
// kicks in later. So you can't immediately query the top item to see where
// we were moved to.
//codeListView.ScrollToTopItem(CodeDisplayList[index]);
// This works much better.
codeListView.ScrollToIndex(index);
}
/// <summary>
/// Scrolls the code list to ensure that the specified index is visible.
/// </summary>
/// <param name="index">Line index of item.</param>
public void CodeListView_EnsureVisible(int index) {
Debug.Assert(index >= 0 && index < CodeDisplayList.Count);
codeListView.ScrollIntoView(CodeDisplayList[index]);
}
/// <summary>
/// Adds an address/label selection highlight to the specified line.
/// </summary>
/// <param name="index">Line index. If &lt; 0, method has no effect.</param>
public void CodeListView_AddSelectionHighlight(int index) {
if (index < 0) {
return;
}
CodeListView_ReplaceEntry(index,
DisplayList.FormattedParts.AddSelectionHighlight(CodeDisplayList[index]));
}
/// <summary>
/// Removes an address/label selection highlight from the specified line.
/// </summary>
/// <param name="index">Line index. If &lt; 0, method has no effect.</param>
public void CodeListView_RemoveSelectionHighlight(int index) {
if (index < 0) {
return;
}
CodeListView_ReplaceEntry(index,
DisplayList.FormattedParts.RemoveSelectionHighlight(CodeDisplayList[index]));
}
/// <summary>
/// Replaces an entry in the code list. If the item was selected, the selection is
/// cleared and restored.
/// </summary>
/// <param name="index">List index.</param>
/// <param name="newParts">Replacement parts.</param>
private void CodeListView_ReplaceEntry(int index, DisplayList.FormattedParts newParts) {
bool isSelected = CodeDisplayList.SelectedIndices[index];
if (isSelected) {
codeListView.SelectedItems.Remove(CodeDisplayList[index]);
}
CodeDisplayList[index] = newParts;
if (isSelected) {
codeListView.SelectedItems.Add(newParts);
}
}
/// <summary>
/// Ensures the the code ListView control has input focus.
/// </summary>
public void CodeListView_Focus() {
codeListView.Focus();
}
#endregion Selection management
#region Can-execute handlers
/// <summary>
/// Returns true if the project is open.
/// </summary>
/// <returns></returns>
private bool IsProjectOpen() {
return mMainCtrl != null && mMainCtrl.IsProjectOpen();
}
/// <summary>
/// Returns true if the project is open. Intended for use in XAML CommandBindings.
/// </summary>
private void IsProjectOpen(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen();
}
private void CanDeleteMlc(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanDeleteMlc();
}
private void CanEditAddress(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditAddress();
}
private void CanEditComment(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditComment();
}
private void CanEditLabel(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditLabel();
}
private void CanEditLongComment(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditLongComment();
}
private void CanEditNote(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditNote();
}
private void CanEditOperand(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditOperand();
}
private void CanEditProjectSymbol(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditProjectSymbol();
}
private void CanEditStatusFlags(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditStatusFlags();
}
private void CanFormatAsWord(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanFormatAsWord();
}
private void CanFormatSplitAddress(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanFormatSplitAddress();
}
private void CanHintAsCodeEntryPoint(object sender, CanExecuteRoutedEventArgs e) {
if (!IsProjectOpen()) {
e.CanExecute = false;
return;
}
MainController.EntityCounts counts = mMainCtrl.SelectionAnalysis.mEntityCounts;
e.CanExecute = (counts.mDataLines > 0 || counts.mCodeLines > 0) &&
(counts.mDataHints != 0 || counts.mInlineDataHints != 0 || counts.mNoHints != 0);
}
private void CanHintAsDataStart(object sender, CanExecuteRoutedEventArgs e) {
if (!IsProjectOpen()) {
e.CanExecute = false;
return;
}
MainController.EntityCounts counts = mMainCtrl.SelectionAnalysis.mEntityCounts;
e.CanExecute = (counts.mDataLines > 0 || counts.mCodeLines > 0) &&
(counts.mCodeHints != 0 || counts.mInlineDataHints != 0 || counts.mNoHints != 0);
}
private void CanHintAsInlineData(object sender, CanExecuteRoutedEventArgs e) {
if (!IsProjectOpen()) {
e.CanExecute = false;
return;
}
MainController.EntityCounts counts = mMainCtrl.SelectionAnalysis.mEntityCounts;
e.CanExecute = (counts.mDataLines > 0 || counts.mCodeLines > 0) &&
(counts.mCodeHints != 0 || counts.mDataHints != 0 || counts.mNoHints != 0);
}
private void CanRemoveHints(object sender, CanExecuteRoutedEventArgs e) {
if (!IsProjectOpen()) {
e.CanExecute = false;
return;
}
MainController.EntityCounts counts = mMainCtrl.SelectionAnalysis.mEntityCounts;
e.CanExecute = (counts.mDataLines > 0 || counts.mCodeLines > 0) &&
(counts.mCodeHints != 0 || counts.mDataHints != 0 || counts.mInlineDataHints != 0);
}
private void CanToggleSingleByteFormat(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanToggleSingleByteFormat();
}
private void CanNavigateBackward(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanNavigateBackward();
}
private void CanNavigateForward(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanNavigateForward();
}
private void CanRedo(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanRedo();
}
private void CanUndo(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = IsProjectOpen() && mMainCtrl.CanUndo();
}
#endregion Can-execute handlers
#region Command handlers
private void AboutCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.ShowAboutBox();
}
private void AssembleCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.AssembleProject();
}
private void CloseCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
if (!mMainCtrl.CloseProject()) {
Debug.WriteLine("Close canceled");
}
}
private void CopyCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.CopyToClipboard();
}
private void DeleteMlcCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.DeleteMlc();
}
private void EditAddressCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditAddress();
}
private void EditAppSettingsCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditAppSettings();
}
private void EditCommentCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditComment();
}
private void EditHeaderCommentCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditHeaderComment();
}
private void EditLabelCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditLabel();
}
private void EditLongCommentCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditLongComment();
}
private void EditNoteCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditNote();
}
private void EditOperandCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditOperand();
}
private void EditProjectSymbolCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditProjectSymbol();
}
private void EditStatusFlagsCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditStatusFlags();
}
private void ExitCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
Close();
}
private void FindCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.Find();
}
private void FindNextCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.FindNext();
}
private void FormatAsWordCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.FormatAsWord();
}
private void FormatSplitAddressCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.FormatSplitAddress();
}
private void GotoCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.Goto();
}
private void HelpCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.ShowHelp();
}
private void HintAsCodeEntryPointCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
Debug.WriteLine("hint as code entry point");
mMainCtrl.MarkAsType(CodeAnalysis.TypeHint.Code, true);
}
private void HintAsDataStartCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
Debug.WriteLine("hint as data start");
mMainCtrl.MarkAsType(CodeAnalysis.TypeHint.Data, true);
}
private void HintAsInlineDataCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
Debug.WriteLine("hint as inline data");
mMainCtrl.MarkAsType(CodeAnalysis.TypeHint.InlineData, false);
}
private void NavigateBackwardCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.NavigateBackward();
}
private void NavigateForwardCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.NavigateForward();
}
private void NewProjectCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.NewProject();
}
private void OpenCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.OpenProject();
}
private void EditProjectPropertiesCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.EditProjectProperties();
}
private void RecentProjectCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
int recentIndex;
if (e.Parameter is int) {
recentIndex = (int)e.Parameter;
} else if (e.Parameter is string) {
recentIndex = int.Parse((string)e.Parameter);
} else {
throw new Exception("Bad parameter: " + e.Parameter);
}
if (recentIndex < 0 || recentIndex >= MainController.MAX_RECENT_PROJECTS) {
throw new Exception("Bad parameter: " + e.Parameter);
}
Debug.WriteLine("Recent project #" + recentIndex);
mMainCtrl.OpenRecentProject(recentIndex);
}
private void RemoveHintsCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
Debug.WriteLine("remove hints");
mMainCtrl.MarkAsType(CodeAnalysis.TypeHint.NoHint, false);
}
private void RedoCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.RedoChanges();
}
private void SaveCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.SaveProject();
}
private void SaveAsCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.SaveProjectAs();
}
private void SelectAllCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
DateTime start = DateTime.Now;
codeListView.SelectAll();
//codeListView.SelectedItems.Clear();
//foreach (var item in codeListView.Items) {
// codeListView.SelectedItems.Add(item);
//}
// This seems to be faster than setting items individually (10x), but is still O(n^2)
// or worse, and hence unsuitable for very large lists.
//codeListView.SelectedItems.Clear();
//listViewSetSelectedItems.Invoke(codeListView, new object[] { codeListView.Items });
Debug.WriteLine("Select All cmd: " + (DateTime.Now - start).TotalMilliseconds + " ms");
}
private void ShowFileHexDumpCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.ShowFileHexDump();
}
private void ShowHexDumpCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.ShowHexDump();
}
private void ToggleAsciiChartCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.ToggleAsciiChart();
}
private void ToggleDataScanCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.ToggleDataScan();
}
private void ToggleSingleByteFormatCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.ToggleSingleByteFormat();
}
private void UndoCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.UndoChanges();
}
private void Debug_ExtensionScriptInfoCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.Debug_ExtensionScriptInfo();
}
private void Debug_RefreshCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.Debug_Refresh();
}
private void Debug_ShowAnalysisTimersCmd_Executed(object sender,
ExecutedRoutedEventArgs e) {
mMainCtrl.Debug_ShowAnalysisTimers();
}
private void Debug_ShowAnalyzerOutputCmd_Executed(object sender,
ExecutedRoutedEventArgs e) {
mMainCtrl.Debug_ShowAnalyzerOutput();
}
private void Debug_ShowUndoRedoHistoryCmd_Executed(object sender,
ExecutedRoutedEventArgs e) {
mMainCtrl.Debug_ShowUndoRedoHistory();
}
private void Debug_SourceGenerationTestsCmd_Executed(object sender,
ExecutedRoutedEventArgs e) {
mMainCtrl.Debug_RunSourceGenerationTests();
}
private void Debug_ToggleCommentRulersCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.Debug_ToggleCommentRulers();
}
private void Debug_ToggleKeepAliveHackCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.Debug_ToggleKeepAliveHack();
}
#endregion Command handlers
#region Misc
/// <summary>
/// Handles a double-click on the code list. We have to figure out which row and
/// column were clicked, which is not easy in WPF.
/// </summary>
private void CodeListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
Debug.Assert(sender == codeListView);
ListViewItem lvi = codeListView.GetClickedItem(e);
if (lvi == null) {
return;
}
DisplayList.FormattedParts parts = (DisplayList.FormattedParts)lvi.Content;
int row = parts.ListIndex;
int col = codeListView.GetClickEventColumn(e);
if (col < 0) {
return;
}
mMainCtrl.HandleCodeListDoubleClick(row, col);
}
private void RecentProjectsMenu_SubmenuOpened(object sender, RoutedEventArgs e) {
MenuItem recents = (MenuItem)sender;
recents.Items.Clear();
//Debug.WriteLine("COUNT is " + mMainCtrl.RecentProjectPaths.Count);
if (mMainCtrl.RecentProjectPaths.Count == 0) {
MenuItem mi = new MenuItem();
mi.Header = Res.Strings.PARENTHETICAL_NONE;
recents.Items.Add(mi);
} else {
for (int i = 0; i < mMainCtrl.RecentProjectPaths.Count; i++) {
MenuItem mi = new MenuItem();
mi.Header = string.Format("{0}: {1}", i + 1, mMainCtrl.RecentProjectPaths[i]);
mi.Command = recentProjectCmd.Command;
mi.CommandParameter = i;
recents.Items.Add(mi);
}
}
}
public void UpdateRecentLinks() {
List<string> pathList = mMainCtrl.RecentProjectPaths;
if (pathList.Count >= 1) {
recentProjectName1.Text = Path.GetFileName(pathList[0]);
recentProjectButton1.Visibility = Visibility.Visible;
} else {
recentProjectName1.Text = string.Empty;
recentProjectButton1.Visibility = Visibility.Collapsed;
}
if (pathList.Count >= 2) {
recentProjectName2.Text = Path.GetFileName(pathList[1]);
recentProjectButton2.Visibility = Visibility.Visible;
} else {
recentProjectName2.Text = string.Empty;
recentProjectButton2.Visibility = Visibility.Collapsed;
}
}
/// <summary>
/// Update menu items when the "edit" menu is opened.
/// </summary>
private void EditMenu_SubmenuOpened(object sender, RoutedEventArgs e) {
// Set the checkbox on the "Toggle Data Scan" item.
//
// I initially bound a property to the menu item's IsChecked, but that caused
// us to get "set" calls when the menu was selected. I want to get activity
// through ICommand, not property set, so things are consistent for menus and
// keyboard shortcuts. So we just drive the checkbox manually. I don't know
// if there's a better way.
//
// The project's AnalyzeUncategorizedData property can be set in various ways
// (project property dialog, undo, redo), so we want to query it when we need
// it rather than try to push changes around.
toggleDataScanMenuItem.IsChecked = mMainCtrl.IsAnalyzeUncategorizedDataEnabled;
}
private void ToolsMenu_SubmenuOpened(object sender, RoutedEventArgs e) {
toggleAsciiChartMenuItem.IsChecked = mMainCtrl.IsAsciiChartOpen;
}
private void DebugMenu_SubmenuOpened(object sender, RoutedEventArgs e) {
debugCommentRulersMenuItem.IsChecked = MultiLineComment.DebugShowRuler;
debugKeepAliveHackMenuItem.IsChecked = Sandbox.ScriptManager.UseKeepAliveHack;
debugAnalysisTimersMenuItem.IsChecked = mMainCtrl.IsDebugAnalysisTimersOpen;
debugAnalyzerOutputMenuItem.IsChecked = mMainCtrl.IsDebugAnalyzerOutputOpen;
debugUndoRedoHistoryMenuItem.IsChecked = mMainCtrl.IsDebugUndoRedoHistoryOpen;
}
#endregion Misc
#region References panel
public class ReferencesListItem {
public int OffsetValue { get; private set; }
public string Offset { get; private set; }
public string Addr { get; private set; }
public string Type { get; private set; }
public ReferencesListItem(int offsetValue, string offset, string addr, string type) {
OffsetValue = offsetValue;
Offset = offset;
Addr = addr;
Type = type;
}
public override string ToString() {
return "[ReferencesListItem: off=" + Offset + " addr=" + Addr + " type=" +
Type + "]";
}
}
public ObservableCollection<ReferencesListItem> ReferencesList { get; private set; } =
new ObservableCollection<ReferencesListItem>();
private void ReferencesList_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
if (!referencesGrid.GetClickRowColItem(e, out int rowIndex, out int colIndex,
out object item)) {
// Header or empty area; ignore.
return;
}
ReferencesListItem rli = (ReferencesListItem)item;
// Jump to the offset, then shift the focus back to the code list.
mMainCtrl.GoToOffset(rli.OffsetValue, false, true);
codeListView.Focus();
}
#endregion References panel
#region Notes panel
public class NotesListItem {
public int OffsetValue { get; private set; }
public string Offset { get; private set; }
public string Note { get; private set; }
public SolidColorBrush BackBrush { get; private set; }
public NotesListItem(int offsetValue, string offset, string note, Color backColor) {
OffsetValue = offsetValue;
Offset = offset;
Note = note;
BackBrush = new SolidColorBrush(backColor);
}
public override string ToString() {
return "[NotesListItem: off=" + Offset + " note=" + Note + " brush=" +
BackBrush + "]";
}
}
public ObservableCollection<NotesListItem> NotesList { get; private set; } =
new ObservableCollection<NotesListItem>();
private void NotesList_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
if (!notesGrid.GetClickRowColItem(e, out int rowIndex, out int colIndex,
out object item)) {
// Header or empty area; ignore.
return;
}
NotesListItem nli = (NotesListItem)item;
// Jump to the offset, then shift the focus back to the code list.
mMainCtrl.GoToOffset(nli.OffsetValue, true, true);
codeListView.Focus();
}
#endregion Notes panel
#region Symbols panel
public class SymbolsListItem {
public Symbol Sym { get; private set; }
public string Type { get; private set; }
public string Value { get; private set; }
public string Name { get; private set; }
public SymbolsListItem(Symbol sym, string type, string value, string name) {
Sym = sym;
Type = type;
Value = value;
Name = name;
}
public override string ToString() {
return "[SymbolsListItem: type=" + Type + " value=" + Value + " name=" +
Name + "]";
}
}
public ObservableCollection<SymbolsListItem> SymbolsList { get; private set; } =
new ObservableCollection<SymbolsListItem>();
private void SymbolsList_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
if (!symbolsGrid.GetClickRowColItem(e, out int rowIndex, out int colIndex,
out object item)) {
// Header or empty area; ignore.
return;
}
SymbolsListItem sli = (SymbolsListItem)item;
mMainCtrl.GoToLabel(sli.Sym);
codeListView.Focus();
}
private void SymbolsList_Filter(object sender, FilterEventArgs e) {
SymbolsListItem sli = (SymbolsListItem)e.Item;
if (sli == null) {
return;
}
if ((SymFilterUserLabels != true && sli.Sym.SymbolSource == Symbol.Source.User) ||
(SymFilterProjectSymbols != true && sli.Sym.SymbolSource == Symbol.Source.Project) ||
(SymFilterPlatformSymbols != true && sli.Sym.SymbolSource == Symbol.Source.Platform) ||
(SymFilterAutoLabels != true && sli.Sym.SymbolSource == Symbol.Source.Auto) ||
(SymFilterAddresses != true && sli.Sym.SymbolType != Symbol.Type.Constant) ||
(SymFilterConstants != true && sli.Sym.SymbolType == Symbol.Type.Constant))
{
e.Accepted = false;
} else {
e.Accepted = true;
}
}
/// <summary>
/// Refreshes the symbols list when a filter option changes.
/// </summary>
private void SymbolsListFilterChanged() {
// This delightfully obscure call causes the list to refresh. See
// https://docs.microsoft.com/en-us/dotnet/framework/wpf/controls/how-to-group-sort-and-filter-data-in-the-datagrid-control
CollectionViewSource.GetDefaultView(symbolsGrid.ItemsSource).Refresh();
}
/// <summary>
/// Handles a Sorting event. We want to do a secondary sort on Name when one of the
/// other columns is the primary sort key.
/// </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;
switch (col.Header) {
case "Type":
comparer = new SymTabSortComparer(Symbol.SymbolSortField.CombinedType,
isAscending);
break;
case "Value":
comparer = new SymTabSortComparer(Symbol.SymbolSortField.Value, isAscending);
break;
case "Name":
comparer = new SymTabSortComparer(Symbol.SymbolSortField.Name, isAscending);
break;
default:
comparer = null;
Debug.Assert(false);
break;
}
ListCollectionView lcv =
(ListCollectionView)CollectionViewSource.GetDefaultView(symbolsGrid.ItemsSource);
lcv.CustomSort = comparer;
e.Handled = true;
}
// Symbol table sort comparison helper.
private class SymTabSortComparer : IComparer {
private Symbol.SymbolSortField mSortField;
private bool mIsAscending;
public SymTabSortComparer(Symbol.SymbolSortField prim, bool isAscending) {
mSortField = prim;
mIsAscending = isAscending;
}
// IComparer interface
public int Compare(object oa, object ob) {
Symbol a = ((SymbolsListItem)oa).Sym;
Symbol b = ((SymbolsListItem)ob).Sym;
return Symbol.Compare(mSortField, mIsAscending, a, b);
}
}
#endregion Symbols panel
#region Info panel
/// <summary>
/// Text to display in the Info panel. This is a simple TextBox.
/// </summary>
public string InfoPanelContents {
get {
return mInfoBoxContents;
}
set {
mInfoBoxContents = value;
OnPropertyChanged();
}
}
private string mInfoBoxContents;
#endregion Info panel
}
}