diff --git a/SourceGen/AppSettings.cs b/SourceGen/AppSettings.cs index 9d17e8e..83a7adc 100644 --- a/SourceGen/AppSettings.cs +++ b/SourceGen/AppSettings.cs @@ -109,6 +109,9 @@ namespace SourceGen { public const string HEXD_ASCII_ONLY = "hexd-ascii-only"; public const string HEXD_CHAR_CONV = "hexd-char-conv1"; + // Apple II screen chart viewer settings. + public const string A2SC_MODE = "a2sc-mode"; + // ASCII chart viewer settings. public const string ASCCH_MODE = "ascch-mode1"; diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 1efe195..1c9fdb6 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -95,6 +95,16 @@ namespace SourceGen { /// public bool IsAsciiChartOpen { get { return mAsciiChartDialog != null; } } + /// + /// Apple II screen chart window. Not tied to the project. + /// + private Tools.WpfGui.Apple2ScreenChart mApple2ScreenChartDialog; + + /// + /// Returns true if the ASCII chart window is currently open. + /// + public bool IsApple2ScreenChartOpen { get { return mApple2ScreenChartDialog != null; } } + /// /// Instruction chart reference window. Not tied to the project. /// @@ -1315,6 +1325,7 @@ namespace SourceGen { // WPF won't exit until all windows are closed, so any unowned windows need // to be cleaned up here. + mApple2ScreenChartDialog?.Close(); mAsciiChartDialog?.Close(); mInstructionChartDialog?.Close(); mHexDumpDialog?.Close(); @@ -4161,6 +4172,20 @@ namespace SourceGen { #region Tools + public void ToggleApple2ScreenChart() { + if (mApple2ScreenChartDialog == null) { + // Create without owner so it doesn't have to be in front of main window. + mApple2ScreenChartDialog = new Tools.WpfGui.Apple2ScreenChart(null, mFormatter); + mApple2ScreenChartDialog.Closing += (sender, e) => { + Debug.WriteLine("Apple II screen chart closed"); + mApple2ScreenChartDialog = null; + }; + mApple2ScreenChartDialog.Show(); + } else { + mApple2ScreenChartDialog.Close(); + } + } + public void ToggleAsciiChart() { if (mAsciiChartDialog == null) { // Create without owner so it doesn't have to be in front of main window. diff --git a/SourceGen/RuntimeData/Help/tools.html b/SourceGen/RuntimeData/Help/tools.html index fc1d4a0..9bd0cc7 100644 --- a/SourceGen/RuntimeData/Help/tools.html +++ b/SourceGen/RuntimeData/Help/tools.html @@ -31,6 +31,14 @@ pop-up list at the bottom allows you to flip between standard and "high" ASCII.

+

Apple II Screen Chart

+ +

The Apple II text and hi-res screens are mapped to memory in a way +that makes sense to computers but is a little confusing for humans. This +chart maps line numbers to addresses and vice-versa. Select different +screens and sort orders from the list at the bottom.

+ +

Hex Dump Viewer

You can use this to view the contents of the project data file diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj index 060c42a..10094c7 100644 --- a/SourceGen/SourceGen.csproj +++ b/SourceGen/SourceGen.csproj @@ -98,6 +98,9 @@ OmfViewer.xaml + + Apple2ScreenChart.xaml + FileConcatenator.xaml @@ -299,6 +302,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/SourceGen/Tools/WpfGui/Apple2ScreenChart.xaml b/SourceGen/Tools/WpfGui/Apple2ScreenChart.xaml new file mode 100644 index 0000000..a8ae206 --- /dev/null +++ b/SourceGen/Tools/WpfGui/Apple2ScreenChart.xaml @@ -0,0 +1,48 @@ + + + + + + Hi-Res Graphics, page 1, by line + Hi-Res Graphics, page 2, by line + Hi-Res Graphics, page 1, by address + Hi-Res Graphics, page 2, by address + Text Pages 1 & 2 + + + + + + + + diff --git a/SourceGen/Tools/WpfGui/Apple2ScreenChart.xaml.cs b/SourceGen/Tools/WpfGui/Apple2ScreenChart.xaml.cs new file mode 100644 index 0000000..35ca9dc --- /dev/null +++ b/SourceGen/Tools/WpfGui/Apple2ScreenChart.xaml.cs @@ -0,0 +1,281 @@ +/* + * Copyright 2020 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.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +using Asm65; + +namespace SourceGen.Tools.WpfGui { + ///

+ /// Apple II text/graphics memory map chart. + /// + public partial class Apple2ScreenChart : Window { + private const int NUM_HI_RES_ROWS = 192; + private const int NUM_TEXT_ROWS = 24; + private Formatter mFormatter; + + private static int[] sHiResRowsByAddr = GenerateHiResRowsByAddr(); + private static int[] sTextRowsByAddr = GenerateTextRowsByAddr(); + + public enum ChartMode { + Unknown = 0, + HiRes1_L, + HiRes2_L, + HiRes1_A, + HiRes2_A, + Text + }; + public class ChartModeItem { + public string Name { get; private set; } + public ChartMode Mode { get; private set; } + + public ChartModeItem(string name, ChartMode mode) { + Name = name; + Mode = mode; + } + } + public ChartModeItem[] ChartModeItems { get; private set; } + + + public Apple2ScreenChart(Window owner, Formatter formatter) { + InitializeComponent(); + Owner = owner; + DataContext = this; + + mFormatter = formatter; + + ChartModeItems = new ChartModeItem[] { + new ChartModeItem((string)FindResource("str_HiRes1_L"), ChartMode.HiRes1_L), + new ChartModeItem((string)FindResource("str_HiRes2_L"), ChartMode.HiRes2_L), + new ChartModeItem((string)FindResource("str_HiRes1_A"), ChartMode.HiRes1_A), + new ChartModeItem((string)FindResource("str_HiRes2_A"), ChartMode.HiRes2_A), + new ChartModeItem((string)FindResource("str_Text"), ChartMode.Text), + }; + } + + public void Window_Loaded(object sender, RoutedEventArgs e) { + // Restore chart mode setting. + ChartMode mode = (ChartMode)AppSettings.Global.GetEnum( + AppSettings.A2SC_MODE, typeof(ChartMode), (int)ChartMode.HiRes1_L); + int index = 0; + for (int i = 0; i < ChartModeItems.Length; i++) { + if (ChartModeItems[i].Mode == mode) { + index = i; + break; + } + } + chartModeComboBox.SelectedIndex = index; + // should call UpdateControls via SelectionChanged + } + + // Catch ESC key. + private void Window_KeyEventHandler(object sender, KeyEventArgs e) { + if (e.Key == Key.Escape) { + Close(); + } + } + + private void ChartModeComboBox_SelectionChanged(object sender, + SelectionChangedEventArgs e) { + UpdateControls(); + } + + private void UpdateControls() { + ChartModeItem item = (ChartModeItem)chartModeComboBox.SelectedItem; + if (item == null) { + // initializing + return; + } + + AppSettings.Global.SetEnum(AppSettings.A2SC_MODE, typeof(ChartMode), (int)item.Mode); + + string text; + switch (item.Mode) { + case ChartMode.HiRes1_L: + text = DrawHiRes(0x2000, true); + break; + case ChartMode.HiRes2_L: + text = DrawHiRes(0x4000, true); + break; + case ChartMode.HiRes1_A: + text = DrawHiRes(0x2000, false); + break; + case ChartMode.HiRes2_A: + text = DrawHiRes(0x4000, false); + break; + case ChartMode.Text: + text = DrawText(); + break; + default: + text = "UNKNOWN MODE"; + break; + } + + chartTextBox.Text = text; + chartTextBox.SelectionStart = text.Length; + chartTextBox.SelectionLength = 0; + } + + /// + /// Draws chart for hi-res graphics. + /// + /// Base address ($2000/$4000). + /// True if we want to sort by line number, false for address. + /// String with entire chart. + private string DrawHiRes(int baseAddr, bool byLine) { + const string eol = " \r\n"; // add space for balance + const string div = " | "; + string hdr; + if (byLine) { + hdr = "Line Addr "; + } else { + hdr = "Addr Line"; + } + + StringBuilder sb = new StringBuilder( + (hdr.Length * 4 + div.Length * 3 + eol.Length) * (NUM_HI_RES_ROWS / 4)); + + sb.Append(hdr); + sb.Append(div); + sb.Append(hdr); + sb.Append(div); + sb.Append(hdr); + sb.Append(div); + sb.Append(hdr); + sb.Append(eol); + for (int i = 0; i < NUM_HI_RES_ROWS / 4; i++) { + DrawHiResEntry(baseAddr, byLine, i, sb); + sb.Append(div); + DrawHiResEntry(baseAddr, byLine, i + NUM_HI_RES_ROWS / 4, sb); + sb.Append(div); + DrawHiResEntry(baseAddr, byLine, i + (NUM_HI_RES_ROWS * 2) / 4, sb); + sb.Append(div); + DrawHiResEntry(baseAddr, byLine, i + (NUM_HI_RES_ROWS * 3) / 4, sb); + sb.Append(eol); + } + return sb.ToString(); + } + + private void DrawHiResEntry(int baseAddr, bool byLine, int index, StringBuilder sb) { + if (byLine) { + sb.AppendFormat("{0,3:D} {1}", index, + mFormatter.FormatHexValue(HiResRowToAddr(baseAddr, index), 4)); + } else { + int row = sHiResRowsByAddr[index]; + sb.AppendFormat("{1} {0,3:D}", row, + mFormatter.FormatHexValue(HiResRowToAddr(baseAddr, row), 4)); + } + } + + /// + /// Generates the address of a line on the hi-res screen. + /// + /// Base address ($2000 or $4000). + /// Row number, 0-191. + /// Address of start of line. + private static int HiResRowToAddr(int baseAddr, int row) { + // If row is ABCDEFGH, we want pppFGHCD EABAB000 (where p would be $20/$40). + int low = ((row & 0xc0) >> 1) | ((row & 0xc0) >> 3) | ((row & 0x08) << 4); + int high = ((row & 0x07) << 2) | ((row & 0x30) >> 4); + int rowAddr = baseAddr + ((high << 8) | low); + return rowAddr; + } + + /// + /// Generates a sorted list of hi-res row numbers. The ordering is determined by the + /// address in memory of the row. + /// + /// List of rows, in memory order. + private static int[] GenerateHiResRowsByAddr() { + SortedList addrList = new SortedList(NUM_HI_RES_ROWS); + for (int i = 0; i < NUM_HI_RES_ROWS; i++) { + addrList.Add(HiResRowToAddr(0, i), i); + } + + return addrList.Values.ToArray(); + } + + /// + /// Draws chart for the text screen. There are few enough rows that we can do + /// by-line and by-address for both pages in a reasonable amount of space. + /// + /// String with entire chart. + private string DrawText() { + const string eol = " \r\n"; // add space for balance + const string div = " | "; + const string hdr1 = "Line Page1 Page2"; + const string hdr2 = "Page1 Page2 Line"; + + StringBuilder sb = new StringBuilder( + (hdr1.Length * 2 + div.Length * 1 + eol.Length) * NUM_TEXT_ROWS); + + sb.Append(hdr1); + sb.Append(div); + sb.Append(hdr2); + sb.Append(eol); + for (int i = 0; i < NUM_TEXT_ROWS; i++) { + const int base1 = 0x400; + const int base2 = 0x800; + + int textRow = sTextRowsByAddr[i]; + + sb.AppendFormat(" {0,2:D} {1} {2}", i, + mFormatter.FormatHexValue(TextRowToAddr(base1, i), 4), + mFormatter.FormatHexValue(TextRowToAddr(base2, i), 4)); + sb.Append(div); + sb.AppendFormat("{1} {2} {0,2:D}", textRow, + mFormatter.FormatHexValue(TextRowToAddr(base1, textRow), 4), + mFormatter.FormatHexValue(TextRowToAddr(base2, textRow), 4)); + sb.Append(eol); + } + return sb.ToString(); + } + + /// + /// Generates the address of a line on the text screen. + /// + /// Base address (0x0400 or 0x0800). + /// Row number (0-23). + /// Address of start of line. + private static int TextRowToAddr(int baseAddr, int row) { + // If row is 000ABCDE, we want 0000ppCD EABAB000 (where p is $04/$08). + int high = (row & 0x06) >> 1; + int low = (row & 0x18) | ((row & 0x18) << 2) | ((row & 0x01) << 7); + int rowAddr = baseAddr + ((high << 8) | low); + return rowAddr; + } + + /// + /// Generates a sorted list of text row numbers. The ordering is determined by the + /// address in memory of the row. + /// + /// List of rows, in memory order. + private static int[] GenerateTextRowsByAddr() { + SortedList addrList = new SortedList(NUM_TEXT_ROWS); + for (int i = 0; i < NUM_TEXT_ROWS; i++) { + addrList.Add(TextRowToAddr(0, i), i); + } + + return addrList.Values.ToArray(); + } + } +} diff --git a/SourceGen/WpfGui/MainWindow.xaml b/SourceGen/WpfGui/MainWindow.xaml index 9a1a9c7..075f85b 100644 --- a/SourceGen/WpfGui/MainWindow.xaml +++ b/SourceGen/WpfGui/MainWindow.xaml @@ -185,6 +185,7 @@ limitations under the License. + @@ -326,6 +327,8 @@ limitations under the License. CanExecute="IsProjectOpen" Executed="ShowHexDumpCmd_Executed"/> + + diff --git a/SourceGen/WpfGui/MainWindow.xaml.cs b/SourceGen/WpfGui/MainWindow.xaml.cs index 35c57b4..f62ad67 100644 --- a/SourceGen/WpfGui/MainWindow.xaml.cs +++ b/SourceGen/WpfGui/MainWindow.xaml.cs @@ -1369,6 +1369,10 @@ namespace SourceGen.WpfGui { mMainCtrl.SliceFiles(); } + private void ToggleApple2ScreenChartCmd_Executed(object sender, ExecutedRoutedEventArgs e) { + mMainCtrl.ToggleApple2ScreenChart(); + } + private void ToggleAsciiChartCmd_Executed(object sender, ExecutedRoutedEventArgs e) { mMainCtrl.ToggleAsciiChart(); } @@ -1571,6 +1575,7 @@ namespace SourceGen.WpfGui { toggleDataScanMenuItem.IsChecked = mMainCtrl.IsAnalyzeUncategorizedDataEnabled; } private void ToolsMenu_SubmenuOpened(object sender, RoutedEventArgs e) { + toggleApple2ScreenChartMenuItem.IsChecked = mMainCtrl.IsApple2ScreenChartOpen; toggleAsciiChartMenuItem.IsChecked = mMainCtrl.IsAsciiChartOpen; toggleInstructionChartMenuItem.IsChecked = mMainCtrl.IsInstructionChartOpen; }