1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-27 18:29:30 +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>
public class AssemblerInfo {
/// <summary>
/// Enumeration of supported assemblers. Alphabetical order looks nicest.
/// Enumeration of supported assemblers. Sorted alphabetically by human-readable name
/// looks nicest.
/// </summary>
public enum Id {
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_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_ParentheticalNone">(none)</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_ProgressAssembling">Executing assembler...</system:String>

View File

@ -135,6 +135,8 @@ namespace SourceGenWPF.Res {
(string)Application.Current.FindResource("str_OpenDataWrongLengthFmt");
public static string OPERATION_FAILED =
(string)Application.Current.FindResource("str_OperationFailed");
public static string PARENTHETICAL_NONE =
(string)Application.Current.FindResource("str_ParentheticalNone");
public static string PLUGIN_DIR_FAIL_FMT =
(string)Application.Current.FindResource("str_PluginDirFailFmt");
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:"
HorizontalAlignment="Right" Margin="0,5,4,0"/>
<ComboBox Grid.Column="1" Grid.Row="0" IsReadOnly="True"
Width="150" HorizontalAlignment="Left" Margin="0,4,0,20"/>
<ComboBox Name="asmConfigComboBox" Grid.Column="1" Grid.Row="0" IsReadOnly="True"
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:"
HorizontalAlignment="Right" Margin="0,0,4,0"/>
<DockPanel Grid.Column="1" Grid.Row="1">
<Button DockPanel.Dock="Right" Content="Browse..." Width="75"
Margin="8,0,0,8"/>
<TextBox DockPanel.Dock="Left" Margin="0,0,0,8">C:\something</TextBox>
Margin="8,0,0,8" Click="AsmExeBrowseButton_Click"/>
<TextBox Name="asmExePathTextBox" DockPanel.Dock="Left" Margin="0,0,0,8"
Text="C:\something"/>
</DockPanel>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Column widths:"
HorizontalAlignment="Right" Margin="0,0,4,0"/>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal">
<TextBox Width="60" Margin="0,0,4,0"/>
<TextBox Width="60" Margin="0,0,4,0"/>
<TextBox Width="60" Margin="0,0,4,0"/>
<TextBox Width="60" Margin="0,0,8,0"/>
<TextBox Name="asmLabelColWidthTextBox" Width="60" Margin="0,0,4,0"
PreviewTextInput="CheckWidthInput"
TextChanged="AsmLabelColWidthTextBox_TextChanged"/>
<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)"/>
</StackPanel>
</Grid>
</GroupBox>
<TextBlock Text="General code generation:" Margin="4,16,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"/>
<CheckBox Content="Identify assembler in output" Margin="4,8,0,0"/>
<CheckBox Content="Disable label localization" Margin="4,8,0,0"/>
<CheckBox Content="Show cycle counts" Margin="4,8,0,0"
IsChecked="{Binding ShowCycleCounts}"/>
<CheckBox Content="Put long labels on separate line" 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>
</TabItem>

View File

@ -14,12 +14,15 @@
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Win32;
using CommonUtil;
@ -50,7 +53,8 @@ namespace SourceGenWPF.WpfGui {
private AppSettings mSettings;
/// <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>
public bool IsDirty {
get { return mIsDirty; }
@ -62,7 +66,7 @@ namespace SourceGenWPF.WpfGui {
private bool mIsDirty;
/// <summary>
/// Tab page enumeration. Numbers must match page indices in designer.
/// Tab page enumeration.
/// </summary>
public enum Tab {
Unknown = -1,
@ -82,9 +86,11 @@ namespace SourceGenWPF.WpfGui {
/// </summary>
private AssemblerInfo.Id mInitialAsmId;
public List<AssemblerInfo> AssemblerList { get; private set; }
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
@ -144,11 +150,18 @@ namespace SourceGenWPF.WpfGui {
new TextBoxPropertyMap(strDciTextBox, "StrDci"),
new TextBoxPropertyMap(strDciHiTextBox, "StrDciHi"),
};
#endif
ConfigureComboBox(asmConfigComboBox);
ConfigureComboBox(displayFmtQuickComboBox);
ConfigureComboBox(pseudoOpQuickComboBox);
// Create an assembler list for the assembler-config combo box and the two
// "quick set" combo boxes.
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";
foreach (ExpressionStyleItem esi in sExpStyleItems) {
expressionStyleComboBox.Items.Add(esi);
@ -157,19 +170,10 @@ namespace SourceGenWPF.WpfGui {
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
PrepCodeView();
Loaded_CodeView();
Loaded_AsmConfig();
#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.
string opStrCereal = mSettings.GetString(AppSettings.FMT_PSEUDO_OP_NAMES, null);
@ -241,7 +245,7 @@ namespace SourceGenWPF.WpfGui {
#region Code View
private void PrepCodeView() {
private void Loaded_CodeView() {
// Column widths. We called CaptureColumnWidths() during init, so this
// should always be a valid serialized string.
string widthStr = mSettings.GetString(AppSettings.CDLV_COL_WIDTHS, null);
@ -444,21 +448,203 @@ namespace SourceGenWPF.WpfGui {
#region Asm Config
/// <summary>
/// Holds an item for the assembler-selection combox box.
/// </summary>
private class AsmComboItem {
// Enumerated ID.
public AssemblerInfo.Id AssemblerId { get; private set; }
// Human-readable name for display.
public string Name { get; private set; }
public AsmComboItem(AssemblerInfo info) {
AssemblerId = info.AssemblerId;
Name = info.Name;
private bool mShowCycleCounts;
public bool ShowCycleCounts {
get { return mShowCycleCounts; }
set {
mShowCycleCounts = value;
OnPropertyChanged();
mSettings.SetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, value);
IsDirty = true;
}
}
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

View File

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