From 96a92f0335ae4260b22783bbd42caaca6e646860 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Wed, 19 Jun 2019 16:31:56 -0700 Subject: [PATCH] Load/save app settings This change pulls in the settings file code, which is mostly unchanged except when it comes to saving and restoring the window location and size. The old system has been replaced with a PInvoke-based version that calls the underlying Win32 window placement code. This is more likely to be correct when multiple displays are in use, and can record the un-maximized size of a maximized window. It leaves a nasty XML string embedded in the config file, but it's not really meant to be human-readable anyway. The sub-window dividers all work completely differently from the way they did in WinForms, and some of the behavior is a bit obscure (like noticing when a splitter moves due to keyboard input, and setting the position in a way that doesn't break the auto-sizing). Yay WPF. Still need to preserve column widths. --- CommonWPF/CommonWPF.csproj | 1 + CommonWPF/WPFExtensions.cs | 4 +- CommonWPF/WindowPlacement.cs | 126 ++++++++++++ SourceGenWPF/AppSettings.cs | 10 +- SourceGenWPF/MainController.cs | 244 ++++++++++++++++++------ SourceGenWPF/ProjWin/MainWindow.xaml | 18 +- SourceGenWPF/ProjWin/MainWindow.xaml.cs | 121 ++++++++++-- SourceGenWPF/Res/Strings.xaml | 2 + SourceGenWPF/Res/Strings.xaml.cs | 4 + 9 files changed, 444 insertions(+), 86 deletions(-) create mode 100644 CommonWPF/WindowPlacement.cs diff --git a/CommonWPF/CommonWPF.csproj b/CommonWPF/CommonWPF.csproj index 9882231..d00b625 100644 --- a/CommonWPF/CommonWPF.csproj +++ b/CommonWPF/CommonWPF.csproj @@ -63,6 +63,7 @@ Settings.settings True + ResXFileCodeGenerator diff --git a/CommonWPF/WPFExtensions.cs b/CommonWPF/WPFExtensions.cs index 31db020..4f3a15a 100644 --- a/CommonWPF/WPFExtensions.cs +++ b/CommonWPF/WPFExtensions.cs @@ -36,8 +36,8 @@ namespace CommonWPF { /// From https://social.msdn.microsoft.com/Forums/vstudio/en-US/7d0626cb-67e8-4a09-a01e-8e56ee7411b2/gridviewcolumheader-radiobuttons?forum=wpf /// /// - /// - /// + /// Start point. + /// Object of appropriate type, or null if not found. public static T GetVisualChild(this Visual referenceVisual) where T : Visual { Visual child = null; for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++) { diff --git a/CommonWPF/WindowPlacement.cs b/CommonWPF/WindowPlacement.cs new file mode 100644 index 0000000..25ccc88 --- /dev/null +++ b/CommonWPF/WindowPlacement.cs @@ -0,0 +1,126 @@ +/* + * This comes from "David Rickard's Tech Blog", posted by RandomEngy on March 8 2010: + * https://blogs.msdn.microsoft.com/davidrickard/2010/03/08/saving-window-size-and-location-in-wpf-and-winforms/ + * + * Saving and restoring a window's size and position can be tricky when there are multiple + * displays involved. This uses the Win32 system functions to do the job properly and + * consistently. (In theory.) + * + * The code works for WinForms (save on FormClosing, restore on Load, using the native handle + * from the Handle property) and WPF (use the Window extension methods in Closing and + * SourceInitialized). Besides convenience, it has the added benefit of being able to + * capture the non-maximized values for a maximized window. + */ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows; +using System.Windows.Interop; +using System.Xml; +using System.Xml.Serialization; + +namespace CommonWPF { + // RECT structure required by WINDOWPLACEMENT structure + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct RECT { + public int Left; + public int Top; + public int Right; + public int Bottom; + + public RECT(int left, int top, int right, int bottom) { + this.Left = left; + this.Top = top; + this.Right = right; + this.Bottom = bottom; + } + } + + // POINT structure required by WINDOWPLACEMENT structure + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct POINT { + public int X; + public int Y; + + public POINT(int x, int y) { + this.X = x; + this.Y = y; + } + } + + // WINDOWPLACEMENT stores the position, size, and state of a window + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct WINDOWPLACEMENT { + public int length; + public int flags; + public int showCmd; + public POINT minPosition; + public POINT maxPosition; + public RECT normalPosition; + } + + public static class WindowPlacement { + private static Encoding encoding = new UTF8Encoding(); + private static XmlSerializer serializer = new XmlSerializer(typeof(WINDOWPLACEMENT)); + + [DllImport("user32.dll")] + private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl); + + [DllImport("user32.dll")] + private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl); + + private const int SW_SHOWNORMAL = 1; + private const int SW_SHOWMINIMIZED = 2; + + public static void SetPlacement(IntPtr windowHandle, string placementXml) { + if (string.IsNullOrEmpty(placementXml)) { + return; + } + + WINDOWPLACEMENT placement; + byte[] xmlBytes = encoding.GetBytes(placementXml); + + try { + using (MemoryStream memoryStream = new MemoryStream(xmlBytes)) { + placement = (WINDOWPLACEMENT)serializer.Deserialize(memoryStream); + } + + placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT)); + placement.flags = 0; + placement.showCmd = (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd); + SetWindowPlacement(windowHandle, ref placement); + } catch (InvalidOperationException) { + // Parsing placement XML failed. Fail silently. + } + } + + public static string GetPlacement(IntPtr windowHandle) { + WINDOWPLACEMENT placement = new WINDOWPLACEMENT(); + GetWindowPlacement(windowHandle, out placement); + + using (MemoryStream memoryStream = new MemoryStream()) { + using (XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8)) { + serializer.Serialize(xmlTextWriter, placement); + byte[] xmlBytes = memoryStream.ToArray(); + return encoding.GetString(xmlBytes); + } + } + } + + // + // Extension methods for WPF. + // + + public static string GetPlacement(this Window window) { + return GetPlacement(new WindowInteropHelper(window).Handle); + } + public static void SetPlacement(this Window window, string placementXml) { + SetPlacement(new WindowInteropHelper(window).Handle, placementXml); + } + } +} diff --git a/SourceGenWPF/AppSettings.cs b/SourceGenWPF/AppSettings.cs index d30aa68..087aedf 100644 --- a/SourceGenWPF/AppSettings.cs +++ b/SourceGenWPF/AppSettings.cs @@ -37,15 +37,11 @@ namespace SourceGenWPF { // makes it easy to find all uses. // Main window. - public const string MAIN_WINDOW_WIDTH = "main-window-width"; - public const string MAIN_WINDOW_HEIGHT = "main-window-height"; - public const string MAIN_WINDOW_LOC_X = "main-window-loc-x"; - public const string MAIN_WINDOW_LOC_Y = "main-window-loc-y"; - public const string MAIN_WINDOW_MAXIMIZED = "main-window-maximized"; + public const string MAIN_WINDOW_PLACEMENT = "main-window-placement"; public const string MAIN_LEFT_PANEL_WIDTH = "main-left-panel-width"; public const string MAIN_RIGHT_PANEL_WIDTH = "main-right-panel-width"; - public const string MAIN_LEFT_SIDE_SPLITTER_DIST = "main-left-side-splitter-dist"; - public const string MAIN_RIGHT_SIDE_SPLITTER_DIST = "main-right-side-splitter-dist"; + public const string MAIN_REFERENCES_HEIGHT = "main-references-height"; + public const string MAIN_SYMBOLS_HEIGHT = "main-symbols-height"; // New project dialog. public const string NEWP_SELECTED_SYSTEM = "newp-selected-system"; diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs index 9279367..1eda803 100644 --- a/SourceGenWPF/MainController.cs +++ b/SourceGenWPF/MainController.cs @@ -24,7 +24,9 @@ using System.Windows; using Asm65; using CommonUtil; +using CommonWPF; using SourceGenWPF.ProjWin; +using SourceGenWPF.Sandbox; namespace SourceGenWPF { /// @@ -35,6 +37,8 @@ namespace SourceGenWPF { /// There is some Windows-specific stuff, like MessageBox and OpenFileDialog. /// public class MainController { + private const string SETTINGS_FILE_NAME = "SourceGen-settings"; + #region Project state // Currently open project, or null if none. @@ -46,14 +50,6 @@ namespace SourceGenWPF { // Pathname of .dis65 file. This will be empty for a new project. private string mProjectPathName; -#if false - /// - /// Symbol subset, used to supply data to the symbol ListView. Initialized with - /// an empty symbol table. - /// - private SymbolTableSubset mSymbolSubset; -#endif - /// /// Data backing the code list. /// @@ -155,6 +151,18 @@ namespace SourceGenWPF { mMainWin = win; } + /// + /// Early initialization, before the window is visible. Notably, we want to get the + /// window placement data, so we can position and size the window before it's first + /// drawn (avoids a blink). + /// + public void WindowSourceInitialized() { + // Load the settings from the file. If this fails we have no way to tell the user, + // so just keep going. + LoadAppSettings(); + SetAppWindowLocation(); + } + /// /// Perform one-time initialization after the Window has finished loading. We defer /// to this point so we can report fatal errors directly to the user. @@ -167,7 +175,6 @@ namespace SourceGenWPF { Application.Current.Shutdown(); return; } -#if false try { PluginDllCache.PreparePluginDir(); } catch (Exception ex) { @@ -175,61 +182,20 @@ namespace SourceGenWPF { if (pluginPath == null) { pluginPath = ""; } - string msg = string.Format(Properties.Resources.PLUGIN_DIR_FAIL, + string msg = string.Format(Res.Strings.PLUGIN_DIR_FAIL_FMT, pluginPath + ": " + ex.Message); - MessageBox.Show(this, msg, Properties.Resources.PLUGIN_DIR_FAIL_CAPTION, - MessageBoxButtons.OK, MessageBoxIcon.Error); - Application.Exit(); + MessageBox.Show(msg, Res.Strings.PLUGIN_DIR_FAIL_CAPTION, + MessageBoxButton.OK, MessageBoxImage.Error); + Application.Current.Shutdown(); return; } -#endif - -#if false - logoPictureBox.ImageLocation = RuntimeDataAccess.GetPathName(LOGO_FILE_NAME); - versionLabel.Text = string.Format(Properties.Resources.VERSION_FMT, - Program.ProgramVersion); - - toolStripStatusLabel.Text = Properties.Resources.STATUS_READY; - - mProjectControl = this.codeListView; - mNoProjectControl = this.noProjectPanel; - - // Clone the menu structure from the designer. The same items are used for - // both Edit > Actions and the right-click context menu in codeListView. - mActionsMenuItems = new ToolStripItem[actionsToolStripMenuItem.DropDownItems.Count]; - for (int i = 0; i < actionsToolStripMenuItem.DropDownItems.Count; i++) { - mActionsMenuItems[i] = actionsToolStripMenuItem.DropDownItems[i]; - } -#endif - -#if false - // Load the settings from the file. Some things (like the symbol subset) need - // these. The general "apply settings" doesn't happen until a bit later, after - // the sub-windows have been initialized. - LoadAppSettings(); - - // Init primary ListView (virtual, ownerdraw) - InitCodeListView(); - - // Init Symbols ListView (virtual, non-ownerdraw) - mSymbolSubset = new SymbolTableSubset(new SymbolTable()); - symbolListView.SetDoubleBuffered(true); - InitSymbolListView(); - - // Init References ListView (non-virtual, non-ownerdraw) - referencesListView.SetDoubleBuffered(true); // Place the main window and apply the various settings. - SetAppWindowLocation(); -#endif ApplyAppSettings(); #if false - UpdateActionMenu(); UpdateMenuItemsAndTitle(); UpdateRecentLinks(); - - ShowNoProject(); #endif ProcessCommandLine(); @@ -242,6 +208,163 @@ namespace SourceGenWPF { } } + + /// + /// Loads settings from the settings file into AppSettings.Global. Does not apply + /// them to the ProjectView. + /// + private void LoadAppSettings() { + AppSettings settings = AppSettings.Global; + + // Set some default settings for first-time use. The general rule is to set + // a default value of false, 0, or the empty string, so we only need to set + // values here when that isn't the case. The point at which the setting is + // actually used is expected to do something reasonable by default. + + settings.SetBool(AppSettings.SYMWIN_SHOW_USER, true); + settings.SetBool(AppSettings.SYMWIN_SHOW_PROJECT, true); + settings.SetBool(AppSettings.SYMWIN_SHOW_PLATFORM, false); + settings.SetBool(AppSettings.SYMWIN_SHOW_AUTO, false); + settings.SetBool(AppSettings.SYMWIN_SHOW_CONST, true); + settings.SetBool(AppSettings.SYMWIN_SHOW_ADDR, true); + settings.SetBool(AppSettings.SYMWIN_SORT_ASCENDING, true); + settings.SetInt(AppSettings.SYMWIN_SORT_COL, (int)Symbol.SymbolSortField.Name); + + settings.SetBool(AppSettings.FMT_UPPER_OPERAND_A, true); + settings.SetBool(AppSettings.FMT_UPPER_OPERAND_S, true); + settings.SetBool(AppSettings.FMT_ADD_SPACE_FULL_COMMENT, true); + settings.SetString(AppSettings.FMT_OPCODE_SUFFIX_LONG, "l"); + settings.SetString(AppSettings.FMT_OPERAND_PREFIX_ABS, "a:"); + settings.SetString(AppSettings.FMT_OPERAND_PREFIX_LONG, "f:"); + + settings.SetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, true); + settings.SetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, true); + +#if DEBUG + settings.SetBool(AppSettings.DEBUG_MENU_ENABLED, true); +#else + settings.SetBool(AppSettings.DEBUG_MENU_ENABLED, false); +#endif + + // Load the settings file, and merge it into the globals. + string runtimeDataDir = RuntimeDataAccess.GetDirectory(); + if (runtimeDataDir == null) { + Debug.WriteLine("Unable to load settings file"); + return; + } + string settingsDir = Path.GetDirectoryName(runtimeDataDir); + string settingsPath = Path.Combine(settingsDir, SETTINGS_FILE_NAME); + try { + string text = File.ReadAllText(settingsPath); + AppSettings fileSettings = AppSettings.Deserialize(text); + AppSettings.Global.MergeSettings(fileSettings); + Debug.WriteLine("Settings file loaded and merged"); + } catch (Exception ex) { + Debug.WriteLine("Unable to read settings file: " + ex.Message); + } + } + + /// + /// Saves AppSettings to a file. + /// + private void SaveAppSettings() { + if (!AppSettings.Global.Dirty) { + Debug.WriteLine("Settings not dirty, not saving"); + return; + } + + // Main window position and size. + AppSettings.Global.SetString(AppSettings.MAIN_WINDOW_PLACEMENT, + mMainWin.GetPlacement()); + + // Horizontal splitters. + AppSettings.Global.SetInt(AppSettings.MAIN_LEFT_PANEL_WIDTH, + (int)mMainWin.LeftPanelWidth); + AppSettings.Global.SetInt(AppSettings.MAIN_RIGHT_PANEL_WIDTH, + (int)mMainWin.RightPanelWidth); + + // Vertical splitters. + AppSettings.Global.SetInt(AppSettings.MAIN_REFERENCES_HEIGHT, + (int)mMainWin.ReferencesPanelHeight); + AppSettings.Global.SetInt(AppSettings.MAIN_SYMBOLS_HEIGHT, + (int)mMainWin.SymbolsPanelHeight); + +#if false + SerializeCodeListColumnWidths(); + SerializeReferencesColumnWidths(); + SerializeNotesColumnWidths(); + SerializeSymbolColumnWidths(); +#endif + + string runtimeDataDir = RuntimeDataAccess.GetDirectory(); + if (runtimeDataDir == null) { + Debug.WriteLine("Unable to save settings file"); + return; + } + string settingsDir = Path.GetDirectoryName(runtimeDataDir); + string settingsPath = Path.Combine(settingsDir, SETTINGS_FILE_NAME); + try { + string cereal = AppSettings.Global.Serialize(); + File.WriteAllText(settingsPath, cereal); + AppSettings.Global.Dirty = false; + Debug.WriteLine("Saved settings (" + settingsPath + ")"); + } catch (Exception ex) { + Debug.WriteLine("Failed to save settings: " + ex.Message); + } + } + + /// + /// Replaces the contents of the global settings object with the new settings, + /// then applies them to the project. + /// + /// + public void SetAppSettings(AppSettings settings) { + AppSettings.Global.ReplaceSettings(settings); + ApplyAppSettings(); + + // We get called whenever Apply or OK is hit in the settings editor, so it's + // a pretty good time to save the settings out. + SaveAppSettings(); + } + + /// + /// Sets the app window's location and size. This should be called before the window has + /// finished initialization. + /// + private void SetAppWindowLocation() { + const int DEFAULT_SPLIT = 250; + + AppSettings settings = AppSettings.Global; + + string placement = settings.GetString(AppSettings.MAIN_WINDOW_PLACEMENT, null); + if (placement != null) { + mMainWin.SetPlacement(placement); + } + + mMainWin.LeftPanelWidth = + settings.GetInt(AppSettings.MAIN_LEFT_PANEL_WIDTH, DEFAULT_SPLIT); + mMainWin.RightPanelWidth = + settings.GetInt(AppSettings.MAIN_RIGHT_PANEL_WIDTH, DEFAULT_SPLIT); + mMainWin.ReferencesPanelHeight = + settings.GetInt(AppSettings.MAIN_REFERENCES_HEIGHT, 350); + mMainWin.SymbolsPanelHeight = + settings.GetInt(AppSettings.MAIN_SYMBOLS_HEIGHT, 400); + +#if false + // Configure column widths. + string widthStr = settings.GetString(AppSettings.CDLV_COL_WIDTHS, null); + if (!string.IsNullOrEmpty(widthStr)) { + CodeListColumnWidths widths = CodeListColumnWidths.Deserialize(widthStr); + if (widths != null) { + SetCodeListHeaderWidths(widths); + } + } + DeserializeReferencesColumnWidths(); + DeserializeNotesColumnWidths(); + DeserializeSymbolColumnWidths(); +#endif + } + /// /// Applies "actionable" settings to the ProjectView, pulling them out of the global /// settings object. If a project is open, refreshes the display list and all sub-windows. @@ -363,8 +486,6 @@ namespace SourceGenWPF { #endregion Init and settings - - #region Project management private bool PrepareNewProject(string dataPathName, SystemDef sysDef) { @@ -891,14 +1012,21 @@ namespace SourceGenWPF { // Update this, in case this was a new project. UpdateRecentProjectList(pathName); -#if false // Seems like a good time to save this off too. SaveAppSettings(); -#endif return true; } + /// + /// Handles main window closing. + /// + /// True if it's okay for the window to close, false to cancel it. + public bool WindowClosing() { + SaveAppSettings(); + return CloseProject(); + } + /// /// Closes the project and associated modeless dialogs. Unsaved changes will be /// lost, so if the project has outstanding changes the user will be given the diff --git a/SourceGenWPF/ProjWin/MainWindow.xaml b/SourceGenWPF/ProjWin/MainWindow.xaml index 389b8fd..dee8772 100644 --- a/SourceGenWPF/ProjWin/MainWindow.xaml +++ b/SourceGenWPF/ProjWin/MainWindow.xaml @@ -24,8 +24,12 @@ limitations under the License. Title="6502bench SourceGen" Icon="/SourceGenWPF;component/Res/SourceGenIcon.ico" Width="1000" Height="600" MinWidth="800" MinHeight="500" + SourceInitialized="Window_SourceInitialized" Loaded="Window_Loaded" - MouseDown="Window_MouseDown"> + LocationChanged="Window_LocationChanged" + SizeChanged="Window_SizeChanged" + MouseDown="Window_MouseDown" + Closing="Window_Closing"> @@ -245,12 +249,12 @@ limitations under the License. - - + + - + @@ -374,9 +378,9 @@ limitations under the License. - + - + @@ -430,7 +434,7 @@ limitations under the License. + HorizontalAlignment="Stretch" VerticalAlignment="Center"/> diff --git a/SourceGenWPF/ProjWin/MainWindow.xaml.cs b/SourceGenWPF/ProjWin/MainWindow.xaml.cs index 64acab2..044a03b 100644 --- a/SourceGenWPF/ProjWin/MainWindow.xaml.cs +++ b/SourceGenWPF/ProjWin/MainWindow.xaml.cs @@ -77,6 +77,20 @@ namespace SourceGenWPF.ProjWin { AddMultiKeyGestures(); + // Get an event when the splitters move. Because of the way things are set up, it's + // actually best to get an event when the grid row/column sizes change. + // https://stackoverflow.com/a/22495586/294248 + DependencyPropertyDescriptor widthDesc = DependencyPropertyDescriptor.FromProperty( + ColumnDefinition.WidthProperty, typeof(ItemsControl)); + DependencyPropertyDescriptor heightDesc = DependencyPropertyDescriptor.FromProperty( + RowDefinition.HeightProperty, typeof(ItemsControl)); + // main window, left/right panels + widthDesc.AddValueChanged(triptychGrid.ColumnDefinitions[0], GridSizeChanged); + widthDesc.AddValueChanged(triptychGrid.ColumnDefinitions[4], GridSizeChanged); + // references vs. notes + heightDesc.AddValueChanged(leftPanel.RowDefinitions[0], GridSizeChanged); + heightDesc.AddValueChanged(rightPanel.RowDefinitions[0], GridSizeChanged); + //GridView gv = (GridView)codeListView.View; //gv.Columns[0].Width = 50; } @@ -110,18 +124,6 @@ namespace SourceGenWPF.ProjWin { })); } - private void Window_Loaded(object sender, RoutedEventArgs e) { - mMainCtrl.WindowLoaded(); - CreateCodeListContextMenu(); - -#if DEBUG - // Get more info on CollectionChanged events that do not agree with current - // state of Items collection. - PresentationTraceSources.SetTraceLevel(codeListView.ItemContainerGenerator, - PresentationTraceLevel.High); -#endif - } - private void CreateCodeListContextMenu() { // Find Actions menu. ItemCollection mainItems = this.appMenu.Items; @@ -206,6 +208,46 @@ namespace SourceGenWPF.ProjWin { get { return mShowCodeListView ? Visibility.Visible : Visibility.Hidden; } } + + /// + /// Handles source-initialized event. This happens before Loaded, before the window + /// is visible, which makes it a good time to set the size and position. + /// + private void Window_SourceInitialized(object sender, EventArgs e) { + mMainCtrl.WindowSourceInitialized(); + } + + /// + /// Handles window-loaded event. Window is ready to go, so we can start doing things + /// that involve user interaction. + /// + private void Window_Loaded(object sender, RoutedEventArgs e) { + mMainCtrl.WindowLoaded(); + CreateCodeListContextMenu(); + +#if DEBUG + // Get more info on CollectionChanged events that do not agree with current + // state of Items collection. + PresentationTraceSources.SetTraceLevel(codeListView.ItemContainerGenerator, + PresentationTraceLevel.High); +#endif + } + + /// + /// Handles window-close event. The user has an opportunity to cancel. + /// + private void Window_Closing(object sender, CancelEventArgs e) { + Debug.WriteLine("Main app window closing"); + if (mMainCtrl == null) { + // early failure? + return; + } + if (!mMainCtrl.WindowClosing()) { + e.Cancel = true; + return; + } + } + /// /// Catch mouse-down events so we can treat the fourth mouse button as "back". /// @@ -215,6 +257,61 @@ namespace SourceGenWPF.ProjWin { } } + #region Window placement + + // + // We record the location and size of the window, and the sizes of the panels, in + // the settings file. All we need to do here is note that something has changed. + // + private void Window_LocationChanged(object sender, EventArgs e) { + Debug.WriteLine("Main window location changed"); + AppSettings.Global.Dirty = true; + } + private void Window_SizeChanged(object sender, SizeChangedEventArgs e) { + Debug.WriteLine("Main window size changed"); + AppSettings.Global.Dirty = true; + } + private void GridSizeChanged(object sender, EventArgs e) { + Debug.WriteLine("Splitter size change"); + AppSettings.Global.Dirty = true; + } + + public double LeftPanelWidth { + get { return triptychGrid.ColumnDefinitions[0].ActualWidth; } + set { triptychGrid.ColumnDefinitions[0].Width = new GridLength(value); } + } + public double RightPanelWidth { + get { return triptychGrid.ColumnDefinitions[4].ActualWidth; } + set { triptychGrid.ColumnDefinitions[4].Width = new GridLength(value); } + } + public double ReferencesPanelHeight { + get { return leftPanel.RowDefinitions[0].ActualHeight; } + set { + // If you set the height to a pixel value, you lose the auto-sizing behavior, + // and the splitter will happily shove the bottom panel off the bottom of the + // main window. The trick is to use "star" units. + // Thanks: https://stackoverflow.com/q/35000893/294248 + double totalHeight = leftPanel.RowDefinitions[0].ActualHeight + + leftPanel.RowDefinitions[2].ActualHeight; + leftPanel.RowDefinitions[0].Height = new GridLength(value, GridUnitType.Star); + leftPanel.RowDefinitions[2].Height = new GridLength(totalHeight - value, + GridUnitType.Star); + } + } + public double SymbolsPanelHeight { + get { return rightPanel.RowDefinitions[0].ActualHeight; } + set { + double totalHeight = rightPanel.RowDefinitions[0].ActualHeight + + rightPanel.RowDefinitions[2].ActualHeight; + rightPanel.RowDefinitions[0].Height = new GridLength(value, GridUnitType.Star); + rightPanel.RowDefinitions[2].Height = new GridLength(totalHeight - value, + GridUnitType.Star); + } + } + + #endregion Window placement + + /// /// Sets the focus on the code list. /// diff --git a/SourceGenWPF/Res/Strings.xaml b/SourceGenWPF/Res/Strings.xaml index 7ea4d38..d60c87e 100644 --- a/SourceGenWPF/Res/Strings.xaml +++ b/SourceGenWPF/Res/Strings.xaml @@ -61,6 +61,8 @@ limitations under the License. The file is {0:N0} bytes long, but the project expected {1:N0}. The file has CRC {0}, but the project expected {1}. Failed + Failed while preparing the plugin directory {0} + Failed Preparing Plugin Directory Executing assembler... Generating {0}... comment diff --git a/SourceGenWPF/Res/Strings.xaml.cs b/SourceGenWPF/Res/Strings.xaml.cs index 3379a1d..b607809 100644 --- a/SourceGenWPF/Res/Strings.xaml.cs +++ b/SourceGenWPF/Res/Strings.xaml.cs @@ -102,6 +102,10 @@ namespace SourceGenWPF.Res { (string)Application.Current.FindResource("str_OpenDataWrongLengthFmt"); public static string OPERATION_FAILED = (string)Application.Current.FindResource("str_OperationFailed"); + public static string PLUGIN_DIR_FAIL_FMT = + (string)Application.Current.FindResource("str_PluginDirFailFmt"); + public static string PLUGIN_DIR_FAIL_CAPTION = + (string)Application.Current.FindResource("str_PluginDirFailCaption"); public static string PROGRESS_ASSEMBLING = (string)Application.Current.FindResource("str_ProgressAssembling"); public static string PROGRESS_GENERATING_FMT =