From 1373ffd8e350bb7f0390622d959c5b652eeeac95 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sun, 29 Dec 2019 17:59:35 -0800 Subject: [PATCH] Add file slicer tool The tool allows you to cut a piece out of a file by specifying an offset and a length. A pair of hex dumps helps you verify that the positions are correct. Also, minor cleanups elsewhere. --- Asm65/Formatter.cs | 31 +- Asm65/Number.cs | 42 +- SourceGen/MainController.cs | 81 ++-- SourceGen/RuntimeData/Help/tools.html | 39 +- SourceGen/SourceGen.csproj | 7 + SourceGen/Tools/WpfGui/FileConcatenator.xaml | 3 +- .../Tools/WpfGui/FileConcatenator.xaml.cs | 2 +- SourceGen/Tools/WpfGui/FileSlicer.xaml | 93 +++++ SourceGen/Tools/WpfGui/FileSlicer.xaml.cs | 364 ++++++++++++++++++ SourceGen/Tools/WpfGui/HexDumpViewer.xaml | 1 + SourceGen/Tools/WpfGui/HexDumpViewer.xaml.cs | 2 +- SourceGen/WpfGui/EditComment.xaml.cs | 3 +- SourceGen/WpfGui/EditDefSymbol.xaml.cs | 5 +- SourceGen/WpfGui/EditLabel.xaml.cs | 5 +- SourceGen/WpfGui/MainWindow.xaml | 7 +- SourceGen/WpfGui/MainWindow.xaml.cs | 4 + 16 files changed, 619 insertions(+), 70 deletions(-) create mode 100644 SourceGen/Tools/WpfGui/FileSlicer.xaml create mode 100644 SourceGen/Tools/WpfGui/FileSlicer.xaml.cs diff --git a/Asm65/Formatter.cs b/Asm65/Formatter.cs index cfbb8ab..580df13 100644 --- a/Asm65/Formatter.cs +++ b/Asm65/Formatter.cs @@ -985,7 +985,8 @@ namespace Asm65 { /// Start offset. /// Formatted string. public string FormatHexDump(byte[] data, int offset) { - FormatHexDumpCommon(data, offset); + int length = Math.Min(16, data.Length - offset); + FormatHexDumpCommon(data, offset, offset, length); // this is the only allocation return new string(mHexDumpBuffer); } @@ -998,14 +999,21 @@ namespace Asm65 { /// Start offset. /// StringBuilder that receives output. public void FormatHexDump(byte[] data, int offset, StringBuilder sb) { - FormatHexDumpCommon(data, offset); + int length = Math.Min(16, data.Length - offset); + FormatHexDumpCommon(data, offset, offset, length); + sb.Append(mHexDumpBuffer); + } + + public void FormatHexDump(byte[] data, int offset, int addr, int length, + StringBuilder sb) { + FormatHexDumpCommon(data, offset, addr, length); sb.Append(mHexDumpBuffer); } /// /// Formats up to 16 bytes of data into mHexDumpBuffer. /// - private void FormatHexDumpCommon(byte[] data, int offset) { + private void FormatHexDumpCommon(byte[] data, int offset, int addr, int length) { Debug.Assert(offset >= 0 && offset < data.Length); Debug.Assert(data.Length < (1 << 24)); const int dataCol = 8; @@ -1014,21 +1022,30 @@ namespace Asm65 { char[] hexChars = mFormatConfig.mUpperHexDigits ? sHexCharsUpper : sHexCharsLower; char[] outBuf = mHexDumpBuffer; + int skip = addr & 0x0f; // we skip this many entries... + offset -= skip; // ...so adjust offset to balance it + addr &= ~0x0f; + // address field - int addr = offset; for (int i = 5; i >= 0; i--) { outBuf[i] = hexChars[addr & 0x0f]; addr >>= 4; } - // hex digits and characters - int length = Math.Min(16, data.Length - offset); + // If addr doesn't start at xxx0, pad it. int index; - for (index = 0; index < length; index++) { + for (index = 0; index < skip; index++) { + outBuf[dataCol + index * 3] = outBuf[dataCol + index * 3 + 1] = + outBuf[asciiCol + index] = ' '; + } + + // hex digits and characters + for (int i = 0; i < length; i++) { byte val = data[offset + index]; outBuf[dataCol + index * 3] = hexChars[val >> 4]; outBuf[dataCol + index * 3 + 1] = hexChars[val & 0x0f]; outBuf[asciiCol + index] = CharConv(val); + index++; } // for partial line, clear out previous contents diff --git a/Asm65/Number.cs b/Asm65/Number.cs index 50261c3..67adc84 100644 --- a/Asm65/Number.cs +++ b/Asm65/Number.cs @@ -21,7 +21,7 @@ namespace Asm65 { /// /// Parses an integer in a variety of formats (hex, decimal, binary). We allow /// hex to be identified with a leading '$' as well as "0x". - /// + /// /// Trim whitespace before calling here. /// /// String to parse. @@ -57,5 +57,45 @@ namespace Asm65 { return true; } + + /// + /// Parses a long integer in a variety of formats (hex, decimal, binary). We allow + /// hex to be identified with a leading '$' as well as "0x". + /// + /// Trim whitespace before calling here. + /// + /// String to parse. + /// Integer value of string. + /// What base the string was in (2, 10, or 16). + /// True if the parsing was successful. + public static bool TryParseLong(string str, out long val, out int intBase) { + if (string.IsNullOrEmpty(str)) { + val = intBase = 0; + return false; + } + + if (str[0] == '$') { + intBase = 16; + str = str.Substring(1); // Convert functions don't like '$' + } else if (str.Length > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { + intBase = 16; + } else if (str[0] == '%') { + intBase = 2; + str = str.Substring(1); // Convert functions don't like '%' + } else { + intBase = 10; // try it as decimal + } + + try { + val = Convert.ToInt64(str, intBase); + //Debug.WriteLine("GOT " + val + " - " + intBase); + } catch (Exception) { + //Debug.WriteLine("TryParseInt failed on '" + str + "': " + ex.Message); + val = 0; + return false; + } + + return true; + } } } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index ad00dcf..8543b18 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -141,7 +141,7 @@ namespace SourceGen { /// /// This is shared with the DisplayList. /// - private Formatter mOutputFormatter; + private Formatter mFormatter; /// /// Pseudo-op names. @@ -180,7 +180,7 @@ namespace SourceGen { /// CPU definition used when the Formatter was created. If the CPU choice or /// inclusion of undocumented opcodes changes, we need to wipe the formatter. /// - private CpuDef mOutputFormatterCpuDef; + private CpuDef mFormatterCpuDef; /// /// Instruction description object. Used for Info window. @@ -486,10 +486,10 @@ namespace SourceGen { } - // Update the formatter, and null out mOutputFormatterCpuDef to force a refresh + // Update the formatter, and null out mFormatterCpuDef to force a refresh // of related items. - mOutputFormatter = new Formatter(mFormatterConfig); - mOutputFormatterCpuDef = null; + mFormatter = new Formatter(mFormatterConfig); + mFormatterCpuDef = null; // Set pseudo-op names. Entries aren't allowed to be blank, so we start with the // default values and merge in whatever the user has configured. @@ -737,7 +737,7 @@ namespace SourceGen { private void FinishPrep() { CodeLineList = new LineListGen(mProject, mMainWin.CodeDisplayList, - mOutputFormatter, mPseudoOpNames); + mFormatter, mPseudoOpNames); SetCodeLineListColorMultiplier(); string messages = mProject.LoadExternalFiles(); @@ -959,13 +959,13 @@ namespace SourceGen { // Changing the CPU type or whether undocumented instructions are supported // invalidates the Formatter's mnemonic cache. We can change these values // through undo/redo, so we need to check it here. - if (mOutputFormatterCpuDef != mProject.CpuDef) { // reference equality is fine + if (mFormatterCpuDef != mProject.CpuDef) { // reference equality is fine Debug.WriteLine("CpuDef has changed, resetting formatter (now " + mProject.CpuDef + ")"); - mOutputFormatter = new Formatter(mFormatterConfig); - CodeLineList.SetFormatter(mOutputFormatter); + mFormatter = new Formatter(mFormatterConfig); + CodeLineList.SetFormatter(mFormatter); CodeLineList.SetPseudoOpNames(mPseudoOpNames); - mOutputFormatterCpuDef = mProject.CpuDef; + mFormatterCpuDef = mProject.CpuDef; } if (reanalysisRequired != UndoableChange.ReanalysisScope.DisplayOnly) { @@ -978,7 +978,7 @@ namespace SourceGen { mReanalysisTimer.EndTask("Call DisasmProject.Analyze()"); mReanalysisTimer.StartTask("Update message list"); - mMainWin.UpdateMessageList(mProject.Messages, mOutputFormatter); + mMainWin.UpdateMessageList(mProject.Messages, mFormatter); mReanalysisTimer.EndTask("Update message list"); } @@ -1410,7 +1410,7 @@ namespace SourceGen { } else if (format == ClipLineFormat.AllColumns) { colFlags = Exporter.ActiveColumnFlags.ALL; } - Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter, + Exporter eport = new Exporter(mProject, CodeLineList, mFormatter, colFlags, rightWidths); eport.Selection = selection; @@ -1727,7 +1727,7 @@ namespace SourceGen { } EditAddress dlg = new EditAddress(mMainWin, firstOffset, nextOffset, nextAddr, - mProject, mOutputFormatter); + mProject, mFormatter); if (dlg.ShowDialog() != true) { return; } @@ -1826,7 +1826,7 @@ namespace SourceGen { Anattrib attr = mProject.GetAnattrib(offset); EditLabel dlg = new EditLabel(mMainWin, attr.Symbol, attr.Address, offset, - mProject.SymbolTable, mOutputFormatter); + mProject.SymbolTable, mFormatter); if (dlg.ShowDialog() != true) { return; } @@ -1907,7 +1907,7 @@ namespace SourceGen { mProject.LvTables.TryGetValue(offset, out LocalVariableTable oldLvt); EditLocalVariableTable dlg = new EditLocalVariableTable(mMainWin, mProject, - mOutputFormatter, oldLvt, offset); + mFormatter, oldLvt, offset); if (dlg.ShowDialog() != true) { return; } @@ -1953,7 +1953,7 @@ namespace SourceGen { } private void EditLongComment(int offset) { - EditLongComment dlg = new EditLongComment(mMainWin, mOutputFormatter); + EditLongComment dlg = new EditLongComment(mMainWin, mFormatter); if (mProject.LongComments.TryGetValue(offset, out MultiLineComment oldComment)) { dlg.LongComment = oldComment; } @@ -2042,7 +2042,7 @@ namespace SourceGen { private void EditInstructionOperand(int offset) { EditInstructionOperand dlg = new EditInstructionOperand(mMainWin, mProject, - offset, mOutputFormatter); + offset, mFormatter); if (dlg.ShowDialog() != true) { return; } @@ -2130,7 +2130,7 @@ namespace SourceGen { mProject.OperandFormats.TryGetValue(firstOffset.Value, out FormatDescriptor dfd); EditDataOperand dlg = - new EditDataOperand(mMainWin, mProject, mOutputFormatter, trs, dfd); + new EditDataOperand(mMainWin, mProject, mFormatter, trs, dfd); if (dlg.ShowDialog() == true) { // Merge the changes into the OperandFormats list. We need to remove all // FormatDescriptors that overlap the selected region. We don't need to @@ -2153,7 +2153,7 @@ namespace SourceGen { projectDir = Path.GetDirectoryName(mProjectPathName); } EditProjectProperties dlg = new EditProjectProperties(mMainWin, mProject.ProjectProps, - projectDir, mOutputFormatter, initialTab); + projectDir, mFormatter, initialTab); dlg.ShowDialog(); ProjectProperties newProps = dlg.NewProps; @@ -2185,7 +2185,7 @@ namespace SourceGen { DefSymbol origDefSym = mProject.ActiveDefSymbolList[symIndex]; Debug.Assert(origDefSym.SymbolSource == Symbol.Source.Project); - EditDefSymbol dlg = new EditDefSymbol(mMainWin, mOutputFormatter, + EditDefSymbol dlg = new EditDefSymbol(mMainWin, mFormatter, mProject.ProjectProps.ProjectSyms, origDefSym, null); if (dlg.ShowDialog() == true) { ProjectProperties newProps = new ProjectProperties(mProject.ProjectProps); @@ -2245,7 +2245,7 @@ namespace SourceGen { mProject.VisualizationSets.TryGetValue(offset, out VisualizationSet curVisSet); EditVisualizationSet dlg = new EditVisualizationSet(mMainWin, mProject, - mOutputFormatter, curVisSet, offset); + mFormatter, curVisSet, offset); if (dlg.ShowDialog() != true) { return; } @@ -2307,7 +2307,7 @@ namespace SourceGen { dlg.AsmLabelColWidth, dlg.AsmOpcodeColWidth, dlg.AsmOperandColWidth, dlg.AsmCommentColWidth }; - Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter, + Exporter eport = new Exporter(mProject, CodeLineList, mFormatter, dlg.ColFlags, rightWidths); eport.IncludeNotes = dlg.IncludeNotes; eport.GenerateImageFiles = dlg.GenerateImageFiles; @@ -2526,7 +2526,7 @@ namespace SourceGen { } FormatAddressTable dlg = new FormatAddressTable(mMainWin, mProject, trs, - mOutputFormatter); + mFormatter); dlg.ShowDialog(); if (dlg.DialogResult != true) { @@ -2583,7 +2583,7 @@ namespace SourceGen { } int offset = CodeLineList[index].FileOffset; - GotoBox dlg = new GotoBox(mMainWin, mProject, offset, mOutputFormatter); + GotoBox dlg = new GotoBox(mMainWin, mProject, offset, mFormatter); if (dlg.ShowDialog() == true) { GoToLocation(new NavStack.Location(dlg.TargetOffset, 0, false), GoToMode.JumpToCodeData, true); @@ -2696,7 +2696,7 @@ namespace SourceGen { // We're comparing to the formatted strings -- safer than trying to find the symbol // in the table and then guess at how the table arranges itself for display -- so we // need to compare the formatted form of the label. - string cmpStr = mOutputFormatter.FormatVariableLabel(symRef.Label); + string cmpStr = mFormatter.FormatVariableLabel(symRef.Label); int lineIndex = CodeLineList.FindLineIndexByOffset(varOffset); while (lineIndex < mProject.FileDataLength) { LineListGen.Line line = CodeLineList[lineIndex]; @@ -3125,7 +3125,7 @@ namespace SourceGen { // when it's not on top, it can sit behind the main app window until you // double-click something else. mHexDumpDialog = new Tools.WpfGui.HexDumpViewer(null, - mProject.FileData, mOutputFormatter); + mProject.FileData, mFormatter); mHexDumpDialog.Closing += (sender, e) => { Debug.WriteLine("Hex dump dialog closed"); //showHexDumpToolStripMenuItem.Checked = false; @@ -3448,7 +3448,7 @@ namespace SourceGen { } // TODO(someday): localization - Asm65.Formatter formatter = mOutputFormatter; + Asm65.Formatter formatter = mFormatter; bool showBank = !mProject.CpuDef.HasAddr16; for (int i = 0; i < xrefs.Count; i++) { XrefSet.Xref xr = xrefs[i]; @@ -3565,7 +3565,7 @@ namespace SourceGen { } MainWindow.NotesListItem nli = new MainWindow.NotesListItem(offset, - mOutputFormatter.FormatOffset24(offset), + mFormatter.FormatOffset24(offset), nocrlfStr, mlc.BackgroundColor); mMainWin.NotesList.Add(nli); @@ -3583,13 +3583,13 @@ namespace SourceGen { private void PopulateSymbolsList() { mMainWin.SymbolsList.Clear(); foreach (Symbol sym in mProject.SymbolTable) { - string valueStr = mOutputFormatter.FormatHexValue(sym.Value, 0); + string valueStr = mFormatter.FormatHexValue(sym.Value, 0); string sourceTypeStr = sym.SourceTypeString; if (sym is DefSymbol) { DefSymbol defSym = (DefSymbol)sym; if (defSym.MultiMask != null) { valueStr += " & " + - mOutputFormatter.FormatHexValue(defSym.MultiMask.AddressMask, 4); + mFormatter.FormatHexValue(defSym.MultiMask.AddressMask, 4); } if (defSym.Direction == DefSymbol.DirectionFlags.Read) { sourceTypeStr += '<'; @@ -3599,7 +3599,7 @@ namespace SourceGen { } MainWindow.SymbolsListItem sli = new MainWindow.SymbolsListItem(sym, - sourceTypeStr, valueStr, sym.GenerateDisplayLabel(mOutputFormatter)); + sourceTypeStr, valueStr, sym.GenerateDisplayLabel(mFormatter)); mMainWin.SymbolsList.Add(sli); } } @@ -3970,7 +3970,7 @@ namespace SourceGen { public void ToggleInstructionChart() { if (mInstructionChartDialog == null) { // Create without owner so it doesn't have to be in front of main window. - mInstructionChartDialog = new Tools.WpfGui.InstructionChart(null, mOutputFormatter); + mInstructionChartDialog = new Tools.WpfGui.InstructionChart(null, mFormatter); mInstructionChartDialog.Closing += (sender, e) => { Debug.WriteLine("Instruction chart closed"); mInstructionChartDialog = null; @@ -4009,7 +4009,7 @@ namespace SourceGen { // Create the dialog without an owner, and add it to the "unowned" list. Tools.WpfGui.HexDumpViewer dlg = new Tools.WpfGui.HexDumpViewer(null, - data, mOutputFormatter); + data, mFormatter); dlg.SetFileName(Path.GetFileName(fileName)); dlg.Closing += (sender, e) => { Debug.WriteLine("Window " + dlg + " closed, removing from unowned list"); @@ -4025,6 +4025,21 @@ namespace SourceGen { concat.ShowDialog(); } + public void SliceFiles() { + OpenFileDialog fileDlg = new OpenFileDialog() { + Filter = Res.Strings.FILE_FILTER_ALL, + FilterIndex = 1 + }; + if (fileDlg.ShowDialog() != true) { + return; + } + string pathName = Path.GetFullPath(fileDlg.FileName); + + Tools.WpfGui.FileSlicer slicer = new Tools.WpfGui.FileSlicer(this.mMainWin, pathName, + mFormatter); + slicer.ShowDialog(); + } + #endregion Tools #region Debug features diff --git a/SourceGen/RuntimeData/Help/tools.html b/SourceGen/RuntimeData/Help/tools.html index bd9d58c..9b909fc 100644 --- a/SourceGen/RuntimeData/Help/tools.html +++ b/SourceGen/RuntimeData/Help/tools.html @@ -12,6 +12,23 @@

6502bench SourceGen: Tools

+

Instruction Chart

+

This opens a window with a summary of all 256 opcodes. The CPU can +be chosen from the pop-up list at the bottom. Undocumented opcodes are +shown in italics.

+

The status flags affected by each instruction reflect their behavior +on the 65816. The only significant difference between 65816 and +6502/65C02 is the way the BRK instruction affects the D and B/X flags.

+ + +

ASCII Chart

+ +

This opens a window with the ASCII character set. Each character is +displayed next to its numeric value in decimal and hexadecimal. The +pop-up list at the bottom allows you to flip between standard and "high" +ASCII.

+ +

Hex Dump Viewer

You can use this to view the contents of the project data file @@ -53,25 +70,19 @@ external files.

The File Concatenator combines multiple files into a single file. Select the files to add, arrange them in the proper order, then hit -"Save".

+"Save". CRC-32 values are shown for reference.

-

ASCII Chart

+

File Slicer

-

This opens a window with the ASCII character set. Each character is -displayed next to its numeric value in decimal and hexadecimal. The -pop-up list at the bottom allows you to flip between standard and "high" -ASCII.

+

The File Slicer allows you to "slice" a piece out of a file, saving +it to a new file. Specify the start and length in decimal or hex. If +you leave a field blank, they will default to offset 0 and the remaining +length of the file, respectively.

+

The hex dumps show the area just before and after the chunk to be +sliced, allowing you to confirm the placement.

-

Instruction Chart

-

This opens a window with a summary of all 256 opcodes. The CPU can -be chosen from the pop-up list at the bottom. Undocumented opcodes are -shown in italics.

-

The status flags affected by each instruction are shown for the 65816 -implementation. The only significant difference vs. 6502/65C02 -is in the way the BRK instruction affects the D and B/X flags.

-
public void SetFileName(string fileName) { - Title = fileName; + Title = fileName + (string)FindResource("str_TitleAddon"); } private void CharConvComboBox_SelectionChanged(object sender, diff --git a/SourceGen/WpfGui/EditComment.xaml.cs b/SourceGen/WpfGui/EditComment.xaml.cs index 8cda0a7..0e5e0b2 100644 --- a/SourceGen/WpfGui/EditComment.xaml.cs +++ b/SourceGen/WpfGui/EditComment.xaml.cs @@ -40,7 +40,7 @@ namespace SourceGen.WpfGui { } private string mCommentText; - private Brush mDefaultLabelColor; + private Brush mDefaultLabelColor = SystemColors.WindowTextBrush; // INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; @@ -54,7 +54,6 @@ namespace SourceGen.WpfGui { DataContext = this; CommentText = comment; - mDefaultLabelColor = asciiOnlyLabel.Foreground; } public void Window_ContentRendered(object sender, EventArgs e) { diff --git a/SourceGen/WpfGui/EditDefSymbol.xaml.cs b/SourceGen/WpfGui/EditDefSymbol.xaml.cs index 3982c20..d9384cc 100644 --- a/SourceGen/WpfGui/EditDefSymbol.xaml.cs +++ b/SourceGen/WpfGui/EditDefSymbol.xaml.cs @@ -149,8 +149,7 @@ namespace SourceGen.WpfGui { /// private bool mIsWidthOptional; - // Saved off at dialog load time. - private Brush mDefaultLabelColor; + private Brush mDefaultLabelColor = SystemColors.WindowTextBrush; // INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; @@ -205,8 +204,6 @@ namespace SourceGen.WpfGui { } private void Window_Loaded(object sender, RoutedEventArgs e) { - mDefaultLabelColor = labelNotesLabel.Foreground; - if (mOldSym != null) { Label = mOldSym.GenerateDisplayLabel(mNumFormatter); Value = mNumFormatter.FormatValueInBase(mOldSym.Value, diff --git a/SourceGen/WpfGui/EditLabel.xaml.cs b/SourceGen/WpfGui/EditLabel.xaml.cs index 8a3da9d..7ead04c 100644 --- a/SourceGen/WpfGui/EditLabel.xaml.cs +++ b/SourceGen/WpfGui/EditLabel.xaml.cs @@ -53,8 +53,7 @@ namespace SourceGen.WpfGui { /// private Formatter mFormatter; - // Dialog label text color, saved off at dialog load time. - private Brush mDefaultLabelColor; + private Brush mDefaultLabelColor = SystemColors.WindowTextBrush; /// /// Recursion guard. @@ -146,8 +145,6 @@ namespace SourceGen.WpfGui { } private void Window_Loaded(object sender, RoutedEventArgs e) { - mDefaultLabelColor = maxLengthLabel.Foreground; - IsNonUniqueEnabled = IsLocalEnabled = IsGlobalEnabled = IsExportedEnabled = true; if (LabelSym == null) { diff --git a/SourceGen/WpfGui/MainWindow.xaml b/SourceGen/WpfGui/MainWindow.xaml index d598aef..4e2d442 100644 --- a/SourceGen/WpfGui/MainWindow.xaml +++ b/SourceGen/WpfGui/MainWindow.xaml @@ -164,13 +164,14 @@ limitations under the License. + Ctrl+D - + Ctrl+B @@ -291,6 +292,8 @@ limitations under the License. Executed="ShowFileHexDumpCmd_Executed"/> + + + diff --git a/SourceGen/WpfGui/MainWindow.xaml.cs b/SourceGen/WpfGui/MainWindow.xaml.cs index ea02be8..834e046 100644 --- a/SourceGen/WpfGui/MainWindow.xaml.cs +++ b/SourceGen/WpfGui/MainWindow.xaml.cs @@ -1334,6 +1334,10 @@ namespace SourceGen.WpfGui { mMainCtrl.ShowHexDump(); } + private void SliceFilesCmd_Executed(object sender, ExecutedRoutedEventArgs e) { + mMainCtrl.SliceFiles(); + } + private void ToggleAsciiChartCmd_Executed(object sender, ExecutedRoutedEventArgs e) { mMainCtrl.ToggleAsciiChart(); }