From e5104dc2e7d35479293774c2a9cf207e68a7894e Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Tue, 10 Sep 2019 17:43:31 -0700 Subject: [PATCH] Add first pass at source export dialog Ported the column width stuff from EditAppSettings, which it turns out can be simplified slightly. Moved the clipboard copy code out into its own class. Disabled "File > Print", which has never done anything and isn't likely to do anything in the near future. Also, added a note to 2019-local-variables about a test case it should probably have. --- SourceGen/Exporter.cs | 155 ++++++++++++++++++ SourceGen/MainController.cs | 104 ++---------- .../SGTestData/2019-local-variables.dis65 | 5 +- SourceGen/SourceGen.csproj | 8 + SourceGen/WpfGui/EditAppSettings.xaml | 14 +- SourceGen/WpfGui/EditAppSettings.xaml.cs | 15 +- SourceGen/WpfGui/Export.xaml | 117 +++++++++++++ SourceGen/WpfGui/Export.xaml.cs | 115 +++++++++++++ SourceGen/WpfGui/MainWindow.xaml | 6 +- SourceGen/WpfGui/MainWindow.xaml.cs | 12 +- 10 files changed, 441 insertions(+), 110 deletions(-) create mode 100644 SourceGen/Exporter.cs create mode 100644 SourceGen/WpfGui/Export.xaml create mode 100644 SourceGen/WpfGui/Export.xaml.cs diff --git a/SourceGen/Exporter.cs b/SourceGen/Exporter.cs new file mode 100644 index 0000000..fb8a97c --- /dev/null +++ b/SourceGen/Exporter.cs @@ -0,0 +1,155 @@ +/* + * Copyright 2019 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using Asm65; +using CommonUtil; +using ClipLineFormat = SourceGen.MainController.ClipLineFormat; + +namespace SourceGen { + public class Exporter { + /// + /// Project reference. + /// + private DisasmProject mProject; + + /// + /// List of formatted parts. + /// + private LineListGen mCodeLineList; + + /// + /// Text formatter. + /// + private Formatter mFormatter; + + + /// + /// Constructor. + /// + public Exporter(DisasmProject project, LineListGen codeLineList, Formatter formatter) { + mProject = project; + mCodeLineList = codeLineList; + mFormatter = formatter; + } + + /// + /// Converts the selected lines to formatted text. + /// + /// Set of lines to select. + /// Line format. + /// Result; holds text of all selected lines. + /// Result; holds text of all selected lines, in CSV format. + 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); + StringBuilder csv = null; + if (addCsv) { + 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; + } + 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)); + csv.Append("\r\n"); + } + } + + fullText = plainText.ToString(); + csvText = csv.ToString(); + } + } +} diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index d4446f8..27039ac 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -1290,99 +1290,15 @@ namespace SourceGen { /// Copies the selection to the clipboard as formatted text. /// public void CopyToClipboard() { - const bool addCsv = true; - + DisplayListSelection selection = mMainWin.CodeDisplayList.SelectedIndices; ClipLineFormat format = (ClipLineFormat)AppSettings.Global.GetEnum( AppSettings.CLIP_LINE_FORMAT, typeof(ClipLineFormat), (int)ClipLineFormat.AssemblerSource); - DisplayListSelection selection = mMainWin.CodeDisplayList.SelectedIndices; - StringBuilder fullText = new StringBuilder(selection.Count * 50); - StringBuilder csv = new StringBuilder(selection.Count * 40); - StringBuilder sb = new StringBuilder(100); - int addrAdj = mProject.CpuDef.HasAddr16 ? 6 : 9; - int disAdj = 0; - int bytesWidth = 0; - if (format == 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 = (mFormatterConfig.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 = CodeLineList[index]; - DisplayList.FormattedParts parts = CodeLineList.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 (format == 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 (format == 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; - } - fullText.Append(sb); - - 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)); - csv.Append("\r\n"); - } - - sb.Clear(); - } + Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter); + eport.SelectionToText(selection, format, true, + out string fullText, out string csvText); DataObject dataObject = new DataObject(); dataObject.SetText(fullText.ToString()); @@ -1395,8 +1311,9 @@ namespace SourceGen { // should probably be optional.) // // https://stackoverflow.com/a/369219/294248 + const bool addCsv = true; if (addCsv) { - byte[] csvData = Encoding.UTF8.GetBytes(csv.ToString()); + byte[] csvData = Encoding.UTF8.GetBytes(csvText.ToString()); MemoryStream stream = new MemoryStream(csvData); dataObject.SetData(DataFormats.CommaSeparatedValue, stream); } @@ -2036,6 +1953,15 @@ namespace SourceGen { } } + public void Export() { + Export dlg = new Export(mMainWin); + if (dlg.ShowDialog() == false) { + return; + } + + // TODO + } + public void Find() { FindBox dlg = new FindBox(mMainWin, mFindString); if (dlg.ShowDialog() == true) { diff --git a/SourceGen/SGTestData/2019-local-variables.dis65 b/SourceGen/SGTestData/2019-local-variables.dis65 index 88b9f38..e7eb92c 100644 --- a/SourceGen/SGTestData/2019-local-variables.dis65 +++ b/SourceGen/SGTestData/2019-local-variables.dis65 @@ -2,7 +2,7 @@ { "_ContentVersion":2,"FileDataLength":137,"FileDataCrc32":708791740,"ProjectProps":{ "CpuName":"65816","IncludeUndocumentedInstr":false,"EntryFlags":32702671,"AutoLabelStyle":"Simple","AnalysisParams":{ -"AnalyzeUncategorizedData":true,"DefaultTextScanMode":"LowHighAscii","MinCharsForString":4,"SeekNearbyTargets":true}, +"AnalyzeUncategorizedData":true,"DefaultTextScanMode":"LowHighAscii","MinCharsForString":4,"SeekNearbyTargets":true,"SmartPlpHandling":false}, "PlatformSymbolFileIdentifiers":[],"ExtensionScriptFileIdentifiers":[],"ProjectSyms":{ "CONST_ONE":{ "DataDescriptor":{ @@ -38,7 +38,8 @@ "81":{ "Text":"Test name redefinition. This is mostly of interest for assemblers without redefinable variables, but also of interest to the cross-reference window.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}}, "Notes":{ -}, +"0":{ +"Text":"Should add a variable that conflicts with an auto-label.","BoxMode":false,"MaxWidth":80,"BackgroundColor":-256}}, "UserLabels":{ "87":{ "Label":"PTR_1","Value":4183,"Source":"User","Type":"LocalOrGlobalAddr"}, diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj index a060190..a35cd11 100644 --- a/SourceGen/SourceGen.csproj +++ b/SourceGen/SourceGen.csproj @@ -76,6 +76,7 @@ + @@ -127,6 +128,9 @@ EditProjectProperties.xaml + + Export.xaml + FindBox.xaml @@ -290,6 +294,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/SourceGen/WpfGui/EditAppSettings.xaml b/SourceGen/WpfGui/EditAppSettings.xaml index 543a499..f6d08c8 100644 --- a/SourceGen/WpfGui/EditAppSettings.xaml +++ b/SourceGen/WpfGui/EditAppSettings.xaml @@ -88,7 +88,7 @@ limitations under the License. - @@ -444,8 +444,7 @@ limitations under the License. - + @@ -454,8 +453,7 @@ limitations under the License. - + @@ -464,8 +462,7 @@ limitations under the License. - + @@ -474,8 +471,7 @@ limitations under the License. - + diff --git a/SourceGen/WpfGui/EditAppSettings.xaml.cs b/SourceGen/WpfGui/EditAppSettings.xaml.cs index 9ea72a5..a845c7a 100644 --- a/SourceGen/WpfGui/EditAppSettings.xaml.cs +++ b/SourceGen/WpfGui/EditAppSettings.xaml.cs @@ -577,6 +577,11 @@ namespace SourceGen.WpfGui { // Apply/OK buttons when invalid input is present. Instead we just set the width to // the minimum value when validation fails. // + // Note that the "set" property is only called when the value changes, which only + // happens when a valid integer is typed. If you enter garbage, the dirty flag doesn't + // get set. That's just fine for us, but if you need to disable an "is valid" flag + // on bad input you can't rely on updating it at "set" time. + // // See also https://stackoverflow.com/a/44586784/294248 // private int mAsmLabelColWidth; @@ -586,7 +591,7 @@ namespace SourceGen.WpfGui { if (mAsmLabelColWidth != value) { mAsmLabelColWidth = value; OnPropertyChanged(); - IsDirty = true; + AsmColWidthTextChanged(); } } } @@ -597,7 +602,7 @@ namespace SourceGen.WpfGui { if (mAsmOpcodeColWidth != value) { mAsmOpcodeColWidth = value; OnPropertyChanged(); - IsDirty = true; + AsmColWidthTextChanged(); } } } @@ -608,7 +613,7 @@ namespace SourceGen.WpfGui { if (mAsmOperandColWidth != value) { mAsmOperandColWidth = value; OnPropertyChanged(); - IsDirty = true; + AsmColWidthTextChanged(); } } } @@ -619,7 +624,7 @@ namespace SourceGen.WpfGui { if (mAsmCommentColWidth != value) { mAsmCommentColWidth = value; OnPropertyChanged(); - IsDirty = true; + AsmColWidthTextChanged(); } } } @@ -711,7 +716,7 @@ namespace SourceGen.WpfGui { /// populated. That means we'll have incorrect intermediate states, but should /// finish up correctly. /// - private void AsmColWidthTextBox_TextChanged(object sender, TextChangedEventArgs e) { + private void AsmColWidthTextChanged() { AssemblerInfo asm = (AssemblerInfo)asmConfigComboBox.SelectedItem; if (asm == null) { // fires during dialog initialization, before anything is selected diff --git a/SourceGen/WpfGui/Export.xaml b/SourceGen/WpfGui/Export.xaml new file mode 100644 index 0000000..25285b7 --- /dev/null +++ b/SourceGen/WpfGui/Export.xaml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +