1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-30 06:29:32 +00:00

Implement second settings tab ("Asm Config")

Relatively straightforward, thanks to the way WPF ComboBoxes work.
Spent some time fiddling with the column width text boxes, but
ended up with essentially the WinForms approach.
This commit is contained in:
Andy McFadden 2019-06-27 15:15:45 -07:00
parent 3616a26b01
commit 5beae8f926
6 changed files with 250 additions and 45 deletions

View File

@ -26,7 +26,8 @@ namespace SourceGenWPF.AsmGen {
/// </summary> /// </summary>
public class AssemblerInfo { public class AssemblerInfo {
/// <summary> /// <summary>
/// Enumeration of supported assemblers. Alphabetical order looks nicest. /// Enumeration of supported assemblers. Sorted alphabetically by human-readable name
/// looks nicest.
/// </summary> /// </summary>
public enum Id { public enum Id {
Unknown = 0, Unknown = 0,

View File

@ -75,6 +75,7 @@ limitations under the License.
<system:String x:Key="str_OpenDataWrongLengthFmt">The file is {0:N0} bytes long, but the project expected {1:N0}.</system:String> <system:String x:Key="str_OpenDataWrongLengthFmt">The file is {0:N0} bytes long, but the project expected {1:N0}.</system:String>
<system:String x:Key="str_OpenDataWrongCrcFmt">The file has CRC {0}, but the project expected {1}.</system:String> <system:String x:Key="str_OpenDataWrongCrcFmt">The file has CRC {0}, but the project expected {1}.</system:String>
<system:String x:Key="str_OperationFailed">Failed</system:String> <system:String x:Key="str_OperationFailed">Failed</system:String>
<system:String x:Key="str_ParentheticalNone">(none)</system:String>
<system:String x:Key="str_PluginDirFailFmt">Failed while preparing the plugin directory {0}</system:String> <system:String x:Key="str_PluginDirFailFmt">Failed while preparing the plugin directory {0}</system:String>
<system:String x:Key="str_PluginDirFailCaption">Failed Preparing Plugin Directory</system:String> <system:String x:Key="str_PluginDirFailCaption">Failed Preparing Plugin Directory</system:String>
<system:String x:Key="str_ProgressAssembling">Executing assembler...</system:String> <system:String x:Key="str_ProgressAssembling">Executing assembler...</system:String>

View File

@ -135,6 +135,8 @@ namespace SourceGenWPF.Res {
(string)Application.Current.FindResource("str_OpenDataWrongLengthFmt"); (string)Application.Current.FindResource("str_OpenDataWrongLengthFmt");
public static string OPERATION_FAILED = public static string OPERATION_FAILED =
(string)Application.Current.FindResource("str_OperationFailed"); (string)Application.Current.FindResource("str_OperationFailed");
public static string PARENTHETICAL_NONE =
(string)Application.Current.FindResource("str_ParentheticalNone");
public static string PLUGIN_DIR_FAIL_FMT = public static string PLUGIN_DIR_FAIL_FMT =
(string)Application.Current.FindResource("str_PluginDirFailFmt"); (string)Application.Current.FindResource("str_PluginDirFailFmt");
public static string PLUGIN_DIR_FAIL_CAPTION = public static string PLUGIN_DIR_FAIL_CAPTION =

View File

@ -147,34 +147,49 @@ limitations under the License.
<TextBlock Grid.Column="0" Grid.Row="0" Text="Assembler:" <TextBlock Grid.Column="0" Grid.Row="0" Text="Assembler:"
HorizontalAlignment="Right" Margin="0,5,4,0"/> HorizontalAlignment="Right" Margin="0,5,4,0"/>
<ComboBox Grid.Column="1" Grid.Row="0" IsReadOnly="True" <ComboBox Name="asmConfigComboBox" Grid.Column="1" Grid.Row="0" IsReadOnly="True"
Width="150" HorizontalAlignment="Left" Margin="0,4,0,20"/> Width="150" HorizontalAlignment="Left" Margin="0,4,0,20"
ItemsSource="{Binding AssemblerList}" DisplayMemberPath="Name"
SelectionChanged="AsmConfigComboBox_SelectionChanged"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Executable:" <TextBlock Grid.Column="0" Grid.Row="1" Text="Executable:"
HorizontalAlignment="Right" Margin="0,0,4,0"/> HorizontalAlignment="Right" Margin="0,0,4,0"/>
<DockPanel Grid.Column="1" Grid.Row="1"> <DockPanel Grid.Column="1" Grid.Row="1">
<Button DockPanel.Dock="Right" Content="Browse..." Width="75" <Button DockPanel.Dock="Right" Content="Browse..." Width="75"
Margin="8,0,0,8"/> Margin="8,0,0,8" Click="AsmExeBrowseButton_Click"/>
<TextBox DockPanel.Dock="Left" Margin="0,0,0,8">C:\something</TextBox> <TextBox Name="asmExePathTextBox" DockPanel.Dock="Left" Margin="0,0,0,8"
Text="C:\something"/>
</DockPanel> </DockPanel>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Column widths:" <TextBlock Grid.Column="0" Grid.Row="2" Text="Column widths:"
HorizontalAlignment="Right" Margin="0,0,4,0"/> HorizontalAlignment="Right" Margin="0,0,4,0"/>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal"> <StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal">
<TextBox Width="60" Margin="0,0,4,0"/> <TextBox Name="asmLabelColWidthTextBox" Width="60" Margin="0,0,4,0"
<TextBox Width="60" Margin="0,0,4,0"/> PreviewTextInput="CheckWidthInput"
<TextBox Width="60" Margin="0,0,4,0"/> TextChanged="AsmLabelColWidthTextBox_TextChanged"/>
<TextBox Width="60" Margin="0,0,8,0"/> <TextBox Name="asmOpcodeColWidthTextBox" Width="60" Margin="0,0,4,0"
PreviewTextInput="CheckWidthInput"
TextChanged="AsmLabelColWidthTextBox_TextChanged"/>
<TextBox Name="asmOperandColWidthTextBox" Width="60" Margin="0,0,4,0"
PreviewTextInput="CheckWidthInput"
TextChanged="AsmLabelColWidthTextBox_TextChanged"/>
<TextBox Name="asmCommentColWidthTextBox" Width="60" Margin="0,0,8,0"
PreviewTextInput="CheckWidthInput"
TextChanged="AsmLabelColWidthTextBox_TextChanged"/>
<TextBlock Text="(label, opcode, operand, comment)"/> <TextBlock Text="(label, opcode, operand, comment)"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</GroupBox> </GroupBox>
<TextBlock Text="General code generation:" Margin="4,16,0,0"/> <TextBlock Text="General code generation:" Margin="4,16,0,0"/>
<CheckBox Content="Show cycle counts" Margin="4,8,0,0"/> <CheckBox Content="Show cycle counts" Margin="4,8,0,0"
<CheckBox Content="Put long labels on separate line" Margin="4,8,0,0"/> IsChecked="{Binding ShowCycleCounts}"/>
<CheckBox Content="Identify assembler in output" Margin="4,8,0,0"/> <CheckBox Content="Put long labels on separate line" Margin="4,8,0,0"
<CheckBox Content="Disable label localization" Margin="4,8,0,0"/> IsChecked="{Binding LongLabelNewLine}"/>
<CheckBox Content="Identify assembler in output" Margin="4,8,0,0"
IsChecked="{Binding AddIdentComment}"/>
<CheckBox Content="Disable label localization" Margin="4,8,0,0"
IsChecked="{Binding DisableLabelLocalization}"/>
</StackPanel> </StackPanel>
</TabItem> </TabItem>

View File

@ -14,12 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Win32;
using CommonUtil; using CommonUtil;
@ -50,7 +53,8 @@ namespace SourceGenWPF.WpfGui {
private AppSettings mSettings; private AppSettings mSettings;
/// <summary> /// <summary>
/// Dirty flag, set when anything in mSettings changes. /// Dirty flag, set when anything in mSettings changes. Determines whether or not
/// the Apply button is enabled.
/// </summary> /// </summary>
public bool IsDirty { public bool IsDirty {
get { return mIsDirty; } get { return mIsDirty; }
@ -62,7 +66,7 @@ namespace SourceGenWPF.WpfGui {
private bool mIsDirty; private bool mIsDirty;
/// <summary> /// <summary>
/// Tab page enumeration. Numbers must match page indices in designer. /// Tab page enumeration.
/// </summary> /// </summary>
public enum Tab { public enum Tab {
Unknown = -1, Unknown = -1,
@ -82,9 +86,11 @@ namespace SourceGenWPF.WpfGui {
/// </summary> /// </summary>
private AssemblerInfo.Id mInitialAsmId; private AssemblerInfo.Id mInitialAsmId;
public List<AssemblerInfo> AssemblerList { get; private set; }
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "") { private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} }
@ -144,11 +150,18 @@ namespace SourceGenWPF.WpfGui {
new TextBoxPropertyMap(strDciTextBox, "StrDci"), new TextBoxPropertyMap(strDciTextBox, "StrDci"),
new TextBoxPropertyMap(strDciHiTextBox, "StrDciHi"), new TextBoxPropertyMap(strDciHiTextBox, "StrDciHi"),
}; };
#endif
ConfigureComboBox(asmConfigComboBox); // Create an assembler list for the assembler-config combo box and the two
ConfigureComboBox(displayFmtQuickComboBox); // "quick set" combo boxes.
ConfigureComboBox(pseudoOpQuickComboBox); AssemblerList = new List<AssemblerInfo>();
IEnumerator<AssemblerInfo> iter = AssemblerInfo.GetInfoEnumerator();
while (iter.MoveNext()) {
AssemblerList.Add(iter.Current);
}
// Can't set the selected item yet.
#if false
expressionStyleComboBox.DisplayMember = "Name"; expressionStyleComboBox.DisplayMember = "Name";
foreach (ExpressionStyleItem esi in sExpStyleItems) { foreach (ExpressionStyleItem esi in sExpStyleItems) {
expressionStyleComboBox.Items.Add(esi); expressionStyleComboBox.Items.Add(esi);
@ -157,19 +170,10 @@ namespace SourceGenWPF.WpfGui {
} }
private void Window_Loaded(object sender, RoutedEventArgs e) { private void Window_Loaded(object sender, RoutedEventArgs e) {
PrepCodeView(); Loaded_CodeView();
Loaded_AsmConfig();
#if false #if false
// Assemblers.
PopulateAsmConfigItems();
showAsmIdentCheckBox.Checked =
mSettings.GetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, false);
disableLabelLocalizationCheckBox.Checked =
mSettings.GetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, false);
longLabelNewLineCheckBox.Checked =
mSettings.GetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, false);
showCycleCountsCheckBox.Checked =
mSettings.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false);
// Pseudo ops. // Pseudo ops.
string opStrCereal = mSettings.GetString(AppSettings.FMT_PSEUDO_OP_NAMES, null); string opStrCereal = mSettings.GetString(AppSettings.FMT_PSEUDO_OP_NAMES, null);
@ -241,7 +245,7 @@ namespace SourceGenWPF.WpfGui {
#region Code View #region Code View
private void PrepCodeView() { private void Loaded_CodeView() {
// Column widths. We called CaptureColumnWidths() during init, so this // Column widths. We called CaptureColumnWidths() during init, so this
// should always be a valid serialized string. // should always be a valid serialized string.
string widthStr = mSettings.GetString(AppSettings.CDLV_COL_WIDTHS, null); string widthStr = mSettings.GetString(AppSettings.CDLV_COL_WIDTHS, null);
@ -444,21 +448,203 @@ namespace SourceGenWPF.WpfGui {
#region Asm Config #region Asm Config
/// <summary> private bool mShowCycleCounts;
/// Holds an item for the assembler-selection combox box. public bool ShowCycleCounts {
/// </summary> get { return mShowCycleCounts; }
private class AsmComboItem { set {
// Enumerated ID. mShowCycleCounts = value;
public AssemblerInfo.Id AssemblerId { get; private set; } OnPropertyChanged();
mSettings.SetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, value);
// Human-readable name for display. IsDirty = true;
public string Name { get; private set; }
public AsmComboItem(AssemblerInfo info) {
AssemblerId = info.AssemblerId;
Name = info.Name;
} }
} }
private bool mLongLabelNewLine;
public bool LongLabelNewLine {
get { return mLongLabelNewLine; }
set {
mLongLabelNewLine = value;
OnPropertyChanged();
mSettings.SetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, value);
IsDirty = true;
}
}
private bool mAddIdentComment;
public bool AddIdentComment {
get { return mAddIdentComment; }
set {
mAddIdentComment = value;
OnPropertyChanged();
mSettings.SetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, value);
IsDirty = true;
}
}
private bool mDisableLabelLocalization;
public bool DisableLabelLocalization {
get { return mDisableLabelLocalization; }
set {
mDisableLabelLocalization = value;
OnPropertyChanged();
mSettings.SetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, value);
IsDirty = true;
}
}
private void Loaded_AsmConfig() {
asmConfigComboBox.SelectedItem = AssemblerInfo.GetAssemblerInfo(mInitialAsmId);
if (asmConfigComboBox.SelectedIndex < 0) {
Debug.Assert(mInitialAsmId == AssemblerInfo.Id.Unknown);
asmConfigComboBox.SelectedIndex = 0;
}
ShowCycleCounts =
mSettings.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false);
LongLabelNewLine =
mSettings.GetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, false);
AddIdentComment =
mSettings.GetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, false);
DisableLabelLocalization =
mSettings.GetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, false);
}
/// <summary>
/// Populates the UI elements from the asm config item in the settings. If that doesn't
/// exist, use the default config.
/// </summary>
private void PopulateAsmConfigItems() {
AssemblerInfo info = (AssemblerInfo)asmConfigComboBox.SelectedItem;
AssemblerConfig config = AssemblerConfig.GetConfig(mSettings, info.AssemblerId);
if (config == null) {
AsmGen.IAssembler asm = AssemblerInfo.GetAssembler(info.AssemblerId);
config = asm.GetDefaultConfig();
}
asmExePathTextBox.Text = config.ExecutablePath;
asmLabelColWidthTextBox.Text = config.ColumnWidths[0].ToString();
asmOpcodeColWidthTextBox.Text = config.ColumnWidths[1].ToString();
asmOperandColWidthTextBox.Text = config.ColumnWidths[2].ToString();
asmCommentColWidthTextBox.Text = config.ColumnWidths[3].ToString();
}
private void AsmConfigComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
// They're switching to a different asm config. Changing the boxes will cause
// the dirty flag to be raised, which isn't right, so we save/restore it.
bool oldDirty = IsDirty;
PopulateAsmConfigItems();
IsDirty = oldDirty;
}
/// <summary>
/// Checks whether the character typed into a column width entry field is allowed.
/// This is only useful for screening the character set, not the field contents.
/// </summary>
/// <remarks>
/// This just screens the character. Doesn't handle selection operations like pasting.
/// Also, doesn't fire when you hit the space bar. This is only slightly better than
/// useless, but since we don't otherwise give an indication of wrongness it's nice
/// to have.
///
/// Another approach is to bind Text to an integer property. This enables the validation
/// mechanism, which puts a red box around the field when it contains bad things, but
/// only after focus leaves the field.
///
/// See also https://stackoverflow.com/q/1268552/294248
/// </remarks>
private void CheckWidthInput(object sender, TextCompositionEventArgs e) {
// Set e.Handled to true if the character is invalid.
char ch = e.Text[0];
e.Handled = (ch < '0' || ch > '9');
}
/// <summary>
/// Updates the assembler config settings whenever one of the text fields is edited.
/// </summary>
/// <remarks>
/// This fires 4x every time the combo box selection changes, as the new fields are
/// populated. That should work out correctly.
/// </remarks>
private void AsmLabelColWidthTextBox_TextChanged(object sender, TextChangedEventArgs e) {
AssemblerInfo asm = (AssemblerInfo)asmConfigComboBox.SelectedItem;
AssemblerConfig.SetConfig(mSettings, asm.AssemblerId, GetAsmConfigFromUi());
IsDirty = true;
}
/// <summary>
/// Extracts the asm configuration items (exe path, column widths) from the UI.
/// </summary>
/// <returns></returns>
private AssemblerConfig GetAsmConfigFromUi() {
const int MIN_WIDTH = 1;
const int MAX_WIDTH = 200;
int[] widths = new int[4];
for (int i = 0; i < widths.Length; i++) {
widths[i] = MIN_WIDTH;
}
int result;
if (int.TryParse(asmLabelColWidthTextBox.Text, out result) && result >= MIN_WIDTH &&
result <= MAX_WIDTH) {
widths[0] = result;
}
if (int.TryParse(asmOpcodeColWidthTextBox.Text, out result) && result >= MIN_WIDTH &&
result <= MAX_WIDTH) {
widths[1] = result;
}
if (int.TryParse(asmOperandColWidthTextBox.Text, out result) && result >= MIN_WIDTH &&
result <= MAX_WIDTH) {
widths[2] = result;
}
if (int.TryParse(asmCommentColWidthTextBox.Text, out result) && result >= MIN_WIDTH &&
result <= MAX_WIDTH) {
widths[3] = result;
}
return new AssemblerConfig(asmExePathTextBox.Text, widths);
}
private void AsmExeBrowseButton_Click(object sender, RoutedEventArgs e) {
AssemblerInfo asmInfo = (AssemblerInfo)asmConfigComboBox.SelectedItem;
// Figure out what we're looking for. For example, cc65 needs "cl65".
AsmGen.IAssembler asm = AssemblerInfo.GetAssembler(asmInfo.AssemblerId);
asm.GetExeIdentifiers(out string humanName, out string exeName);
// Ask the user to find it.
string pathName = BrowseForExecutable(humanName, exeName);
if (pathName != null) {
asmExePathTextBox.Text = pathName;
AssemblerConfig.SetConfig(mSettings, asmInfo.AssemblerId, GetAsmConfigFromUi());
IsDirty = true;
}
}
/// <summary>
/// Creates a file dialog to search for a specific executable.
/// </summary>
/// <param name="prefix">Human-readable filter string for UI.</param>
/// <param name="name">Filename of executable.</param>
/// <returns>Path of executable, or null if dialog was canceled.</returns>
private string BrowseForExecutable(string prefix, string name) {
string pathName = null;
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
name += ".exe";
}
OpenFileDialog dlg = new OpenFileDialog() {
FileName = name,
Filter = prefix + "|" + name,
RestoreDirectory = true
};
if (dlg.ShowDialog() == true) {
pathName = dlg.FileName;
}
return pathName;
}
#endregion AsmConfig #endregion AsmConfig

View File

@ -917,7 +917,7 @@ namespace SourceGenWPF.WpfGui {
Debug.WriteLine("COUNT is " + mMainCtrl.RecentProjectPaths.Count); Debug.WriteLine("COUNT is " + mMainCtrl.RecentProjectPaths.Count);
if (mMainCtrl.RecentProjectPaths.Count == 0) { if (mMainCtrl.RecentProjectPaths.Count == 0) {
MenuItem mi = new MenuItem(); MenuItem mi = new MenuItem();
mi.Header = "(none)"; mi.Header = Res.Strings.PARENTHETICAL_NONE;
recents.Items.Add(mi); recents.Items.Add(mi);
} else { } else {
for (int i = 0; i < mMainCtrl.RecentProjectPaths.Count; i++) { for (int i = 0; i < mMainCtrl.RecentProjectPaths.Count; i++) {