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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +