2019-07-01 21:07:30 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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;
|
2019-10-02 01:20:51 +00:00
|
|
|
|
using System.Collections;
|
2019-07-01 21:07:30 +00:00
|
|
|
|
using System.Collections.Generic;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
using System.Collections.ObjectModel;
|
2019-07-01 21:07:30 +00:00
|
|
|
|
using System.ComponentModel;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
2019-07-01 21:07:30 +00:00
|
|
|
|
using System.Runtime.CompilerServices;
|
2019-07-03 21:27:54 +00:00
|
|
|
|
using System.Text;
|
2019-07-01 21:07:30 +00:00
|
|
|
|
using System.Windows;
|
2019-07-03 21:27:54 +00:00
|
|
|
|
using System.Windows.Controls;
|
2019-10-02 01:20:51 +00:00
|
|
|
|
using System.Windows.Data;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
using System.Windows.Input;
|
2019-07-05 22:05:42 +00:00
|
|
|
|
using Microsoft.Win32;
|
2019-07-01 21:07:30 +00:00
|
|
|
|
|
|
|
|
|
using Asm65;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
using CommonUtil;
|
|
|
|
|
using CommonWPF;
|
2019-08-13 00:01:50 +00:00
|
|
|
|
using TextScanMode = SourceGen.ProjectProperties.AnalysisParameters.TextScanMode;
|
2019-07-01 21:07:30 +00:00
|
|
|
|
|
2019-07-20 20:28:10 +00:00
|
|
|
|
namespace SourceGen.WpfGui {
|
2019-07-01 21:07:30 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Project properties dialog.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public partial class EditProjectProperties : Window, INotifyPropertyChanged {
|
2019-10-02 01:20:51 +00:00
|
|
|
|
private const string NO_WIDTH_STR = "-";
|
|
|
|
|
|
2020-07-24 23:59:36 +00:00
|
|
|
|
private static double sWindowWidth = -1;
|
|
|
|
|
private static double sWindowHeight = -1;
|
|
|
|
|
|
2019-07-01 21:07:30 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// New set. Updated when Apply or OK is hit. This will be null if no changes have
|
|
|
|
|
/// been applied.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public ProjectProperties NewProps { get; private set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Format object to use when formatting addresses and constants.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Formatter mFormatter;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Working set. Used internally to hold state.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private ProjectProperties mWorkProps;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Dirty flag. Determines whether or not the Apply button is enabled.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Ideally this would just be "WorkProps != OldProps", but it doesn't
|
|
|
|
|
/// seem worthwhile to maintain an equality operator.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public bool IsDirty {
|
|
|
|
|
get { return mIsDirty; }
|
2020-07-24 23:59:36 +00:00
|
|
|
|
set { mIsDirty = value; OnPropertyChanged(); }
|
2019-07-01 21:07:30 +00:00
|
|
|
|
}
|
|
|
|
|
private bool mIsDirty;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Project directory, if one has been established; otherwise empty.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private string mProjectDir;
|
2019-10-09 20:24:09 +00:00
|
|
|
|
public bool HasProjectDir { get { return !string.IsNullOrEmpty(mProjectDir); } }
|
2019-07-01 21:07:30 +00:00
|
|
|
|
|
2019-12-13 19:29:37 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tab page enumeration.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public enum Tab {
|
|
|
|
|
Unknown = 0,
|
|
|
|
|
General,
|
|
|
|
|
ProjectSymbols,
|
|
|
|
|
SymbolFiles,
|
|
|
|
|
ExtensionScripts
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tab to show when dialog is first opened.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Tab mInitialTab;
|
|
|
|
|
|
|
|
|
|
// INotifyPropertyChanged implementation
|
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
|
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
|
|
|
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-01 21:07:30 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Constructor. Initial state is configured from an existing ProjectProperties object.
|
|
|
|
|
/// </summary>
|
2019-12-13 19:29:37 +00:00
|
|
|
|
/// <param name="owner">Parent window.</param>
|
2020-07-04 00:37:04 +00:00
|
|
|
|
/// <param name="project">Project object.</param>
|
2019-07-01 21:07:30 +00:00
|
|
|
|
/// <param name="projectDir">Project directory, if known.</param>
|
|
|
|
|
/// <param name="formatter">Text formatter.</param>
|
2019-12-13 19:29:37 +00:00
|
|
|
|
/// <param name="initialTab">Tab to open initially. Pass "Unknown" for default.</param>
|
2020-07-04 00:37:04 +00:00
|
|
|
|
public EditProjectProperties(Window owner, DisasmProject project, string projectDir,
|
2019-12-13 19:29:37 +00:00
|
|
|
|
Formatter formatter, Tab initialTab) {
|
2019-07-01 21:07:30 +00:00
|
|
|
|
InitializeComponent();
|
|
|
|
|
Owner = owner;
|
|
|
|
|
DataContext = this;
|
|
|
|
|
|
2020-07-04 00:37:04 +00:00
|
|
|
|
mWorkProps = new ProjectProperties(project.ProjectProps); // make a work copy
|
2019-07-01 21:07:30 +00:00
|
|
|
|
mProjectDir = projectDir;
|
|
|
|
|
mFormatter = formatter;
|
2019-12-13 19:29:37 +00:00
|
|
|
|
mInitialTab = initialTab;
|
2019-08-13 00:01:50 +00:00
|
|
|
|
|
2020-07-04 00:37:04 +00:00
|
|
|
|
IsRelocDataAvailable = (project.RelocList.Count > 0);
|
|
|
|
|
|
2019-08-13 00:01:50 +00:00
|
|
|
|
// Construct arrays used as item sources for combo boxes.
|
|
|
|
|
CpuItems = new CpuItem[] {
|
|
|
|
|
new CpuItem((string)FindResource("str_6502"), CpuDef.CpuType.Cpu6502),
|
|
|
|
|
new CpuItem((string)FindResource("str_65C02"), CpuDef.CpuType.Cpu65C02),
|
2020-10-10 22:33:08 +00:00
|
|
|
|
new CpuItem((string)FindResource("str_W65C02"), CpuDef.CpuType.CpuW65C02),
|
2019-08-13 00:01:50 +00:00
|
|
|
|
new CpuItem((string)FindResource("str_65816"), CpuDef.CpuType.Cpu65816),
|
|
|
|
|
};
|
|
|
|
|
DefaultTextScanModeItems = new DefaultTextScanMode[] {
|
2019-08-16 00:53:12 +00:00
|
|
|
|
new DefaultTextScanMode(Res.Strings.SCAN_LOW_ASCII,
|
2019-08-13 00:01:50 +00:00
|
|
|
|
TextScanMode.LowAscii),
|
2019-08-16 00:53:12 +00:00
|
|
|
|
new DefaultTextScanMode(Res.Strings.SCAN_LOW_HIGH_ASCII,
|
2019-08-13 00:01:50 +00:00
|
|
|
|
TextScanMode.LowHighAscii),
|
2019-08-16 00:53:12 +00:00
|
|
|
|
new DefaultTextScanMode(Res.Strings.SCAN_C64_PETSCII,
|
2019-08-13 00:01:50 +00:00
|
|
|
|
TextScanMode.C64Petscii),
|
2019-08-16 00:53:12 +00:00
|
|
|
|
new DefaultTextScanMode(Res.Strings.SCAN_C64_SCREEN_CODE,
|
2019-08-13 00:01:50 +00:00
|
|
|
|
TextScanMode.C64ScreenCode),
|
|
|
|
|
};
|
|
|
|
|
MinCharsItems = new MinCharsItem[] {
|
|
|
|
|
new MinCharsItem((string)FindResource("str_DisableStringScan"),
|
|
|
|
|
DataAnalysis.MIN_CHARS_FOR_STRING_DISABLED),
|
|
|
|
|
new MinCharsItem("3", 3),
|
|
|
|
|
new MinCharsItem("4", 4),
|
|
|
|
|
new MinCharsItem("5", 5),
|
|
|
|
|
new MinCharsItem("6", 6),
|
|
|
|
|
new MinCharsItem("7", 7),
|
|
|
|
|
new MinCharsItem("8", 8),
|
|
|
|
|
new MinCharsItem("9", 9),
|
|
|
|
|
new MinCharsItem("10", 10),
|
|
|
|
|
};
|
|
|
|
|
AutoLabelItems = new AutoLabelItem[] {
|
|
|
|
|
new AutoLabelItem((string)FindResource("str_AutoLabelSimple"),
|
|
|
|
|
AutoLabel.Style.Simple),
|
|
|
|
|
new AutoLabelItem((string)FindResource("str_AutoLabelAnnotated"),
|
|
|
|
|
AutoLabel.Style.Annotated),
|
|
|
|
|
new AutoLabelItem((string)FindResource("str_AutoLabelFullyAnnotated"),
|
|
|
|
|
AutoLabel.Style.FullyAnnotated),
|
|
|
|
|
};
|
2019-07-01 21:07:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Window_Loaded(object sender, RoutedEventArgs e) {
|
2020-07-24 23:59:36 +00:00
|
|
|
|
// Configure window size. Initially we set it to MinWidth/MinHeight. On subsequent
|
|
|
|
|
// visits we set it to the size it was the last time.
|
|
|
|
|
if (sWindowWidth < 0) {
|
|
|
|
|
sWindowWidth = Width = MinWidth;
|
|
|
|
|
sWindowHeight = Height = MinHeight;
|
|
|
|
|
} else {
|
|
|
|
|
Width = sWindowWidth;
|
|
|
|
|
Height = sWindowHeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Configure controls and clear IsDirty.
|
2019-07-03 21:27:54 +00:00
|
|
|
|
Loaded_General();
|
2019-07-01 21:07:30 +00:00
|
|
|
|
|
|
|
|
|
LoadProjectSymbols();
|
|
|
|
|
LoadPlatformSymbolFiles();
|
|
|
|
|
LoadExtensionScriptNames();
|
2019-07-05 23:20:31 +00:00
|
|
|
|
|
2019-12-13 19:29:37 +00:00
|
|
|
|
switch (mInitialTab) {
|
|
|
|
|
case Tab.General:
|
|
|
|
|
tabControl.SelectedItem = generalTab;
|
|
|
|
|
break;
|
|
|
|
|
case Tab.ProjectSymbols:
|
|
|
|
|
tabControl.SelectedItem = projectSymbolsTab;
|
|
|
|
|
break;
|
|
|
|
|
case Tab.SymbolFiles:
|
|
|
|
|
tabControl.SelectedItem = symbolFilesTab;
|
|
|
|
|
break;
|
|
|
|
|
case Tab.ExtensionScripts:
|
|
|
|
|
tabControl.SelectedItem = extensionScriptsTab;
|
|
|
|
|
break;
|
|
|
|
|
case Tab.Unknown:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
Debug.Assert(false);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
UpdateControls();
|
2019-07-01 21:07:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-05 01:39:59 +00:00
|
|
|
|
private void Window_Closing(object sender, CancelEventArgs e) {
|
|
|
|
|
if (IsDirty) {
|
|
|
|
|
string msg = (string)FindResource("str_ConfirmDiscardChanges");
|
|
|
|
|
string caption = (string)FindResource("str_ConfirmDiscardChangesCaption");
|
|
|
|
|
MessageBoxResult result = MessageBox.Show(msg, caption, MessageBoxButton.OKCancel,
|
|
|
|
|
MessageBoxImage.Question);
|
|
|
|
|
if (result == MessageBoxResult.Cancel) {
|
|
|
|
|
e.Cancel = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-24 23:59:36 +00:00
|
|
|
|
|
|
|
|
|
sWindowWidth = Width;
|
|
|
|
|
sWindowHeight = Height;
|
2019-11-05 01:39:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-01 21:07:30 +00:00
|
|
|
|
private void ApplyButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
NewProps = new ProjectProperties(mWorkProps);
|
|
|
|
|
IsDirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OkButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
// TODO(maybe): ideally we'd return false here if nothing has changed from the
|
|
|
|
|
// state things were in when the dialog first opened. Checking IsDirty is
|
|
|
|
|
// insufficient. Might be best to just let the UndoableChange stuff figure out
|
|
|
|
|
// that nothing changed.
|
|
|
|
|
NewProps = new ProjectProperties(mWorkProps);
|
2019-11-05 01:39:59 +00:00
|
|
|
|
IsDirty = false;
|
2019-07-01 21:07:30 +00:00
|
|
|
|
DialogResult = true;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
|
2019-08-26 00:25:15 +00:00
|
|
|
|
//GridView view = (GridView)projectSymbolsListView.View;
|
|
|
|
|
//foreach (GridViewColumn header in view.Columns) {
|
|
|
|
|
// Debug.WriteLine("WIDTH " + header.ActualWidth);
|
|
|
|
|
//}
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpdateControls() {
|
|
|
|
|
//
|
|
|
|
|
// Project symbols tab
|
|
|
|
|
//
|
|
|
|
|
// 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.)
|
2019-10-02 01:20:51 +00:00
|
|
|
|
int symSelCount = projectSymbolsList.SelectedItems.Count;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
removeSymbolButton.IsEnabled = (symSelCount == 1);
|
|
|
|
|
editSymbolButton.IsEnabled = (symSelCount == 1);
|
2019-07-05 23:20:31 +00:00
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Platform symbol files tab
|
|
|
|
|
//
|
|
|
|
|
int fileSelCount = symbolFilesListBox.SelectedItems.Count;
|
|
|
|
|
symbolFileRemoveButton.IsEnabled = (fileSelCount != 0);
|
|
|
|
|
symbolFileUpButton.IsEnabled = (fileSelCount == 1 &&
|
|
|
|
|
symbolFilesListBox.SelectedIndex != 0);
|
|
|
|
|
symbolFileDownButton.IsEnabled = (fileSelCount == 1 &&
|
|
|
|
|
symbolFilesListBox.SelectedIndex != symbolFilesListBox.Items.Count - 1);
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Extension Scripts tab
|
|
|
|
|
//
|
|
|
|
|
fileSelCount = extensionScriptsListBox.SelectedItems.Count;
|
|
|
|
|
extensionScriptRemoveButton.IsEnabled = (fileSelCount != 0);
|
2019-07-01 21:07:30 +00:00
|
|
|
|
}
|
2019-07-03 21:27:54 +00:00
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handles a change in the selection of any of the lists.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void List_SelectionChanged(object sender, SelectionChangedEventArgs e) {
|
|
|
|
|
// Enable/disable buttons as the selection changes.
|
|
|
|
|
UpdateControls();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-07-03 21:27:54 +00:00
|
|
|
|
#region General
|
|
|
|
|
|
|
|
|
|
// CPU combo box items
|
|
|
|
|
public class CpuItem {
|
|
|
|
|
public string Name { get; private set; }
|
|
|
|
|
public CpuDef.CpuType Type { get; private set; }
|
|
|
|
|
|
|
|
|
|
public CpuItem(string name, CpuDef.CpuType type) {
|
|
|
|
|
Name = name;
|
|
|
|
|
Type = type;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-13 00:01:50 +00:00
|
|
|
|
public CpuItem[] CpuItems { get; private set; }
|
|
|
|
|
|
|
|
|
|
// Default text encoding combo box items
|
|
|
|
|
public class DefaultTextScanMode {
|
|
|
|
|
public string Name { get; private set; }
|
|
|
|
|
public TextScanMode Mode { get; private set; }
|
|
|
|
|
|
|
|
|
|
public DefaultTextScanMode(string name, TextScanMode mode) {
|
|
|
|
|
Name = name;
|
|
|
|
|
Mode = mode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public DefaultTextScanMode[] DefaultTextScanModeItems { get; private set; }
|
2019-07-03 21:27:54 +00:00
|
|
|
|
|
|
|
|
|
// Min chars for string combo box items
|
|
|
|
|
public class MinCharsItem {
|
|
|
|
|
public string Name { get; private set; }
|
|
|
|
|
public int Value { get; private set; }
|
|
|
|
|
|
|
|
|
|
public MinCharsItem(string name, int value) {
|
|
|
|
|
Name = name;
|
|
|
|
|
Value = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-13 00:01:50 +00:00
|
|
|
|
public MinCharsItem[] MinCharsItems { get; private set; }
|
2019-07-03 21:27:54 +00:00
|
|
|
|
|
|
|
|
|
// Auto-label style combo box items
|
|
|
|
|
public class AutoLabelItem {
|
|
|
|
|
public string Name { get; private set; }
|
|
|
|
|
public AutoLabel.Style Style { get; private set; }
|
|
|
|
|
|
|
|
|
|
public AutoLabelItem(string name, AutoLabel.Style style) {
|
|
|
|
|
Name = name;
|
|
|
|
|
Style = style;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-13 00:01:50 +00:00
|
|
|
|
public AutoLabelItem[] AutoLabelItems { get; private set; }
|
2019-07-03 21:27:54 +00:00
|
|
|
|
|
Optionally treat BRKs as two-byte instructions
Early data sheets listed BRK as one byte, but RTI after a BRK skips
the following byte, effectively making BRK a 2-byte instruction.
Sometimes, such as when diassembling Apple /// SOS code, it's handy
to treat it that way explicitly.
This change makes two-byte BRKs optional, controlled by a checkbox
in the project settings. In the system definitions it defaults to
true for Apple ///, false for all others.
ACME doesn't allow BRK to have an arg, and cc65 only allows it for
65816 code (?), so it's emitted as a hex blob for those assemblers.
Anyone wishing to target those assemblers should stick to 1-byte mode.
Extension scripts have to switch between formatting one byte of
inline data and formatting an instruction with a one-byte operand.
A helper function has been added to the plugin Util class.
To get some regression test coverage, 2022-extension-scripts has
been configured to use two-byte BRK.
Also, added/corrected some SOS constants.
See also issue #44.
2019-10-09 21:55:56 +00:00
|
|
|
|
//
|
2019-07-03 21:27:54 +00:00
|
|
|
|
// properties for checkboxes
|
Optionally treat BRKs as two-byte instructions
Early data sheets listed BRK as one byte, but RTI after a BRK skips
the following byte, effectively making BRK a 2-byte instruction.
Sometimes, such as when diassembling Apple /// SOS code, it's handy
to treat it that way explicitly.
This change makes two-byte BRKs optional, controlled by a checkbox
in the project settings. In the system definitions it defaults to
true for Apple ///, false for all others.
ACME doesn't allow BRK to have an arg, and cc65 only allows it for
65816 code (?), so it's emitted as a hex blob for those assemblers.
Anyone wishing to target those assemblers should stick to 1-byte mode.
Extension scripts have to switch between formatting one byte of
inline data and formatting an instruction with a one-byte operand.
A helper function has been added to the plugin Util class.
To get some regression test coverage, 2022-extension-scripts has
been configured to use two-byte BRK.
Also, added/corrected some SOS constants.
See also issue #44.
2019-10-09 21:55:56 +00:00
|
|
|
|
//
|
2019-07-03 21:27:54 +00:00
|
|
|
|
public bool IncludeUndocumentedInstr {
|
|
|
|
|
get { return mWorkProps.IncludeUndocumentedInstr; }
|
|
|
|
|
set {
|
|
|
|
|
mWorkProps.IncludeUndocumentedInstr = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
Optionally treat BRKs as two-byte instructions
Early data sheets listed BRK as one byte, but RTI after a BRK skips
the following byte, effectively making BRK a 2-byte instruction.
Sometimes, such as when diassembling Apple /// SOS code, it's handy
to treat it that way explicitly.
This change makes two-byte BRKs optional, controlled by a checkbox
in the project settings. In the system definitions it defaults to
true for Apple ///, false for all others.
ACME doesn't allow BRK to have an arg, and cc65 only allows it for
65816 code (?), so it's emitted as a hex blob for those assemblers.
Anyone wishing to target those assemblers should stick to 1-byte mode.
Extension scripts have to switch between formatting one byte of
inline data and formatting an instruction with a one-byte operand.
A helper function has been added to the plugin Util class.
To get some regression test coverage, 2022-extension-scripts has
been configured to use two-byte BRK.
Also, added/corrected some SOS constants.
See also issue #44.
2019-10-09 21:55:56 +00:00
|
|
|
|
public bool TwoByteBrk {
|
|
|
|
|
get { return mWorkProps.TwoByteBrk; }
|
|
|
|
|
set {
|
|
|
|
|
mWorkProps.TwoByteBrk = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-03 21:27:54 +00:00
|
|
|
|
public bool AnalyzeUncategorizedData {
|
|
|
|
|
get { return mWorkProps.AnalysisParams.AnalyzeUncategorizedData; }
|
|
|
|
|
set {
|
|
|
|
|
mWorkProps.AnalysisParams.AnalyzeUncategorizedData = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public bool SeekNearbyTargets {
|
|
|
|
|
get { return mWorkProps.AnalysisParams.SeekNearbyTargets; }
|
|
|
|
|
set {
|
|
|
|
|
mWorkProps.AnalysisParams.SeekNearbyTargets = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-04 00:37:04 +00:00
|
|
|
|
public bool UseRelocData {
|
|
|
|
|
get { return mWorkProps.AnalysisParams.UseRelocData; }
|
|
|
|
|
set {
|
|
|
|
|
mWorkProps.AnalysisParams.UseRelocData = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private bool mIsRelocDataAvailable;
|
|
|
|
|
public bool IsRelocDataAvailable {
|
|
|
|
|
get { return mIsRelocDataAvailable; }
|
|
|
|
|
set { mIsRelocDataAvailable = value; OnPropertyChanged(); }
|
|
|
|
|
}
|
2019-09-02 22:57:59 +00:00
|
|
|
|
public bool SmartPlpHandling {
|
|
|
|
|
get { return mWorkProps.AnalysisParams.SmartPlpHandling; }
|
|
|
|
|
set {
|
|
|
|
|
mWorkProps.AnalysisParams.SmartPlpHandling = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-10 02:36:22 +00:00
|
|
|
|
public bool SmartPlbHandling {
|
|
|
|
|
get { return mWorkProps.AnalysisParams.SmartPlbHandling; }
|
|
|
|
|
set {
|
|
|
|
|
mWorkProps.AnalysisParams.SmartPlbHandling = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-03 21:27:54 +00:00
|
|
|
|
|
|
|
|
|
private void Loaded_General() {
|
|
|
|
|
for (int i = 0; i < CpuItems.Length; i++) {
|
|
|
|
|
if (CpuItems[i].Type == mWorkProps.CpuType) {
|
|
|
|
|
cpuComboBox.SelectedItem = CpuItems[i];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (cpuComboBox.SelectedItem == null) {
|
2019-08-13 00:01:50 +00:00
|
|
|
|
cpuComboBox.SelectedIndex = 0; // 6502
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < DefaultTextScanModeItems.Length; i++) {
|
|
|
|
|
if (DefaultTextScanModeItems[i].Mode ==
|
|
|
|
|
mWorkProps.AnalysisParams.DefaultTextScanMode) {
|
|
|
|
|
defaultTextEncComboBox.SelectedItem = DefaultTextScanModeItems[i];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (defaultTextEncComboBox.SelectedItem == null) {
|
|
|
|
|
defaultTextEncComboBox.SelectedIndex = 1; // low+high ASCII
|
2019-07-03 21:27:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < MinCharsItems.Length; i++) {
|
|
|
|
|
if (MinCharsItems[i].Value == mWorkProps.AnalysisParams.MinCharsForString) {
|
|
|
|
|
minStringCharsComboBox.SelectedItem = MinCharsItems[i];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (minStringCharsComboBox.SelectedItem == null) {
|
2019-08-13 00:01:50 +00:00
|
|
|
|
minStringCharsComboBox.SelectedIndex = 2; // 4
|
2019-07-03 21:27:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < AutoLabelItems.Length; i++) {
|
|
|
|
|
if (AutoLabelItems[i].Style == mWorkProps.AutoLabelStyle) {
|
|
|
|
|
autoLabelStyleComboBox.SelectedItem = AutoLabelItems[i];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (autoLabelStyleComboBox.SelectedItem == null) {
|
2019-08-13 00:01:50 +00:00
|
|
|
|
autoLabelStyleComboBox.SelectedIndex = 0; // simple
|
2019-07-03 21:27:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdateEntryFlags();
|
2019-08-13 00:01:50 +00:00
|
|
|
|
IsDirty = false;
|
2019-07-03 21:27:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpdateEntryFlags() {
|
|
|
|
|
const string FLAGS = "CZIDXMVNE"; // flags, in order low to high, plus emu bit
|
|
|
|
|
const string VALUES = "-?01";
|
|
|
|
|
StringBuilder sb = new StringBuilder(27);
|
|
|
|
|
StatusFlags flags = mWorkProps.EntryFlags;
|
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
|
|
|
// Want to show P reg flags (first 8) in conventional high-to-low order.
|
|
|
|
|
int idx = (7 - i) + (i == 8 ? 9 : 0);
|
|
|
|
|
int val = flags.GetBit((StatusFlags.FlagBits)idx);
|
|
|
|
|
sb.Append(FLAGS[idx]);
|
|
|
|
|
sb.Append(VALUES[val + 2]);
|
|
|
|
|
sb.Append(' ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentFlagsText.Text = sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void CpuComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
|
|
|
|
|
CpuItem item = (CpuItem)cpuComboBox.SelectedItem;
|
|
|
|
|
mWorkProps.CpuType = item.Type;
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-13 00:01:50 +00:00
|
|
|
|
private void DefaultTextEncComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
|
|
|
|
|
DefaultTextScanMode item =
|
|
|
|
|
(DefaultTextScanMode)defaultTextEncComboBox.SelectedItem;
|
|
|
|
|
mWorkProps.AnalysisParams.DefaultTextScanMode = item.Mode;
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-03 21:27:54 +00:00
|
|
|
|
private void MinStringCharsComboBox_SelectionChanged(object sender,
|
|
|
|
|
SelectionChangedEventArgs e) {
|
|
|
|
|
MinCharsItem item = (MinCharsItem)minStringCharsComboBox.SelectedItem;
|
|
|
|
|
mWorkProps.AnalysisParams.MinCharsForString = item.Value;
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void AutoLabelStyleComboBox_SelectionChanged(object sender,
|
|
|
|
|
SelectionChangedEventArgs e) {
|
|
|
|
|
AutoLabelItem item = (AutoLabelItem)autoLabelStyleComboBox.SelectedItem;
|
|
|
|
|
mWorkProps.AutoLabelStyle = item.Style;
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ChangeFlagButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
CpuDef cpuDef = CpuDef.GetBestMatch(mWorkProps.CpuType,
|
Optionally treat BRKs as two-byte instructions
Early data sheets listed BRK as one byte, but RTI after a BRK skips
the following byte, effectively making BRK a 2-byte instruction.
Sometimes, such as when diassembling Apple /// SOS code, it's handy
to treat it that way explicitly.
This change makes two-byte BRKs optional, controlled by a checkbox
in the project settings. In the system definitions it defaults to
true for Apple ///, false for all others.
ACME doesn't allow BRK to have an arg, and cc65 only allows it for
65816 code (?), so it's emitted as a hex blob for those assemblers.
Anyone wishing to target those assemblers should stick to 1-byte mode.
Extension scripts have to switch between formatting one byte of
inline data and formatting an instruction with a one-byte operand.
A helper function has been added to the plugin Util class.
To get some regression test coverage, 2022-extension-scripts has
been configured to use two-byte BRK.
Also, added/corrected some SOS constants.
See also issue #44.
2019-10-09 21:55:56 +00:00
|
|
|
|
mWorkProps.IncludeUndocumentedInstr, mWorkProps.TwoByteBrk);
|
2019-07-03 21:27:54 +00:00
|
|
|
|
EditStatusFlags dlg =
|
|
|
|
|
new EditStatusFlags(this, mWorkProps.EntryFlags, cpuDef.HasEmuFlag);
|
|
|
|
|
|
|
|
|
|
dlg.ShowDialog();
|
|
|
|
|
if (dlg.DialogResult == true) {
|
|
|
|
|
if (mWorkProps.EntryFlags != dlg.FlagValue) {
|
|
|
|
|
// Flags changed.
|
|
|
|
|
mWorkProps.EntryFlags = dlg.FlagValue;
|
|
|
|
|
UpdateEntryFlags();
|
|
|
|
|
IsDirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion General
|
2019-07-05 21:53:45 +00:00
|
|
|
|
|
|
|
|
|
#region Project Symbols
|
|
|
|
|
|
|
|
|
|
// Item for the project symbol list view.
|
|
|
|
|
public class FormattedSymbol {
|
|
|
|
|
public string Label { get; private set; }
|
|
|
|
|
public string Value { get; private set; }
|
|
|
|
|
public string Type { get; private set; }
|
2019-10-02 01:20:51 +00:00
|
|
|
|
public string Width { get; private set; }
|
2019-07-05 21:53:45 +00:00
|
|
|
|
public string Comment { get; private set; }
|
|
|
|
|
|
2019-11-09 04:44:45 +00:00
|
|
|
|
public DefSymbol DefSym { get; private set; }
|
2019-10-02 01:20:51 +00:00
|
|
|
|
|
2019-11-09 04:44:45 +00:00
|
|
|
|
public FormattedSymbol(DefSymbol defSym, string label, string value,
|
|
|
|
|
string type, string width, string comment) {
|
|
|
|
|
DefSym = defSym;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
Label = label;
|
|
|
|
|
Value = value;
|
|
|
|
|
Type = type;
|
2019-10-02 01:20:51 +00:00
|
|
|
|
Width = width;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
Comment = comment;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public ObservableCollection<FormattedSymbol> ProjectSymbols { get; private set; } =
|
|
|
|
|
new ObservableCollection<FormattedSymbol>();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Prepares the project symbols ListView.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void LoadProjectSymbols() {
|
|
|
|
|
ProjectSymbols.Clear();
|
|
|
|
|
|
|
|
|
|
foreach (KeyValuePair<string, DefSymbol> kvp in mWorkProps.ProjectSyms) {
|
|
|
|
|
DefSymbol defSym = kvp.Value;
|
2019-10-02 01:20:51 +00:00
|
|
|
|
ProjectSymbols.Add(CreateFormattedSymbol(defSym));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This doesn't seem to enable "live sorting". It does an initial sort, but
|
|
|
|
|
// without calling the Sorting function. I'm not sure what the point of this is,
|
|
|
|
|
// or how to cause the DataGrid to behave like somebody clicked on a header.
|
|
|
|
|
|
|
|
|
|
//ICollectionView view =
|
|
|
|
|
// CollectionViewSource.GetDefaultView(projectSymbolsList.ItemsSource);
|
|
|
|
|
//SortDescriptionCollection sortDes = view.SortDescriptions;
|
|
|
|
|
//sortDes.Add(new SortDescription("Value", ListSortDirection.Descending));
|
|
|
|
|
//projectSymbolsList.Columns[0].SortDirection = ListSortDirection.Ascending;
|
|
|
|
|
//view.Refresh();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private FormattedSymbol CreateFormattedSymbol(DefSymbol defSym) {
|
|
|
|
|
string typeStr;
|
2019-10-23 04:27:49 +00:00
|
|
|
|
if (defSym.IsConstant) {
|
2019-10-02 01:20:51 +00:00
|
|
|
|
typeStr = Res.Strings.ABBREV_CONSTANT;
|
|
|
|
|
} else {
|
|
|
|
|
typeStr = Res.Strings.ABBREV_ADDRESS;
|
2019-10-21 01:02:23 +00:00
|
|
|
|
|
|
|
|
|
if (defSym.Direction == DefSymbol.DirectionFlags.Read) {
|
|
|
|
|
typeStr += "<";
|
|
|
|
|
} else if (defSym.Direction == DefSymbol.DirectionFlags.Write) {
|
|
|
|
|
typeStr += ">";
|
|
|
|
|
}
|
2019-10-02 01:20:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FormattedSymbol fsym = new FormattedSymbol(
|
2019-11-09 04:44:45 +00:00
|
|
|
|
defSym,
|
2019-11-13 01:24:41 +00:00
|
|
|
|
defSym.GenerateDisplayLabel(mFormatter),
|
2019-10-02 01:20:51 +00:00
|
|
|
|
mFormatter.FormatValueInBase(defSym.Value, defSym.DataDescriptor.NumBase),
|
|
|
|
|
typeStr,
|
|
|
|
|
defSym.HasWidth ? defSym.DataDescriptor.Length.ToString() : NO_WIDTH_STR,
|
|
|
|
|
defSym.Comment);
|
|
|
|
|
return fsym;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ProjectSymbolsList_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 ProjectSymbolComparer(col.DisplayIndex, isAscending);
|
|
|
|
|
ListCollectionView lcv = (ListCollectionView)
|
|
|
|
|
CollectionViewSource.GetDefaultView(projectSymbolsList.ItemsSource);
|
|
|
|
|
lcv.CustomSort = comparer;
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
}
|
2019-07-05 21:53:45 +00:00
|
|
|
|
|
2019-10-02 01:20:51 +00:00
|
|
|
|
private class ProjectSymbolComparer : 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;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
|
2019-10-02 01:20:51 +00:00
|
|
|
|
public ProjectSymbolComparer(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:
|
2019-11-09 04:44:45 +00:00
|
|
|
|
cmp = fsym1.DefSym.Value - fsym2.DefSym.Value;
|
2019-10-02 01:20:51 +00:00
|
|
|
|
break;
|
|
|
|
|
case SortField.Type:
|
|
|
|
|
cmp = string.Compare(fsym1.Type, fsym2.Type);
|
|
|
|
|
break;
|
|
|
|
|
case SortField.Width:
|
|
|
|
|
// The no-width case is a little weird, because the actual width will
|
|
|
|
|
// be 1, but we want it to sort separately.
|
|
|
|
|
if (fsym1.Width == fsym2.Width) {
|
|
|
|
|
cmp = 0;
|
|
|
|
|
} else if (fsym1.Width == NO_WIDTH_STR) {
|
|
|
|
|
cmp = -1;
|
|
|
|
|
} else if (fsym2.Width == NO_WIDTH_STR) {
|
|
|
|
|
cmp = 1;
|
|
|
|
|
} else {
|
2019-11-09 04:44:45 +00:00
|
|
|
|
cmp = fsym1.DefSym.DataDescriptor.Length -
|
|
|
|
|
fsym2.DefSym.DataDescriptor.Length;
|
2019-10-02 01:20:51 +00:00
|
|
|
|
}
|
|
|
|
|
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;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void NewSymbolButton_Click(object sender, RoutedEventArgs e) {
|
2019-08-26 23:58:53 +00:00
|
|
|
|
EditDefSymbol dlg = new EditDefSymbol(this, mFormatter, mWorkProps.ProjectSyms, null,
|
Instruction operand editor rework, part 2
Implemented local variable editing. Operands that have a local
variable reference, or are eligible to have one, can now be edited
directly from the instruction operand edit dialog.
Also, updated the code list double-click handler so that, if you
double-click on the opcode of an instruction that uses a local
variable reference, the selection and view will jump to the place
where that variable was defined.
Also, tweaked the way the References window refers to references
to an address that didn't use a symbol at that address. Updated
the explanation in the manual, which was a bit confusing.
Also, fixed some odds and ends in the manual.
Also, fixed a nasty infinite recursion bug (issue #47).
2019-09-08 01:57:22 +00:00
|
|
|
|
null);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
dlg.ShowDialog();
|
|
|
|
|
if (dlg.DialogResult == true) {
|
2019-08-26 00:25:15 +00:00
|
|
|
|
Debug.WriteLine("ADD: " + dlg.NewSym);
|
|
|
|
|
mWorkProps.ProjectSyms[dlg.NewSym.Label] = dlg.NewSym;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
IsDirty = true;
|
|
|
|
|
|
2019-10-02 01:20:51 +00:00
|
|
|
|
FormattedSymbol newItem = CreateFormattedSymbol(dlg.NewSym);
|
|
|
|
|
ProjectSymbols.Add(newItem);
|
|
|
|
|
projectSymbolsList.SelectedItem = newItem;
|
|
|
|
|
projectSymbolsList.ScrollIntoView(newItem);
|
2019-09-19 01:11:48 +00:00
|
|
|
|
|
2019-10-02 01:20:51 +00:00
|
|
|
|
UpdateControls();
|
2019-09-19 01:11:48 +00:00
|
|
|
|
okButton.Focus();
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EditSymbolButton_Click(object sender, EventArgs e) {
|
|
|
|
|
// Single-select list view, button dimmed when no selection.
|
2019-10-02 01:20:51 +00:00
|
|
|
|
Debug.Assert(projectSymbolsList.SelectedItems.Count == 1);
|
|
|
|
|
FormattedSymbol item = (FormattedSymbol)projectSymbolsList.SelectedItems[0];
|
2019-11-09 04:44:45 +00:00
|
|
|
|
DoEditSymbol(item.DefSym);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-02 01:20:51 +00:00
|
|
|
|
private void ProjectSymbolsList_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
|
|
|
|
|
if (!projectSymbolsList.GetClickRowColItem(e, out int unusedRow, out int unusedCol,
|
|
|
|
|
out object objItem)) {
|
|
|
|
|
// Header or empty area; ignore.
|
2019-07-05 21:53:45 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-10-02 01:20:51 +00:00
|
|
|
|
FormattedSymbol item = (FormattedSymbol)objItem;
|
2019-11-09 04:44:45 +00:00
|
|
|
|
DoEditSymbol(item.DefSym);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DoEditSymbol(DefSymbol defSym) {
|
2019-08-26 00:25:15 +00:00
|
|
|
|
EditDefSymbol dlg = new EditDefSymbol(this, mFormatter, mWorkProps.ProjectSyms,
|
Instruction operand editor rework, part 2
Implemented local variable editing. Operands that have a local
variable reference, or are eligible to have one, can now be edited
directly from the instruction operand edit dialog.
Also, updated the code list double-click handler so that, if you
double-click on the opcode of an instruction that uses a local
variable reference, the selection and view will jump to the place
where that variable was defined.
Also, tweaked the way the References window refers to references
to an address that didn't use a symbol at that address. Updated
the explanation in the manual, which was a bit confusing.
Also, fixed some odds and ends in the manual.
Also, fixed a nasty infinite recursion bug (issue #47).
2019-09-08 01:57:22 +00:00
|
|
|
|
defSym, null);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
dlg.ShowDialog();
|
|
|
|
|
if (dlg.DialogResult == true) {
|
|
|
|
|
// Label might have changed, so remove old before adding new.
|
|
|
|
|
mWorkProps.ProjectSyms.Remove(defSym.Label);
|
2019-08-26 00:25:15 +00:00
|
|
|
|
mWorkProps.ProjectSyms[dlg.NewSym.Label] = dlg.NewSym;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
IsDirty = true;
|
2019-09-19 01:11:48 +00:00
|
|
|
|
|
2019-10-02 01:20:51 +00:00
|
|
|
|
// Replace entry in items source.
|
2019-11-09 04:44:45 +00:00
|
|
|
|
int i;
|
|
|
|
|
for (i = 0; i < ProjectSymbols.Count; i++) {
|
|
|
|
|
if (ProjectSymbols[i].DefSym == defSym) {
|
2019-10-02 01:20:51 +00:00
|
|
|
|
ProjectSymbols[i] = CreateFormattedSymbol(dlg.NewSym);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-09 04:44:45 +00:00
|
|
|
|
Debug.Assert(i != ProjectSymbols.Count);
|
2019-10-02 01:20:51 +00:00
|
|
|
|
|
|
|
|
|
UpdateControls();
|
2019-09-19 01:11:48 +00:00
|
|
|
|
okButton.Focus();
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RemoveSymbolButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
// Single-select list view, button dimmed when no selection.
|
2019-10-02 01:20:51 +00:00
|
|
|
|
Debug.Assert(projectSymbolsList.SelectedItems.Count == 1);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
|
2019-10-02 01:20:51 +00:00
|
|
|
|
int selectionIndex = projectSymbolsList.SelectedIndex;
|
|
|
|
|
FormattedSymbol item = (FormattedSymbol)projectSymbolsList.SelectedItems[0];
|
2019-11-09 04:44:45 +00:00
|
|
|
|
DefSymbol defSym = item.DefSym;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
mWorkProps.ProjectSyms.Remove(defSym.Label);
|
|
|
|
|
IsDirty = true;
|
2019-10-02 01:20:51 +00:00
|
|
|
|
|
|
|
|
|
for (int i = 0; i < ProjectSymbols.Count; i++) {
|
2019-11-09 04:44:45 +00:00
|
|
|
|
if (ProjectSymbols[i].DefSym == defSym) {
|
2019-10-02 01:20:51 +00:00
|
|
|
|
ProjectSymbols.RemoveAt(i);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-05 21:53:45 +00:00
|
|
|
|
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.
|
2019-10-02 01:20:51 +00:00
|
|
|
|
int newCount = projectSymbolsList.Items.Count;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
if (selectionIndex >= newCount) {
|
|
|
|
|
selectionIndex = newCount - 1;
|
|
|
|
|
}
|
|
|
|
|
if (selectionIndex >= 0) {
|
2019-10-02 01:20:51 +00:00
|
|
|
|
projectSymbolsList.SelectedIndex = selectionIndex;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
removeSymbolButton.Focus();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ImportSymbolsButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
OpenFileDialog fileDlg = new OpenFileDialog() {
|
|
|
|
|
Filter = ProjectFile.FILENAME_FILTER + "|" + Res.Strings.FILE_FILTER_ALL,
|
|
|
|
|
FilterIndex = 1
|
|
|
|
|
};
|
|
|
|
|
if (fileDlg.ShowDialog() != true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
string projPathName = Path.GetFullPath(fileDlg.FileName);
|
|
|
|
|
|
|
|
|
|
DisasmProject newProject = new DisasmProject();
|
|
|
|
|
if (!ProjectFile.DeserializeFromFile(projPathName, newProject,
|
|
|
|
|
out FileLoadReport report)) {
|
|
|
|
|
// Unable to open project file. Report error and bail.
|
|
|
|
|
ProjectLoadIssues dlg = new ProjectLoadIssues(this, report.Format(),
|
|
|
|
|
ProjectLoadIssues.Buttons.Cancel);
|
|
|
|
|
dlg.ShowDialog();
|
|
|
|
|
// ignore dlg.DialogResult
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Import all user labels that were marked as "global export". These become
|
Allow explicit widths in project/platform symbols, part 1
The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.
The first step is to allow widths to be specified in platform files,
and set with the project symbol editor. The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants. (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)
We also now show the symbol's type (address or constant) and width
in the listing. This gets really distracting when overused, so we
only show it when the width is explicitly set. The default width
is 1, which most things will be, so users can make an aesthetic
choice there. (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)
The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page. The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.
The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.
Widths have been added to a handful of Apple II platform defs.
2019-10-01 21:58:24 +00:00
|
|
|
|
// external-address project symbols with unspecified width.
|
2019-07-05 21:53:45 +00:00
|
|
|
|
int foundCount = 0;
|
|
|
|
|
foreach (KeyValuePair<int, Symbol> kvp in newProject.UserLabels) {
|
|
|
|
|
if (kvp.Value.SymbolType == Symbol.Type.GlobalAddrExport) {
|
|
|
|
|
Symbol sym = kvp.Value;
|
|
|
|
|
DefSymbol defSym = new DefSymbol(sym.Label, sym.Value, Symbol.Source.Project,
|
Allow explicit widths in project/platform symbols, part 1
The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.
The first step is to allow widths to be specified in platform files,
and set with the project symbol editor. The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants. (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)
We also now show the symbol's type (address or constant) and width
in the listing. This gets really distracting when overused, so we
only show it when the width is explicitly set. The default width
is 1, which most things will be, so users can make an aesthetic
choice there. (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)
The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page. The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.
The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.
Widths have been added to a handful of Apple II platform defs.
2019-10-01 21:58:24 +00:00
|
|
|
|
Symbol.Type.ExternalAddr, FormatDescriptor.SubType.None);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
mWorkProps.ProjectSyms[defSym.Label] = defSym;
|
|
|
|
|
foundCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (foundCount != 0) {
|
|
|
|
|
IsDirty = true;
|
2019-10-02 01:20:51 +00:00
|
|
|
|
LoadProjectSymbols(); // just reload the whole set
|
2019-07-05 21:53:45 +00:00
|
|
|
|
UpdateControls();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newProject.Cleanup();
|
|
|
|
|
|
|
|
|
|
// Tell the user we did something. Might be nice to tell them how many weren't
|
|
|
|
|
// already present.
|
|
|
|
|
string msg;
|
|
|
|
|
if (foundCount == 0) {
|
|
|
|
|
msg = Res.Strings.SYMBOL_IMPORT_NONE;
|
|
|
|
|
} else {
|
|
|
|
|
msg = string.Format(Res.Strings.SYMBOL_IMPORT_GOOD_FMT, foundCount);
|
|
|
|
|
}
|
|
|
|
|
MessageBox.Show(msg, Res.Strings.SYMBOL_IMPORT_CAPTION,
|
|
|
|
|
MessageBoxButton.OK, MessageBoxImage.Information);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Project Symbols
|
|
|
|
|
|
|
|
|
|
#region Platform symbol files
|
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
public ObservableCollection<string> PlatformSymbolIdentifiers { get; private set; } =
|
|
|
|
|
new ObservableCollection<string>();
|
|
|
|
|
|
2019-07-05 21:53:45 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Loads the platform symbol file names into the list control.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void LoadPlatformSymbolFiles() {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
PlatformSymbolIdentifiers.Clear();
|
2019-07-05 21:53:45 +00:00
|
|
|
|
foreach (string fileName in mWorkProps.PlatformSymbolFileIdentifiers) {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
PlatformSymbolIdentifiers.Add(fileName);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-09 20:24:09 +00:00
|
|
|
|
private void AddSymbolFilesPlatformButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
AddSymbolFiles(true);
|
|
|
|
|
}
|
|
|
|
|
private void AddSymbolFilesProjectButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
AddSymbolFiles(false);
|
|
|
|
|
}
|
|
|
|
|
private void AddSymbolFiles(bool fromPlatform) {
|
2019-07-05 21:53:45 +00:00
|
|
|
|
OpenFileDialog fileDlg = new OpenFileDialog() {
|
|
|
|
|
Filter = PlatformSymbols.FILENAME_FILTER,
|
|
|
|
|
Multiselect = true,
|
2019-10-09 20:24:09 +00:00
|
|
|
|
InitialDirectory = fromPlatform ? RuntimeDataAccess.GetDirectory() : mProjectDir,
|
2019-07-05 21:53:45 +00:00
|
|
|
|
RestoreDirectory = true // doesn't seem to work?
|
|
|
|
|
};
|
2019-07-05 23:20:31 +00:00
|
|
|
|
if (fileDlg.ShowDialog() != true) {
|
2019-07-05 21:53:45 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (string pathName in fileDlg.FileNames) {
|
|
|
|
|
// I'm assuming the full names got the Path.GetFullPath() canonicalization and
|
|
|
|
|
// don't need further processing. Also, I'm assuming that all files live in
|
|
|
|
|
// the same directory, so if one is in an invalid location then they all are.
|
|
|
|
|
ExternalFile ef = ExternalFile.CreateFromPath(pathName, mProjectDir);
|
|
|
|
|
if (ef == null) {
|
|
|
|
|
// Files not found in runtime or project directory.
|
|
|
|
|
string projDir = mProjectDir;
|
|
|
|
|
if (string.IsNullOrEmpty(projDir)) {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
projDir = Res.Strings.UNSET;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
2019-07-05 23:20:31 +00:00
|
|
|
|
string msg = string.Format(Res.Strings.EXTERNAL_FILE_BAD_DIR_FMT,
|
2019-07-05 21:53:45 +00:00
|
|
|
|
RuntimeDataAccess.GetDirectory(), projDir, pathName);
|
2019-07-05 23:20:31 +00:00
|
|
|
|
MessageBox.Show(msg, Res.Strings.EXTERNAL_FILE_BAD_DIR_CAPTION,
|
|
|
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string ident = ef.Identifier;
|
|
|
|
|
|
|
|
|
|
if (mWorkProps.PlatformSymbolFileIdentifiers.Contains(ident)) {
|
|
|
|
|
Debug.WriteLine("Already present: " + ident);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug.WriteLine("Adding symbol file: " + ident);
|
|
|
|
|
mWorkProps.PlatformSymbolFileIdentifiers.Add(ident);
|
2019-07-05 23:20:31 +00:00
|
|
|
|
IsDirty = true;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
if (IsDirty) {
|
2019-07-05 21:53:45 +00:00
|
|
|
|
LoadPlatformSymbolFiles();
|
|
|
|
|
UpdateControls();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-29 01:14:29 +00:00
|
|
|
|
private void SymbolFileUpButton_Click(object sender, RoutedEventArgs e) {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
Debug.Assert(symbolFilesListBox.SelectedItems.Count == 1);
|
|
|
|
|
int selIndex = symbolFilesListBox.SelectedIndex;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
Debug.Assert(selIndex > 0);
|
|
|
|
|
|
|
|
|
|
MoveSingleItem(selIndex, symbolFilesListBox.SelectedItem, -1);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-29 01:14:29 +00:00
|
|
|
|
private void SymbolFileDownButton_Click(object sender, RoutedEventArgs e) {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
Debug.Assert(symbolFilesListBox.SelectedItems.Count == 1);
|
|
|
|
|
int selIndex = symbolFilesListBox.SelectedIndex;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
Debug.Assert(selIndex < symbolFilesListBox.Items.Count - 1);
|
|
|
|
|
|
|
|
|
|
MoveSingleItem(selIndex, symbolFilesListBox.SelectedItem, +1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void MoveSingleItem(int selIndex, object selectedItem, int adj) {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
string selected = (string)symbolFilesListBox.SelectedItem;
|
|
|
|
|
PlatformSymbolIdentifiers.Remove(selected);
|
|
|
|
|
PlatformSymbolIdentifiers.Insert(selIndex + adj, selected);
|
|
|
|
|
symbolFilesListBox.SelectedIndex = selIndex + adj;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
|
|
|
|
|
// do the same operation in the file name list
|
|
|
|
|
string str = mWorkProps.PlatformSymbolFileIdentifiers[selIndex];
|
|
|
|
|
mWorkProps.PlatformSymbolFileIdentifiers.RemoveAt(selIndex);
|
|
|
|
|
mWorkProps.PlatformSymbolFileIdentifiers.Insert(selIndex + adj, str);
|
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
IsDirty = true;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
UpdateControls();
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-29 01:14:29 +00:00
|
|
|
|
private void SymbolFileRemoveButton_Click(object sender, RoutedEventArgs e) {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
Debug.Assert(symbolFilesListBox.SelectedItems.Count > 0);
|
|
|
|
|
for (int i = symbolFilesListBox.SelectedItems.Count - 1; i >= 0; i--) {
|
|
|
|
|
string selItem = (string)symbolFilesListBox.SelectedItems[i];
|
|
|
|
|
PlatformSymbolIdentifiers.Remove(selItem);
|
|
|
|
|
mWorkProps.PlatformSymbolFileIdentifiers.Remove(selItem);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
IsDirty = true;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
UpdateControls();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Platform symbol files
|
|
|
|
|
|
|
|
|
|
#region Extension scripts
|
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
public ObservableCollection<string> ExtensionScriptIdentifiers { get; private set; } =
|
|
|
|
|
new ObservableCollection<string>();
|
|
|
|
|
|
2019-07-05 21:53:45 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Loads the extension script file names into the list control.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void LoadExtensionScriptNames() {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
ExtensionScriptIdentifiers.Clear();
|
2019-07-05 21:53:45 +00:00
|
|
|
|
|
|
|
|
|
foreach (string fileName in mWorkProps.ExtensionScriptFileIdentifiers) {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
ExtensionScriptIdentifiers.Add(fileName);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-09 20:24:09 +00:00
|
|
|
|
private void AddExtensionScriptsPlatformButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
AddExtensionScripts(true);
|
|
|
|
|
}
|
|
|
|
|
private void AddExtensionScriptsProjectButton_Click(object sender, RoutedEventArgs e) {
|
|
|
|
|
AddExtensionScripts(false);
|
|
|
|
|
}
|
|
|
|
|
private void AddExtensionScripts(bool fromPlatform) {
|
2019-07-05 21:53:45 +00:00
|
|
|
|
OpenFileDialog fileDlg = new OpenFileDialog() {
|
|
|
|
|
Filter = Sandbox.ScriptManager.FILENAME_FILTER,
|
|
|
|
|
Multiselect = true,
|
2019-10-09 20:24:09 +00:00
|
|
|
|
InitialDirectory = fromPlatform ? RuntimeDataAccess.GetDirectory() : mProjectDir,
|
2019-07-05 21:53:45 +00:00
|
|
|
|
RestoreDirectory = true // doesn't seem to work?
|
|
|
|
|
};
|
2019-07-05 23:20:31 +00:00
|
|
|
|
if (fileDlg.ShowDialog() != true) {
|
2019-07-05 21:53:45 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (string pathName in fileDlg.FileNames) {
|
|
|
|
|
// I'm assuming the full names got the Path.GetFullPath() canonicalization and
|
|
|
|
|
// don't need further processing. Also, I'm assuming that all files live in
|
|
|
|
|
// the same directory, so if one is in an invalid location then they all are.
|
|
|
|
|
ExternalFile ef = ExternalFile.CreateFromPath(pathName, mProjectDir);
|
|
|
|
|
if (ef == null) {
|
|
|
|
|
// Files not found in runtime or project directory.
|
|
|
|
|
string projDir = mProjectDir;
|
|
|
|
|
if (string.IsNullOrEmpty(projDir)) {
|
2019-07-05 23:20:31 +00:00
|
|
|
|
projDir = Res.Strings.UNSET;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
2019-07-05 23:20:31 +00:00
|
|
|
|
string msg = string.Format(Res.Strings.EXTERNAL_FILE_BAD_DIR_FMT,
|
2019-07-05 21:53:45 +00:00
|
|
|
|
RuntimeDataAccess.GetDirectory(), projDir, pathName);
|
2019-07-05 23:20:31 +00:00
|
|
|
|
MessageBox.Show(this, msg, Res.Strings.EXTERNAL_FILE_BAD_DIR_CAPTION,
|
|
|
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string ident = ef.Identifier;
|
|
|
|
|
|
|
|
|
|
if (mWorkProps.ExtensionScriptFileIdentifiers.Contains(ident)) {
|
|
|
|
|
Debug.WriteLine("Already present: " + ident);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug.WriteLine("Adding extension script: " + ident);
|
|
|
|
|
mWorkProps.ExtensionScriptFileIdentifiers.Add(ident);
|
2019-07-05 23:20:31 +00:00
|
|
|
|
IsDirty = true;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
if (IsDirty) {
|
2019-07-05 21:53:45 +00:00
|
|
|
|
LoadExtensionScriptNames();
|
|
|
|
|
UpdateControls();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
private void ExtensionScriptRemoveButton_Click(object sender, EventArgs e) {
|
|
|
|
|
Debug.Assert(extensionScriptsListBox.SelectedItems.Count > 0);
|
|
|
|
|
for (int i = extensionScriptsListBox.SelectedItems.Count - 1; i >= 0; i--) {
|
|
|
|
|
string selItem = (string)extensionScriptsListBox.SelectedItems[i];
|
|
|
|
|
ExtensionScriptIdentifiers.Remove(selItem);
|
|
|
|
|
mWorkProps.ExtensionScriptFileIdentifiers.Remove(selItem);
|
2019-07-05 21:53:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 23:20:31 +00:00
|
|
|
|
IsDirty = true;
|
2019-07-05 21:53:45 +00:00
|
|
|
|
UpdateControls();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Extension scripts
|
2019-07-01 21:07:30 +00:00
|
|
|
|
}
|
|
|
|
|
}
|