diff --git a/SourceGenWPF/AppSettings.cs b/SourceGenWPF/AppSettings.cs index 8a03ef7..7a6fcda 100644 --- a/SourceGenWPF/AppSettings.cs +++ b/SourceGenWPF/AppSettings.cs @@ -94,7 +94,7 @@ namespace SourceGenWPF { public const string HEXD_CHAR_CONV = "hexd-char-conv1"; // ASCII chart viewer settings. - public const string ASCCH_MODE = "ascch-mode"; + public const string ASCCH_MODE = "ascch-mode1"; // Source generation settings. public const string SRCGEN_DEFAULT_ASM = "srcgen-default-asm"; diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs index 3a3e50e..dfb96c4 100644 --- a/SourceGenWPF/MainController.cs +++ b/SourceGenWPF/MainController.cs @@ -64,10 +64,27 @@ namespace SourceGenWPF { private MainWindow mMainWin; /// - /// Hex dump viewer window. + /// Hex dump viewer window. This is used for the currently open project. /// private Tools.WpfGui.HexDumpViewer mHexDumpDialog; + /// + /// This holds any un-owned Windows that we don't otherwise track. It's used for + /// hex dump windows of arbitrary files. We need to close them when the main window + /// is closed. + /// + private List mUnownedWindows = new List(); + + /// + /// ASCII chart reference window. Not tied to the project. + /// + private Tools.WpfGui.AsciiChart mAsciiChartDialog; + + /// + /// Returns true if the ASCII chart window is currently open. + /// + public bool IsAsciiChartOpen { get { return mAsciiChartDialog != null; } } + /// /// List of recently-opened projects. /// @@ -1108,7 +1125,30 @@ namespace SourceGenWPF { /// True if it's okay for the window to close, false to cancel it. public bool WindowClosing() { SaveAppSettings(); - return CloseProject(); + if (!CloseProject()) { + return false; + } + + // WPF won't exit until all windows are closed, so any unowned windows need + // to be cleaned up here. + if (mAsciiChartDialog != null) { + mAsciiChartDialog.Close(); + } + if (mHexDumpDialog != null) { + mHexDumpDialog.Close(); + } + while (mUnownedWindows.Count > 0) { + int count = mUnownedWindows.Count; + mUnownedWindows[0].Close(); + if (count == mUnownedWindows.Count) { + // Window failed to remove itself; this will cause an infinite loop. + // The user will have to close them manually. + Debug.Assert(false, "Failed to close window " + mUnownedWindows[0]); + break; + } + } + + return true; } /// @@ -2500,18 +2540,26 @@ namespace SourceGenWPF { return; } - // Fire and forget. - Tools.WpfGui.HexDumpViewer dlg = new Tools.WpfGui.HexDumpViewer(mMainWin, + // Create the dialog without an owner, and add it to the "unowned" list. + Tools.WpfGui.HexDumpViewer dlg = new Tools.WpfGui.HexDumpViewer(null, data, mOutputFormatter); dlg.SetFileName(Path.GetFileName(fileName)); + dlg.Closing += (sender, e) => { + Debug.WriteLine("Window " + dlg + " closed, removing from unowned list"); + mUnownedWindows.Remove(dlg); + }; + mUnownedWindows.Add(dlg); dlg.Show(); } public void ShowHexDump() { if (mHexDumpDialog == null) { // Create and show modeless dialog. This one is "always on top" by default, - // to allow the user to click around to various points. - mHexDumpDialog = new Tools.WpfGui.HexDumpViewer(mMainWin, + // to allow the user to click around to various points. Note that "on top" + // means on top of *everything*. We create this without an owner so that, + // 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); mHexDumpDialog.Closing += (sender, e) => { Debug.WriteLine("Hex dump dialog closed"); @@ -2633,6 +2681,20 @@ namespace SourceGenWPF { dlg.ShowDialog(); } + public void ToggleAsciiChart() { + if (mAsciiChartDialog == null) { + // Create without owner so it doesn't have to be in front of main window. + mAsciiChartDialog = new Tools.WpfGui.AsciiChart(null); + mAsciiChartDialog.Closing += (sender, e) => { + Debug.WriteLine("ASCII chart closed"); + mAsciiChartDialog = null; + }; + mAsciiChartDialog.Show(); + } else { + mAsciiChartDialog.Close(); + } + } + public void ToggleDataScan() { ProjectProperties oldProps = mProject.ProjectProps; ProjectProperties newProps = new ProjectProperties(oldProps); diff --git a/SourceGenWPF/SourceGenWPF.csproj b/SourceGenWPF/SourceGenWPF.csproj index 7844dac..0a2e050 100644 --- a/SourceGenWPF/SourceGenWPF.csproj +++ b/SourceGenWPF/SourceGenWPF.csproj @@ -79,6 +79,9 @@ GenTestRunner.xaml + + AsciiChart.xaml + AboutBox.xaml @@ -219,6 +222,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/SourceGenWPF/Tools/WpfGui/AsciiChart.xaml b/SourceGenWPF/Tools/WpfGui/AsciiChart.xaml new file mode 100644 index 0000000..f40e181 --- /dev/null +++ b/SourceGenWPF/Tools/WpfGui/AsciiChart.xaml @@ -0,0 +1,45 @@ + + + + + + Standard ASCII + High ASCII + + + + + + + + diff --git a/SourceGenWPF/Tools/WpfGui/AsciiChart.xaml.cs b/SourceGenWPF/Tools/WpfGui/AsciiChart.xaml.cs new file mode 100644 index 0000000..c122a72 --- /dev/null +++ b/SourceGenWPF/Tools/WpfGui/AsciiChart.xaml.cs @@ -0,0 +1,144 @@ +/* + * 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.Diagnostics; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace SourceGenWPF.Tools.WpfGui { + /// + /// ASCII chart. + /// + public partial class AsciiChart : Window { + public enum ChartMode { + Unknown = 0, + Standard, + High + }; + 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 AsciiChart(Window owner) { + InitializeComponent(); + Owner = owner; + DataContext = this; + + ChartModeItems = new ChartModeItem[] { + new ChartModeItem((string)FindResource("str_Standard"), ChartMode.Standard), + new ChartModeItem((string)FindResource("str_High"), ChartMode.High), + }; + } + + public void Window_Loaded(object sender, RoutedEventArgs e) { + // Restore chart mode setting. + ChartMode mode = (ChartMode)AppSettings.Global.GetEnum( + AppSettings.ASCCH_MODE, typeof(ChartMode), (int)ChartMode.Standard); + 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.ASCCH_MODE, typeof(ChartMode), (int)item.Mode); + + // + // Draw box contents. + // + const string hdr = "Dec Hex Chr"; + const string div = " | "; + const string eol = "\r\n"; + + StringBuilder sb = new StringBuilder( + (hdr.Length * 4 + div.Length * 3 + eol.Length) * 32); + 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 < 32; i++) { + DrawEntry(item.Mode, i, sb); + sb.Append(div); + DrawEntry(item.Mode, i + 32, sb); + sb.Append(div); + DrawEntry(item.Mode, i + 64, sb); + sb.Append(div); + DrawEntry(item.Mode, i + 96, sb); + sb.Append(eol); + } + + chartTextBox.Text = sb.ToString(); + chartTextBox.SelectionStart = sb.Length; + chartTextBox.SelectionLength = 0; + } + + private void DrawEntry(ChartMode mode, int val, StringBuilder sb) { + // Format is: Dec Hex Chr + int modVal = (mode == ChartMode.High) ? val | 0x80 : val; + sb.AppendFormat("{0,3:D} {1,3:X2} ", modVal, modVal); + if (val < 0x20) { + sb.Append('^'); + sb.Append((char)(val + 0x40)); + sb.Append(' '); + } else if (val == 0x20) { + sb.Append("' '"); + } else if (val < 0x7f) { + sb.Append(' '); + sb.Append((char)val); + sb.Append(' '); + } else { + sb.Append("DEL"); + } + } + } +} diff --git a/SourceGenWPF/Tools/WpfGui/HexDumpViewer.xaml b/SourceGenWPF/Tools/WpfGui/HexDumpViewer.xaml index a056347..d207f98 100644 --- a/SourceGenWPF/Tools/WpfGui/HexDumpViewer.xaml +++ b/SourceGenWPF/Tools/WpfGui/HexDumpViewer.xaml @@ -25,7 +25,7 @@ limitations under the License. Title="Hex Dump Viewer" Icon="/SourceGenWPF;component/Res/SourceGenIcon.ico" Width="542" Height="600" MinWidth="542" MinHeight="180" - ShowInTaskbar="False" WindowStartupLocation="CenterOwner" + ShowInTaskbar="True" Loaded="Window_Loaded"> diff --git a/SourceGenWPF/WpfGui/MainWindow.xaml b/SourceGenWPF/WpfGui/MainWindow.xaml index 270744c..a9ef6c3 100644 --- a/SourceGenWPF/WpfGui/MainWindow.xaml +++ b/SourceGenWPF/WpfGui/MainWindow.xaml @@ -140,6 +140,7 @@ limitations under the License. + Ctrl+D @@ -241,6 +242,8 @@ limitations under the License. Executed="ShowFileHexDumpCmd_Executed"/> + - + - + diff --git a/SourceGenWPF/WpfGui/MainWindow.xaml.cs b/SourceGenWPF/WpfGui/MainWindow.xaml.cs index e7b1aad..cb39ec0 100644 --- a/SourceGenWPF/WpfGui/MainWindow.xaml.cs +++ b/SourceGenWPF/WpfGui/MainWindow.xaml.cs @@ -1083,6 +1083,10 @@ namespace SourceGenWPF.WpfGui { mMainCtrl.ShowHexDump(); } + private void ToggleAsciiChartCmd_Executed(object sender, ExecutedRoutedEventArgs e) { + mMainCtrl.ToggleAsciiChart(); + } + private void ToggleDataScanCmd_Executed(object sender, ExecutedRoutedEventArgs e) { mMainCtrl.ToggleDataScan(); } @@ -1175,6 +1179,9 @@ namespace SourceGenWPF.WpfGui { // it rather than try to push changes around. toggleDataScanMenuItem.IsChecked = mMainCtrl.IsAnalyzeUncategorizedDataEnabled; } + private void ToolsMenu_SubmenuOpened(object sender, RoutedEventArgs e) { + toggleAsciiChartMenuItem.IsChecked = mMainCtrl.IsAsciiChartOpen; + } #endregion Misc