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

Implement text export

This feature "exports" the source code in the same format it appears
in on screen.  The five columns on the left are optional, the four
on the right can be resized.  Exported text can span the entire file
or just the current selection.  Output can be plain text or CSV.
(I still haven't figured out a good use for CSV.)

The old copy-to-clipboard function is now implemented via the export
mechanism.

This adds a new clipboard mode that includes all columns.  Potentially
useful for filing bug reports against SourceGen.
This commit is contained in:
Andy McFadden 2019-09-12 13:57:52 -07:00
parent e5104dc2e7
commit 81157b6b47
9 changed files with 638 additions and 112 deletions

View File

@ -115,6 +115,18 @@ namespace SourceGen {
// Assembler settings prefix
public const string ASM_CONFIG_PREFIX = "asm-config-";
// Text/HTML export settings.
public const string EXPORT_INCLUDE_NOTES = "export-include-notes";
public const string EXPORT_SHOW_OFFSET = "export-show-offset";
public const string EXPORT_SHOW_ADDR = "export-show-addr";
public const string EXPORT_SHOW_BYTES = "export-show-bytes";
public const string EXPORT_SHOW_FLAGS = "export-show-flags";
public const string EXPORT_SHOW_ATTR = "export-show-attr";
public const string EXPORT_COL_WIDTHS = "export-col-widths";
public const string EXPORT_TEXT_MODE = "export-text-mode";
public const string EXPORT_SELECTION_ONLY = "export-selection-only";
public const string EXPORT_INCLUDE_SYMTAB = "export-include-symtab";
// Internal debugging features.
public const string DEBUG_MENU_ENABLED = "debug-menu-enabled";

View File

@ -14,16 +14,53 @@
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using Asm65;
using CommonUtil;
using ClipLineFormat = SourceGen.MainController.ClipLineFormat;
namespace SourceGen {
/// <summary>
/// Source code export functions.
/// </summary>
/// <remarks>
/// The five columns on the left (offset, address, bytes, flags, attributes) are optional,
/// and have a fixed width. The four columns on the right (label, opcode, operand, comment)
/// are mandatory, and have configurable widths.
/// </remarks>
public class Exporter {
/// <summary>
/// Optional selection specifier. If null, the entire file is included.
/// </summary>
public DisplayListSelection Selection { get; set; }
/// <summary>
/// Should notes be included in the output?
/// </summary>
public bool IncludeNotes { get; set; }
/// <summary>
/// Bit flags, used to indicate which of the optional columns are active.
/// </summary>
[FlagsAttribute]
public enum ActiveColumnFlags {
None = 0,
Offset = 1,
Address = 1 << 1,
Bytes = 1 << 2,
Flags = 1 << 3,
Attr = 1 << 4,
ALL = 0x7f
}
/// <summary>
/// Flags indicating active optional columns.
/// </summary>
private ActiveColumnFlags mLeftFlags;
/// <summary>
/// Project reference.
/// </summary>
@ -39,117 +76,282 @@ namespace SourceGen {
/// </summary>
private Formatter mFormatter;
/// <summary>
/// The cumulative width of the columns determines the end point.
/// </summary>
private int[] mColEnd;
private enum Col {
Offset = 0,
Address = 1,
Bytes = 2,
Flags = 3,
Attr = 4,
Label = 5,
Opcode = 6,
Operand = 7,
Comment = 8,
COUNT // number of elements, must be last
}
/// <summary>
/// Constructor.
/// </summary>
public Exporter(DisasmProject project, LineListGen codeLineList, Formatter formatter) {
public Exporter(DisasmProject project, LineListGen codeLineList, Formatter formatter,
ActiveColumnFlags leftFlags, int[] rightWidths) {
mProject = project;
mCodeLineList = codeLineList;
mFormatter = formatter;
mLeftFlags = leftFlags;
ConfigureColumns(leftFlags, rightWidths);
}
private void ConfigureColumns(ActiveColumnFlags leftFlags, int[] rightWidths) {
mColEnd = new int[(int)Col.COUNT];
int total = 0;
int width;
// offset "+123456"
if ((leftFlags & ActiveColumnFlags.Offset) != 0) {
total = mColEnd[(int)Col.Offset] = total + 7 + 1;
} else {
mColEnd[(int)Col.Offset] = total;
}
// address "1234:" or "12/4567:"
if ((leftFlags & ActiveColumnFlags.Address) != 0) {
width = mProject.CpuDef.HasAddr16 ? 5 : 8;
total = mColEnd[(int)Col.Address] = total + width + 1;
} else {
mColEnd[(int)Col.Address] = total;
}
// bytes "12345678+" or "12 45 78 01+"
if ((leftFlags & ActiveColumnFlags.Bytes) != 0) {
// A limit of 8 gets us 4 bytes from dense display ("20edfd60") and 3 if spaces
// are included ("20 ed fd") with no excess. We want to increase it to 11 so
// we can always show 4 bytes. Add one for a trailing "+".
width = mFormatter.Config.mSpacesBetweenBytes ? 12 : 9;
total = mColEnd[(int)Col.Bytes] = total + width + 1;
} else {
mColEnd[(int)Col.Bytes] = total;
}
// flags "NVMXDIZC" or "NVMXDIZC E"
if ((leftFlags & ActiveColumnFlags.Flags) != 0) {
width = mProject.CpuDef.HasEmuFlag ? 10 : 8;
total = mColEnd[(int)Col.Flags] = total + width + 1;
} else {
mColEnd[(int)Col.Flags] = total;
}
// attributes "@H!#>"
if ((leftFlags & ActiveColumnFlags.Attr) != 0) {
total = mColEnd[(int)Col.Attr] = total + 5 + 1;
} else {
mColEnd[(int)Col.Attr] = total;
}
total = mColEnd[(int)Col.Label] = total + rightWidths[0];
total = mColEnd[(int)Col.Opcode] = total + rightWidths[1];
total = mColEnd[(int)Col.Operand] = total + rightWidths[2];
total = mColEnd[(int)Col.Comment] = total + rightWidths[3];
//Debug.WriteLine("Export col ends:");
//for (int i = 0; i < (int)Col.COUNT; i++) {
// Debug.WriteLine(" " + i + "(" + ((Col)i) + ") " + mColEnd[i]);
//}
}
/// <summary>
/// Converts the selected lines to formatted text.
/// </summary>
/// <param name="selection">Set of lines to select.</param>
/// <param name="lineFormat">Line format.</param>
/// <param name="fullText">Result; holds text of all selected lines.</param>
/// <param name="csvText">Result; holds text of all selected lines, in CSV format.</param>
public void SelectionToText(DisplayListSelection selection, ClipLineFormat lineFormat,
bool addCsv, out string fullText, out string csvText) {
StringBuilder plainText = new StringBuilder(selection.Count * 50);
StringBuilder sb = new StringBuilder(100);
public void SelectionToString(bool addCsv, out string fullText, out string csvText) {
StringBuilder sb = new StringBuilder(128);
StringBuilder plainText = new StringBuilder(Selection.Count * 50);
StringBuilder csv = null;
if (addCsv) {
csv = new StringBuilder(selection.Count * 40);
csv = new StringBuilder(Selection.Count * 40);
}
int addrAdj = mProject.CpuDef.HasAddr16 ? 6 : 9;
int disAdj = 0;
int bytesWidth = 0;
if (lineFormat == MainController.ClipLineFormat.Disassembly) {
// A limit of 8 gets us 4 bytes from dense display ("20edfd60") and 3 if spaces
// are included ("20 ed fd") with no excess. We want to increase it to 11 so
// we can always show 4 bytes.
bytesWidth = (mFormatter.Config.mSpacesBetweenBytes ? 11 : 8);
disAdj = addrAdj + bytesWidth + 2;
}
// Walking through the selected indices can be slow for a large file, so we
// run through the full list and pick out the selected items with our parallel
// structure. (I'm assuming that "select all" will be a common precursor.)
foreach (int index in selection) {
LineListGen.Line line = mCodeLineList[index];
DisplayList.FormattedParts parts = mCodeLineList.GetFormattedParts(index);
switch (line.LineType) {
case LineListGen.Line.Type.Code:
case LineListGen.Line.Type.Data:
case LineListGen.Line.Type.EquDirective:
case LineListGen.Line.Type.RegWidthDirective:
case LineListGen.Line.Type.OrgDirective:
case LineListGen.Line.Type.LocalVariableTable:
if (lineFormat == ClipLineFormat.Disassembly) {
if (!string.IsNullOrEmpty(parts.Addr)) {
sb.Append(parts.Addr);
sb.Append(": ");
}
// Shorten the "...".
string bytesStr = parts.Bytes;
if (bytesStr != null && bytesStr.Length > bytesWidth) {
bytesStr = bytesStr.Substring(0, bytesWidth) + "+";
}
TextUtil.AppendPaddedString(sb, bytesStr, disAdj);
}
TextUtil.AppendPaddedString(sb, parts.Label, disAdj + 9);
TextUtil.AppendPaddedString(sb, parts.Opcode, disAdj + 9 + 8);
TextUtil.AppendPaddedString(sb, parts.Operand, disAdj + 9 + 8 + 11);
if (string.IsNullOrEmpty(parts.Comment)) {
// Trim trailing spaces off opcode or operand.
TextUtil.TrimEnd(sb);
} else {
sb.Append(parts.Comment);
}
sb.Append("\r\n");
break;
case LineListGen.Line.Type.LongComment:
if (lineFormat == ClipLineFormat.Disassembly) {
TextUtil.AppendPaddedString(sb, string.Empty, disAdj);
}
sb.Append(parts.Comment);
sb.Append("\r\n");
break;
case LineListGen.Line.Type.Note:
// don't include notes
break;
case LineListGen.Line.Type.Blank:
sb.Append("\r\n");
break;
default:
Debug.Assert(false);
break;
for (int lineIndex = 0; lineIndex < mCodeLineList.Count; lineIndex++) {
if (!Selection[lineIndex]) {
continue;
}
if (GenerateTextLine(lineIndex, sb)) {
plainText.Append(sb.ToString());
plainText.Append("\r\n");
}
plainText.Append(sb);
sb.Clear();
if (addCsv) {
csv.Append(TextUtil.EscapeCSV(parts.Offset)); csv.Append(',');
csv.Append(TextUtil.EscapeCSV(parts.Addr)); csv.Append(',');
csv.Append(TextUtil.EscapeCSV(parts.Bytes)); csv.Append(',');
csv.Append(TextUtil.EscapeCSV(parts.Flags)); csv.Append(',');
csv.Append(TextUtil.EscapeCSV(parts.Attr)); csv.Append(',');
csv.Append(TextUtil.EscapeCSV(parts.Label)); csv.Append(',');
csv.Append(TextUtil.EscapeCSV(parts.Opcode)); csv.Append(',');
csv.Append(TextUtil.EscapeCSV(parts.Operand)); csv.Append(',');
csv.Append(TextUtil.EscapeCSV(parts.Comment));
GenerateCsvLine(lineIndex, sb);
csv.Append(sb.ToString());
csv.Append("\r\n");
sb.Clear();
}
}
fullText = plainText.ToString();
csvText = csv.ToString();
if (addCsv) {
csvText = csv.ToString();
} else {
csvText = null;
}
}
/// <summary>
/// Generates a full listing and writes it to the specified file.
/// </summary>
/// <param name="pathName">Full path to output file.</param>
/// <param name="asCsv">Output as Comma Separated Values rather than plain text.</param>
public void OutputToText(string pathName, bool asCsv) {
// Generate UTF-8 text. For plain text we omit the byte-order mark, for CSV
// it appears to be meaningful (tested w/ very old version of Excel).
using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(asCsv))) {
StringBuilder sb = new StringBuilder(128);
for (int lineIndex = 0; lineIndex < mCodeLineList.Count; lineIndex++) {
if (Selection != null && !Selection[lineIndex]) {
continue;
}
if (asCsv) {
GenerateCsvLine(lineIndex, sb);
sw.WriteLine(sb.ToString());
} else {
if (GenerateTextLine(lineIndex, sb)) {
sw.WriteLine(sb.ToString());
}
}
sb.Clear();
}
}
}
/// <summary>
/// Generates a line of plain text output. The line will not have EOL markers added.
/// </summary>
/// <param name="index">Index of line to output.</param>
/// <param name="sb">String builder to append text to. Must be cleared before
/// calling here. (This is a minor optimization.)</param>
private bool GenerateTextLine(int index, StringBuilder sb) {
Debug.Assert(sb.Length == 0);
// Width of "bytes" field, without '+' or trailing space.
int bytesWidth = mColEnd[(int)Col.Bytes] - mColEnd[(int)Col.Address] - 2;
LineListGen.Line line = mCodeLineList[index];
DisplayList.FormattedParts parts = mCodeLineList.GetFormattedParts(index);
switch (line.LineType) {
case LineListGen.Line.Type.Code:
case LineListGen.Line.Type.Data:
case LineListGen.Line.Type.EquDirective:
case LineListGen.Line.Type.RegWidthDirective:
case LineListGen.Line.Type.OrgDirective:
case LineListGen.Line.Type.LocalVariableTable:
if (parts.IsLongComment) {
// This happens for long comments embedded in LV tables.
if (mColEnd[(int)Col.Attr] != 0) {
TextUtil.AppendPaddedString(sb, string.Empty, mColEnd[(int)Col.Attr]);
}
sb.Append(parts.Comment);
break;
}
if ((mLeftFlags & ActiveColumnFlags.Offset) != 0) {
TextUtil.AppendPaddedString(sb, parts.Offset,
mColEnd[(int)Col.Offset]);
}
if ((mLeftFlags & ActiveColumnFlags.Address) != 0) {
string str;
if (!string.IsNullOrEmpty(parts.Addr)) {
str = parts.Addr + ":";
} else {
str = string.Empty;
}
TextUtil.AppendPaddedString(sb, str, mColEnd[(int)Col.Address]);
}
if ((mLeftFlags & ActiveColumnFlags.Bytes) != 0) {
// Shorten the "...".
string bytesStr = parts.Bytes;
if (bytesStr != null && bytesStr.Length > bytesWidth) {
bytesStr = bytesStr.Substring(0, bytesWidth) + "+";
}
TextUtil.AppendPaddedString(sb, bytesStr, mColEnd[(int)Col.Bytes]);
}
if ((mLeftFlags & ActiveColumnFlags.Flags) != 0) {
TextUtil.AppendPaddedString(sb, parts.Flags, mColEnd[(int)Col.Flags]);
}
if ((mLeftFlags & ActiveColumnFlags.Attr) != 0) {
TextUtil.AppendPaddedString(sb, parts.Attr, mColEnd[(int)Col.Attr]);
}
TextUtil.AppendPaddedString(sb, parts.Label, mColEnd[(int)Col.Label]);
TextUtil.AppendPaddedString(sb, parts.Opcode, mColEnd[(int)Col.Opcode]);
TextUtil.AppendPaddedString(sb, parts.Operand, mColEnd[(int)Col.Operand]);
if (string.IsNullOrEmpty(parts.Comment)) {
// Trim trailing spaces off opcode or operand. Would be more efficient
// to just not generate the spaces, but this is simpler and we're not
// in a hurry.
TextUtil.TrimEnd(sb);
} else {
sb.Append(parts.Comment);
}
break;
case LineListGen.Line.Type.LongComment:
case LineListGen.Line.Type.Note:
if (line.LineType == LineListGen.Line.Type.Note && !IncludeNotes) {
return false;
}
if (mColEnd[(int)Col.Attr] != 0) {
// Long comments aren't the left-most field, so pad it out.
TextUtil.AppendPaddedString(sb, string.Empty, mColEnd[(int)Col.Attr]);
}
sb.Append(parts.Comment);
break;
case LineListGen.Line.Type.Blank:
break;
default:
Debug.Assert(false);
break;
}
return true;
}
private void GenerateCsvLine(int index, StringBuilder sb) {
LineListGen.Line line = mCodeLineList[index];
DisplayList.FormattedParts parts = mCodeLineList.GetFormattedParts(index);
if ((mLeftFlags & ActiveColumnFlags.Offset) != 0) {
sb.Append(TextUtil.EscapeCSV(parts.Offset)); sb.Append(',');
}
if ((mLeftFlags & ActiveColumnFlags.Address) != 0) {
sb.Append(TextUtil.EscapeCSV(parts.Addr)); sb.Append(',');
}
if ((mLeftFlags & ActiveColumnFlags.Bytes) != 0) {
sb.Append(TextUtil.EscapeCSV(parts.Bytes)); sb.Append(',');
}
if ((mLeftFlags & ActiveColumnFlags.Flags) != 0) {
sb.Append(TextUtil.EscapeCSV(parts.Flags)); sb.Append(',');
}
if ((mLeftFlags & ActiveColumnFlags.Attr) != 0) {
sb.Append(TextUtil.EscapeCSV(parts.Attr)); sb.Append(',');
}
if (parts.IsLongComment) {
// put the comment in the Label column
sb.Append(TextUtil.EscapeCSV(parts.Comment)); sb.Append(",,,");
} else {
sb.Append(TextUtil.EscapeCSV(parts.Label)); sb.Append(',');
sb.Append(TextUtil.EscapeCSV(parts.Opcode)); sb.Append(',');
sb.Append(TextUtil.EscapeCSV(parts.Operand)); sb.Append(',');
sb.Append(TextUtil.EscapeCSV(parts.Comment));
}
}
public void OutputToHtml(string pathName, bool overwriteCss) {
Debug.WriteLine("HTML"); // TODO
}
}
}

View File

@ -1256,9 +1256,17 @@ namespace SourceGen {
int cycles = mProject.CpuDef.GetCycles(op.Opcode, attr.StatusFlags,
attr.BranchTaken, branchCross);
if (cycles > 0) {
eolComment = cycles.ToString() + " " + eolComment;
if (!string.IsNullOrEmpty(eolComment)) {
eolComment = cycles.ToString() + " " + eolComment;
} else {
eolComment = cycles.ToString();
}
} else {
eolComment = (-cycles).ToString() + "+ " + eolComment;
if (!string.IsNullOrEmpty(eolComment)) {
eolComment = (-cycles).ToString() + "+ " + eolComment;
} else {
eolComment = (-cycles).ToString() + "+";
}
}
}
string commentStr = mFormatter.FormatEolComment(eolComment);

View File

@ -185,7 +185,8 @@ namespace SourceGen {
public enum ClipLineFormat {
Unknown = -1,
AssemblerSource = 0,
Disassembly = 1
Disassembly = 1,
AllColumns = 2
}
/// <summary>
@ -1291,14 +1292,29 @@ namespace SourceGen {
/// </summary>
public void CopyToClipboard() {
DisplayListSelection selection = mMainWin.CodeDisplayList.SelectedIndices;
if (selection.Count == 0) {
Debug.WriteLine("Selection is empty!");
return;
}
ClipLineFormat format = (ClipLineFormat)AppSettings.Global.GetEnum(
AppSettings.CLIP_LINE_FORMAT,
typeof(ClipLineFormat),
(int)ClipLineFormat.AssemblerSource);
Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter);
eport.SelectionToText(selection, format, true,
out string fullText, out string csvText);
int[] rightWidths = new int[] { 9, 8, 11, 100 };
Exporter.ActiveColumnFlags colFlags = Exporter.ActiveColumnFlags.None;
if (format == ClipLineFormat.Disassembly) {
colFlags |= Exporter.ActiveColumnFlags.Address |
Exporter.ActiveColumnFlags.Bytes;
} else if (format == ClipLineFormat.AllColumns) {
colFlags = Exporter.ActiveColumnFlags.ALL;
}
Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter,
colFlags, rightWidths);
eport.Selection = selection;
eport.SelectionToString(true, out string fullText, out string csvText);
DataObject dataObject = new DataObject();
dataObject.SetText(fullText.ToString());
@ -1954,12 +1970,45 @@ namespace SourceGen {
}
public void Export() {
Export dlg = new Export(mMainWin);
string outName;
if (string.IsNullOrEmpty(mProjectPathName)) {
outName = Path.GetFileName(mDataPathName);
} else {
outName = Path.GetFileName(mProjectPathName);
}
Export dlg = new Export(mMainWin, outName);
if (dlg.ShowDialog() == false) {
return;
}
// TODO
int[] rightWidths = new int[] {
dlg.AsmLabelColWidth, dlg.AsmOpcodeColWidth,
dlg.AsmOperandColWidth, dlg.AsmCommentColWidth
};
Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter,
dlg.ColFlags, rightWidths);
eport.IncludeNotes = dlg.IncludeNotes;
if (dlg.SelectionOnly) {
DisplayListSelection selection = mMainWin.CodeDisplayList.SelectedIndices;
if (selection.Count == 0) {
// no selection == select all
selection = null;
}
eport.Selection = selection;
}
try {
Mouse.OverrideCursor = Cursors.Wait;
if (dlg.GenType == WpfGui.Export.GenerateFileType.Html) {
eport.OutputToHtml(dlg.PathName, dlg.OverwriteCss);
} else {
eport.OutputToText(dlg.PathName, dlg.TextModeCsv);
}
} finally {
Mouse.OverrideCursor = null;
}
}
public void Find() {

View File

@ -30,6 +30,7 @@ limitations under the License.
<system:String x:Key="str_AsmMismatchDataFmt">Assembled output does not match: offset +{0:x6} has value ${1:x2}, expected ${2:x2}.</system:String>
<system:String x:Key="str_AsmMismatchLengthFmt">Assembled output does not match: length is {0}, expected {1}.</system:String>
<system:String x:Key="str_AsmOutputNotFound">Expected output file wasn't created</system:String>
<system:String x:Key="str_ClipformatAllColumns">All Columns</system:String>
<system:String x:Key="str_ClipformatAssemblerSource">Assembler Source</system:String>
<system:String x:Key="str_ClipformatDisassembly">Disassembly</system:String>
<system:String x:Key="str_DefaultHeaderCommentFmt">6502bench SourceGen v{0}</system:String>
@ -64,9 +65,12 @@ limitations under the License.
<system:String x:Key="str_ExternalFileBadDirFmt" xml:space="preserve">Symbol files and extension scripts must live in the application runtime directory ({0}) or project directory ({1}).&#x0d;&#x0d;File {2} lives elsewhere.</system:String>
<system:String x:Key="str_ExternalFileBadDirCaption">File Not In Runtime Directory</system:String>
<system:String x:Key="str_FileFilterAll">All files (*.*)|*.*</system:String>
<system:String x:Key="str_FileFilterCs">C# Source Files(*.cs)|*.cs</system:String>
<system:String x:Key="str_FileFilterDis65">SourceGen projects(*.dis65)|*.dis65</system:String>
<system:String x:Key="str_FileFilterCs">C# Source Files (*.cs)|*.cs</system:String>
<system:String x:Key="str_FileFilterCsv">CSV files (*.csv)|*.csv</system:String>
<system:String x:Key="str_FileFilterDis65">SourceGen projects (*.dis65)|*.dis65</system:String>
<system:String x:Key="str_FileFilterHtml">HTML files (*.html)|*.html</system:String>
<system:String x:Key="str_FileFilterSym65">SourceGen symbols (*.sym65)|*.sym65</system:String>
<system:String x:Key="str_FileFilterText">Text files (*.txt)|*.txt</system:String>
<system:String x:Key="str_FileInfoFmt">File is {0:N1} KB of raw data.</system:String>
<system:String x:Key="str_FindReachedStart">Find reached the starting point of the search.</system:String>
<system:String x:Key="str_FindReachedStartCaption">Find...</system:String>

View File

@ -53,6 +53,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_DefaultC64PetsciiDelimPat");
public static string DEFAULT_C64_SCREEN_CODE_DELIM_PAT =
(string)Application.Current.FindResource("str_DefaultC64ScreenCodeDelimPat");
public static string CLIPFORMAT_ALL_COLUMNS =
(string)Application.Current.FindResource("str_ClipformatAllColumns");
public static string CLIPFORMAT_ASSEMBLER_SOURCE =
(string)Application.Current.FindResource("str_ClipformatAssemblerSource");
public static string CLIPFORMAT_DISASSEMBLY =
@ -111,10 +113,16 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_FileFilterAll");
public static string FILE_FILTER_CS =
(string)Application.Current.FindResource("str_FileFilterCs");
public static string FILE_FILTER_CSV =
(string)Application.Current.FindResource("str_FileFilterCsv");
public static string FILE_FILTER_DIS65 =
(string)Application.Current.FindResource("str_FileFilterDis65");
public static string FILE_FILTER_HTML =
(string)Application.Current.FindResource("str_FileFilterHtml");
public static string FILE_FILTER_SYM65 =
(string)Application.Current.FindResource("str_FileFilterSym65");
public static string FILE_FILTER_TEXT =
(string)Application.Current.FindResource("str_FileFilterText");
public static string FILE_INFO_FMT =
(string)Application.Current.FindResource("str_FileInfoFmt");
public static string FIND_REACHED_START =

View File

@ -247,7 +247,9 @@ namespace SourceGen.WpfGui {
new ClipboardFormatItem(Res.Strings.CLIPFORMAT_ASSEMBLER_SOURCE,
MainController.ClipLineFormat.AssemblerSource),
new ClipboardFormatItem(Res.Strings.CLIPFORMAT_DISASSEMBLY,
MainController.ClipLineFormat.Disassembly)
MainController.ClipLineFormat.Disassembly),
new ClipboardFormatItem(Res.Strings.CLIPFORMAT_ALL_COLUMNS,
MainController.ClipLineFormat.AllColumns)
};
// ItemsSource for combo box
public ClipboardFormatItem[] ClipboardFormatItems {

View File

@ -23,7 +23,8 @@ limitations under the License.
mc:Ignorable="d"
Title="Export Source"
SizeToContent="WidthAndHeight" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner">
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Loaded="Window_Loaded">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@ -32,12 +33,15 @@ limitations under the License.
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="General Options" Padding="2,4">
<StackPanel>
<CheckBox Content="Include notes"/>
<CheckBox Content="Show address column" Margin="0,2,0,0"/>
<CheckBox Content="Show bytes column" Margin="0,2,0,0"/>
<CheckBox Content="Include notes" IsChecked="{Binding IncludeNotes}"/>
<CheckBox Content="Show Offset column" Margin="0,2,0,0" IsChecked="{Binding ShowOffset}"/>
<CheckBox Content="Show Address column" Margin="0,2,0,0" IsChecked="{Binding ShowAddress}"/>
<CheckBox Content="Show Bytes column" Margin="0,2,0,0" IsChecked="{Binding ShowBytes}"/>
<CheckBox Content="Show Flags column" Margin="0,2,0,0" IsChecked="{Binding ShowFlags}"/>
<CheckBox Content="Show Attributes column" Margin="0,2,0,0" IsChecked="{Binding ShowAttr}"/>
<TextBlock Text="Column widths (label, opcode, operand, comment):" Margin="0,4,0,0"/>
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
@ -82,20 +86,23 @@ limitations under the License.
</TextBox.Text>
</TextBox>
</StackPanel>
<CheckBox Content="Include only selected lines" Margin="0,4,0,0" IsChecked="{Binding SelectionOnly}"/>
</StackPanel>
</GroupBox>
<GroupBox Grid.Row="1" Header="Text Options" Padding="2,4">
<StackPanel>
<RadioButton Content="Plain text (UTF-8)" GroupName="TextMode"/>
<RadioButton Content="CSV" GroupName="TextMode" Margin="0,2,0,0"/>
<RadioButton Content="Plain text (UTF-8)" GroupName="TextMode" IsChecked="{Binding TextModePlain}"/>
<RadioButton Content="CSV" GroupName="TextMode" Margin="0,2,0,0" IsChecked="{Binding TextModeCsv}"/>
</StackPanel>
</GroupBox>
<GroupBox Grid.Row="2" Header="HTML Options" Padding="2,4">
<StackPanel>
<CheckBox Content="Include symbol table"/>
<CheckBox Content="Overwrite CSS file" Margin="0,2,0,0"/>
<CheckBox Content="Include symbol table" IsChecked="{Binding IncludeSymbolTable}"/>
<CheckBox Content="Overwrite CSS file" Margin="0,2,0,0" IsChecked="{Binding OverwriteCss}"/>
</StackPanel>
</GroupBox>
@ -107,9 +114,9 @@ limitations under the License.
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="Generate HTML" Width="100"
IsEnabled="{Binding IsValid}"/>
IsEnabled="{Binding IsValid}" Click="GenerateHtmlButton_Click"/>
<Button Grid.Column="1" Content="Generate Text" Width="100" Margin="8,0,0,0"
IsEnabled="{Binding IsValid}"/>
IsEnabled="{Binding IsValid}" Click="GenerateTextButton_Click"/>
<Button Grid.Column="2" Content="Cancel" IsCancel="True" Width="70" Margin="8,0,0,0"
HorizontalAlignment="Right"/>
</Grid>

View File

@ -16,15 +16,85 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using CommonUtil;
using Microsoft.Win32;
namespace SourceGen.WpfGui {
/// <summary>
/// Export selection dialog.
/// </summary>
public partial class Export : Window, INotifyPropertyChanged {
/// <summary>
/// Indicates which type of file the user wants to generate.
/// </summary>
public enum GenerateFileType {
Unknown = 0,
Text,
Html
}
/// <summary>
/// Result: type of file to generate.
/// </summary>
public GenerateFileType GenType { get; private set; }
/// <summary>
/// Result: full pathname of output file.
/// </summary>
public string PathName { get; private set; }
/// <summary>
/// Result: flags indicating which of the optional columns should be shown.
/// </summary>
public Exporter.ActiveColumnFlags ColFlags { get; private set; }
private bool mIncludeNotes;
public bool IncludeNotes {
get { return mIncludeNotes; }
set { mIncludeNotes = value; OnPropertyChanged(); }
}
private bool mShowOffset;
public bool ShowOffset {
get { return mShowOffset; }
set { mShowOffset = value; OnPropertyChanged(); }
}
private bool mShowAddress;
public bool ShowAddress {
get { return mShowAddress; }
set { mShowAddress = value; OnPropertyChanged(); }
}
private bool mShowBytes;
public bool ShowBytes {
get { return mShowBytes; }
set { mShowBytes = value; OnPropertyChanged(); }
}
private bool mShowFlags;
public bool ShowFlags {
get { return mShowFlags; }
set { mShowFlags = value; OnPropertyChanged(); }
}
private bool mShowAttrs;
public bool ShowAttr {
get { return mShowAttrs; }
set { mShowAttrs = value; OnPropertyChanged(); }
}
private bool mSelectionOnly;
public bool SelectionOnly {
get { return mSelectionOnly; }
set { mSelectionOnly = value; OnPropertyChanged(); }
}
//
// Numeric input fields, bound directly to TextBox.Text. These rely on a TextChanged
// field to update the IsValid flag, because the "set" method is only called when the
@ -71,6 +141,36 @@ namespace SourceGen.WpfGui {
}
}
private bool mTextModePlain;
public bool TextModePlain {
get { return mTextModePlain; }
set { mTextModePlain = value; OnPropertyChanged(); }
}
private bool mTextModeCsv;
public bool TextModeCsv {
get { return mTextModeCsv; }
set { mTextModeCsv = value; OnPropertyChanged(); }
}
private bool mIncludeSymbolTable;
public bool IncludeSymbolTable {
get { return mIncludeSymbolTable; }
set { mIncludeSymbolTable = value; OnPropertyChanged(); }
}
private bool mOverwriteCss;
public bool OverwriteCss {
get { return mOverwriteCss; }
set { mOverwriteCss = value; OnPropertyChanged(); }
}
private enum TextMode {
Unknown = 0,
PlainText,
Csv
}
/// <summary>
/// Valid flag, used to enable the "generate" buttons.
/// </summary>
@ -80,23 +180,96 @@ namespace SourceGen.WpfGui {
}
private bool mIsValid;
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Export(Window owner) {
/// <summary>
/// Filename of project file.
/// </summary>
string mProjectFileName;
public Export(Window owner, string projectFileName) {
InitializeComponent();
Owner = owner;
DataContext = this;
mProjectFileName = projectFileName;
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
// TODO
IncludeNotes = AppSettings.Global.GetBool(AppSettings.EXPORT_INCLUDE_NOTES, false);
ShowOffset = AppSettings.Global.GetBool(AppSettings.EXPORT_SHOW_OFFSET, false);
ShowAddress = AppSettings.Global.GetBool(AppSettings.EXPORT_SHOW_ADDR, false);
ShowBytes = AppSettings.Global.GetBool(AppSettings.EXPORT_SHOW_BYTES, false);
ShowFlags = AppSettings.Global.GetBool(AppSettings.EXPORT_SHOW_FLAGS, false);
ShowAttr = AppSettings.Global.GetBool(AppSettings.EXPORT_SHOW_ATTR, false);
SelectionOnly = AppSettings.Global.GetBool(AppSettings.EXPORT_SELECTION_ONLY, false);
IncludeSymbolTable = AppSettings.Global.GetBool(AppSettings.EXPORT_INCLUDE_SYMTAB,
false);
int[] colWidths = new int[] { 9, 8, 11, 72 }; // 100-col output
string colStr = AppSettings.Global.GetString(AppSettings.EXPORT_COL_WIDTHS, null);
try {
if (!string.IsNullOrEmpty(colStr)) {
colWidths = TextUtil.DeserializeIntArray(colStr);
if (colWidths.Length != 4) {
throw new InvalidOperationException("Bad length " + colWidths.Length);
}
}
} finally {
AsmLabelColWidth = colWidths[0];
AsmOpcodeColWidth = colWidths[1];
AsmOperandColWidth = colWidths[2];
AsmCommentColWidth = colWidths[3];
}
TextMode mode = (TextMode)AppSettings.Global.GetEnum(AppSettings.EXPORT_TEXT_MODE,
typeof(TextMode), (int)TextMode.PlainText);
if (mode == TextMode.PlainText) {
TextModePlain = true;
} else {
TextModeCsv = true;
}
}
/// <summary>
/// Saves the settings to the global settings object.
/// </summary>
private void SaveSettings() {
AppSettings.Global.SetBool(AppSettings.EXPORT_INCLUDE_NOTES, IncludeNotes);
AppSettings.Global.SetBool(AppSettings.EXPORT_SHOW_OFFSET, ShowOffset);
AppSettings.Global.SetBool(AppSettings.EXPORT_SHOW_ADDR, ShowAddress);
AppSettings.Global.SetBool(AppSettings.EXPORT_SHOW_BYTES, ShowBytes);
AppSettings.Global.SetBool(AppSettings.EXPORT_SHOW_FLAGS, ShowFlags);
AppSettings.Global.SetBool(AppSettings.EXPORT_SHOW_ATTR, ShowAttr);
AppSettings.Global.SetBool(AppSettings.EXPORT_SELECTION_ONLY, SelectionOnly);
AppSettings.Global.SetBool(AppSettings.EXPORT_INCLUDE_SYMTAB, IncludeSymbolTable);
int[] colWidths = new int[] {
AsmLabelColWidth, AsmOpcodeColWidth, AsmOperandColWidth, AsmCommentColWidth
};
string cereal = TextUtil.SerializeIntArray(colWidths);
AppSettings.Global.SetString(AppSettings.EXPORT_COL_WIDTHS, cereal);
// OverwriteCss is not saved, since there's generally no reason to replace it.
// Forcing the user to check it every time is essentially the same as popping
// up an "are you sure you want to overwrite" dialog, but less annoying for the
// common case.
TextMode mode;
if (TextModePlain) {
mode = TextMode.PlainText;
} else {
mode = TextMode.Csv;
}
AppSettings.Global.SetEnum(AppSettings.EXPORT_TEXT_MODE, typeof(TextMode), (int)mode);
}
/// <summary>
/// Updates the state of the UI.
/// </summary>
private void UpdateControls() {
bool isValid = true;
@ -108,8 +281,69 @@ namespace SourceGen.WpfGui {
IsValid = isValid;
}
/// <summary>
/// Called whenever something is typed in one of the column width entry boxes.
/// </summary>
/// <remarks>
/// We need this because we're using validated int fields rather than strings. The
/// "set" call doesn't fire if the user types garbage.
/// </remarks>
private void AsmColWidthTextBox_TextChanged(object sender, TextChangedEventArgs e) {
UpdateControls();
}
private void GenerateHtmlButton_Click(object sender, RoutedEventArgs e) {
GenType = GenerateFileType.Html;
Finish(Res.Strings.FILE_FILTER_HTML, ".html");
}
private void GenerateTextButton_Click(object sender, RoutedEventArgs e) {
GenType = GenerateFileType.Text;
if (TextModeCsv) {
Finish(Res.Strings.FILE_FILTER_CSV, ".csv");
} else {
Finish(Res.Strings.FILE_FILTER_TEXT, ".txt");
}
}
/// <summary>
/// Handles a click on one of the "generate" buttons.
/// </summary>
private void Finish(string fileFilter, string fileExt) {
Debug.Assert(mProjectFileName == Path.GetFileName(mProjectFileName));
string initialName = Path.GetFileNameWithoutExtension(mProjectFileName) + fileExt;
SaveFileDialog fileDlg = new SaveFileDialog() {
Filter = fileFilter + "|" + Res.Strings.FILE_FILTER_ALL,
FilterIndex = 1,
ValidateNames = true,
AddExtension = true, // doesn't add extension if non-ext file exists
FileName = initialName
};
if (fileDlg.ShowDialog() != true) {
return;
}
PathName = Path.GetFullPath(fileDlg.FileName);
ColFlags = Exporter.ActiveColumnFlags.None;
if (ShowOffset) {
ColFlags |= Exporter.ActiveColumnFlags.Offset;
}
if (ShowAddress) {
ColFlags |= Exporter.ActiveColumnFlags.Address;
}
if (ShowBytes) {
ColFlags |= Exporter.ActiveColumnFlags.Bytes;
}
if (ShowFlags) {
ColFlags |= Exporter.ActiveColumnFlags.Flags;
}
if (ShowAttr) {
ColFlags |= Exporter.ActiveColumnFlags.Attr;
}
SaveSettings();
DialogResult = true;
}
}
}