diff --git a/ConsoleUI/DependencyScreen.cs b/ConsoleUI/DependencyScreen.cs index b252230da5..254e45b435 100644 --- a/ConsoleUI/DependencyScreen.cs +++ b/ConsoleUI/DependencyScreen.cs @@ -16,8 +16,10 @@ public class DependencyScreen : ConsoleScreen { /// KSP manager containing instances /// Plan of mods to add and remove /// Mods that the user saw and did not select, in this pass or a previous pass - public DependencyScreen(KSPManager mgr, ChangePlan cp, HashSet rej) : base() + /// True if debug options should be available, false otherwise + public DependencyScreen(KSPManager mgr, ChangePlan cp, HashSet rej, bool dbg) : base() { + debug = dbg; manager = mgr; plan = cp; registry = RegistryManager.Instance(manager.CurrentInstance).registry; @@ -79,7 +81,8 @@ public DependencyScreen(KSPManager mgr, ChangePlan cp, HashSet rej) : ba if (dependencyList.Selection != null) { LaunchSubScreen(new ModInfoScreen( manager, plan, - registry.LatestAvailable(dependencyList.Selection.identifier, manager.CurrentInstance.VersionCriteria()) + registry.LatestAvailable(dependencyList.Selection.identifier, manager.CurrentInstance.VersionCriteria()), + debug )); } return true; @@ -207,6 +210,7 @@ private string StatusSymbol(string identifier) private IRegistryQuerier registry; private KSPManager manager; private ChangePlan plan; + private bool debug; private Dictionary dependencies = new Dictionary(); private ConsoleListBox dependencyList; diff --git a/ConsoleUI/InstallScreen.cs b/ConsoleUI/InstallScreen.cs index ea87efb2c0..042af1b347 100644 --- a/ConsoleUI/InstallScreen.cs +++ b/ConsoleUI/InstallScreen.cs @@ -15,12 +15,14 @@ public class InstallScreen : ProgressScreen { /// /// KSP manager containing instances /// Plan of mods to install or remove - public InstallScreen(KSPManager mgr, ChangePlan cp) + /// True if debug options should be available, false otherwise + public InstallScreen(KSPManager mgr, ChangePlan cp, bool dbg) : base( "Installing, Updating, and Removing Mods", "Calculating..." ) { + debug = dbg; manager = mgr; plan = cp; } @@ -45,7 +47,7 @@ public override void Run(Action process = null) // CmdLine assumes recs and ignores sugs if (plan.Install.Count > 0) { // Track previously rejected optional dependencies and don't prompt for them again. - DependencyScreen ds = new DependencyScreen(manager, plan, rejected); + DependencyScreen ds = new DependencyScreen(manager, plan, rejected, debug); if (ds.HaveOptions()) { LaunchSubScreen(ds); } @@ -142,6 +144,7 @@ private void OnModInstalled(CkanModule mod) private KSPManager manager; private ChangePlan plan; + private bool debug; } } diff --git a/ConsoleUI/ModInfoScreen.cs b/ConsoleUI/ModInfoScreen.cs index 4cbb889a48..964619b22a 100644 --- a/ConsoleUI/ModInfoScreen.cs +++ b/ConsoleUI/ModInfoScreen.cs @@ -18,8 +18,10 @@ public class ModInfoScreen : ConsoleScreen { /// KSP manager containing game instances /// Plan of other mods to be added or removed /// The module to display - public ModInfoScreen(KSPManager mgr, ChangePlan cp, CkanModule m) + /// True if debug options should be available, false otherwise + public ModInfoScreen(KSPManager mgr, ChangePlan cp, CkanModule m, bool dbg) { + debug = dbg; mod = m; manager = mgr; plan = cp; @@ -145,6 +147,14 @@ public ModInfoScreen(KSPManager mgr, ChangePlan cp, CkanModule m) () => LaunchURL(mod.resources.curse) )); } + if (debug) { + opts.Add(null); + opts.Add(new ConsoleMenuOption( + "DEBUG: View metadata", "", "Display the full registry data for this mod", + true, + ViewMetadata + )); + } if (opts.Count > 0) { mainMenu = new ConsolePopupMenu(opts); @@ -155,6 +165,19 @@ public ModInfoScreen(KSPManager mgr, ChangePlan cp, CkanModule m) CenterHeader = () => "Mod Details"; } + private bool ViewMetadata() + { + ConsoleMessageDialog md = new ConsoleMessageDialog( + $"\"{mod.identifier}\": {registry.GetAvailableMetadata(mod.identifier)}", + new List {"OK"}, + () => $"{mod.name} Metadata", + TextAlign.Left + ); + md.Run(); + DrawBackground(); + return true; + } + private bool LaunchURL(Uri u) { // I'm getting error output on Linux, because this runs xdg-open which @@ -491,6 +514,7 @@ private void Download() private IRegistryQuerier registry; private ChangePlan plan; private CkanModule mod; + private bool debug; } } diff --git a/ConsoleUI/ModListScreen.cs b/ConsoleUI/ModListScreen.cs index 5e2d1cebb8..dab595216c 100644 --- a/ConsoleUI/ModListScreen.cs +++ b/ConsoleUI/ModListScreen.cs @@ -156,7 +156,7 @@ public ModListScreen(KSPManager mgr, bool dbg) ); moduleList.AddBinding(Keys.Enter, (object sender) => { if (moduleList.Selection != null) { - LaunchSubScreen(new ModInfoScreen(manager, plan, moduleList.Selection)); + LaunchSubScreen(new ModInfoScreen(manager, plan, moduleList.Selection, debug)); } return true; }); @@ -329,7 +329,7 @@ private bool ViewSuggestions() } } try { - DependencyScreen ds = new DependencyScreen(manager, reinstall, new HashSet()); + DependencyScreen ds = new DependencyScreen(manager, reinstall, new HashSet(), debug); if (ds.HaveOptions()) { LaunchSubScreen(ds); bool needRefresh = false; @@ -484,7 +484,7 @@ private bool Help() private bool ApplyChanges() { - LaunchSubScreen(new InstallScreen(manager, plan)); + LaunchSubScreen(new InstallScreen(manager, plan, debug)); RefreshList(); return true; } diff --git a/ConsoleUI/ProgressScreen.cs b/ConsoleUI/ProgressScreen.cs index b88dd4abd7..2fa3e09572 100644 --- a/ConsoleUI/ProgressScreen.cs +++ b/ConsoleUI/ProgressScreen.cs @@ -59,6 +59,8 @@ public override bool RaiseYesNoDialog(string question) // The installer's questions include embedded newlines for spacing in CmdLine question.Trim(), new List() {"Yes", "No"}, + null, + TextAlign.Center, -Console.WindowHeight / 2 ); d.AddBinding(Keys.Y, (object sender) => { @@ -71,27 +73,37 @@ public override bool RaiseYesNoDialog(string question) }); // Scroll messages - d.AddTip("Home / End / Page Up / Page Down", "Scroll messages"); - d.AddBinding(Keys.Home, (object sender) => { + d.AddTip("Cursor keys", "Scroll messages"); + d.AddBinding(Keys.Home, (object sender) => { messages.ScrollToTop(); messages.Draw(false); return true; }); - d.AddBinding(Keys.End, (object sender) => { + d.AddBinding(Keys.End, (object sender) => { messages.ScrollToBottom(); messages.Draw(false); return true; }); - d.AddBinding(Keys.PageUp, (object sender) => { + d.AddBinding(Keys.PageUp, (object sender) => { messages.ScrollUp(); messages.Draw(false); return true; }); - d.AddBinding(Keys.PageDown, (object sender) => { + d.AddBinding(Keys.PageDown, (object sender) => { messages.ScrollDown(); messages.Draw(false); return true; }); + d.AddBinding(Keys.UpArrow, (object sender) => { + messages.ScrollUp(1); + messages.Draw(false); + return true; + }); + d.AddBinding(Keys.DownArrow, (object sender) => { + messages.ScrollDown(1); + messages.Draw(false); + return true; + }); bool val = d.Run() == 0; DrawBackground(); diff --git a/ConsoleUI/Toolkit/ConsoleMessageDialog.cs b/ConsoleUI/Toolkit/ConsoleMessageDialog.cs index 9d93ed0ea1..751474a609 100644 --- a/ConsoleUI/Toolkit/ConsoleMessageDialog.cs +++ b/ConsoleUI/Toolkit/ConsoleMessageDialog.cs @@ -13,13 +13,20 @@ public class ConsoleMessageDialog : ConsoleDialog { /// /// Message to show /// List of captions for buttons + /// Function to generate the header + /// Alignment of the contents /// Pass non-zero to move popup vertically - public ConsoleMessageDialog(string m, List btns, int vertOffset = 0) + public ConsoleMessageDialog(string m, List btns, Func hdr = null, TextAlign ta = TextAlign.Center, int vertOffset = 0) : base() { - int l = GetLeft(), - r = GetRight(); - int w = Console.WindowWidth / 2; + int maxLen = Formatting.MaxLineLength(m); + int w = Math.Min(maxLen + 6, Console.WindowWidth - 4); + int l = (Console.WindowWidth - w) / 2; + int r = -l; + if (hdr != null) { + CenterHeader = hdr; + } + int btnW = btns.Count * buttonWidth + (btns.Count - 1) * buttonPadding; if (w < btnW + 4) { // Widen the window to fit the buttons @@ -32,6 +39,9 @@ public ConsoleMessageDialog(string m, List btns, int vertOffset = 0) List messageLines = Formatting.WordWrap(m, w - 4); int h = 2 + messageLines.Count + (btns.Count > 0 ? 2 : 0) + 2; + if (h > Console.WindowHeight - 4) { + h = Console.WindowHeight - 4; + } // Calculate vertical position including offset int t, b; @@ -55,13 +65,43 @@ public ConsoleMessageDialog(string m, List btns, int vertOffset = 0) ConsoleTextBox tb = new ConsoleTextBox( GetLeft() + 2, GetTop() + 2, GetRight() - 2, GetBottom() - 2 - (btns.Count > 0 ? 2 : 0), false, - TextAlign.Center, + ta, () => ConsoleTheme.Current.PopupBg, () => ConsoleTheme.Current.PopupFg ); AddObject(tb); tb.AddLine(m); + int boxH = GetBottom() - 2 - (btns.Count > 0 ? 2 : 0) - (GetTop() + 2) + 1; + + if (messageLines.Count > boxH) { + // Scroll + AddBinding(Keys.Home, (object sender) => { + tb.ScrollToTop(); + return true; + }); + AddBinding(Keys.End, (object sender) => { + tb.ScrollToBottom(); + return true; + }); + AddBinding(Keys.PageUp, (object sender) => { + tb.ScrollUp(); + return true; + }); + AddBinding(Keys.PageDown, (object sender) => { + tb.ScrollDown(); + return true; + }); + AddBinding(Keys.UpArrow, (object sender) => { + tb.ScrollUp(1); + return true; + }); + AddBinding(Keys.DownArrow, (object sender) => { + tb.ScrollDown(1); + return true; + }); + } + int btnLeft = (Console.WindowWidth - btnW) / 2; for (int i = 0; i < btns.Count; ++i) { string cap = btns[i]; diff --git a/ConsoleUI/Toolkit/ConsoleTextBox.cs b/ConsoleUI/Toolkit/ConsoleTextBox.cs index fbc2cef23d..10b86dd581 100644 --- a/ConsoleUI/Toolkit/ConsoleTextBox.cs +++ b/ConsoleUI/Toolkit/ConsoleTextBox.cs @@ -67,10 +67,9 @@ public void ScrollToBottom() /// /// Scroll the text box up one page /// - public void ScrollUp() + public void ScrollUp(int? howFar = null) { - int h = GetBottom() - GetTop() + 1; - topLine -= h; + topLine -= howFar ?? (GetBottom() - GetTop() + 1); if (topLine < 0) { topLine = 0; } @@ -79,11 +78,12 @@ public void ScrollUp() /// /// Scroll the text box down one page /// - public void ScrollDown() + public void ScrollDown(int? howFar = null) { - int h = GetBottom() - GetTop() + 1; - if (topLine + h <= lines.Count - h) { - topLine += h; + int h = GetBottom() - GetTop() + 1; + int diff = howFar ?? h; + if (topLine + diff <= lines.Count - h) { + topLine += diff; } else { ScrollToBottom(); } diff --git a/ConsoleUI/Toolkit/Formatting.cs b/ConsoleUI/Toolkit/Formatting.cs index a09a467f0e..6b891836b7 100644 --- a/ConsoleUI/Toolkit/Formatting.cs +++ b/ConsoleUI/Toolkit/Formatting.cs @@ -28,6 +28,25 @@ public static int ConvertCoord(int val, int max) } } + /// + /// Calculate the longest line length in a string when split on newlines + /// + /// String to analyze + /// + /// Length of longest line + /// + public static int MaxLineLength(string msg) + { + int len = 0; + string[] hardLines = msg.Split(new string[] {"\r\n", "\n"}, StringSplitOptions.None); + foreach (string line in hardLines) { + if (len < line.Length) { + len = line.Length; + } + } + return len; + } + /// /// Word wrap a long string into separate lines /// @@ -42,9 +61,11 @@ public static List WordWrap(string msg, int w) if (!string.IsNullOrEmpty(msg)) { // The string is allowed to contain line breaks. string[] hardLines = msg.Split(new string[] {"\r\n", "\n"}, StringSplitOptions.None); - foreach (var line in hardLines) { + foreach (string line in hardLines) { if (string.IsNullOrEmpty(line)) { messageLines.Add(""); + } else if (line.Length <= w) { + messageLines.Add(line); } else { int used = 0; while (used < line.Length) { diff --git a/ConsoleUI/Toolkit/ScreenContainer.cs b/ConsoleUI/Toolkit/ScreenContainer.cs index 7a1494775f..d7773ca41d 100644 --- a/ConsoleUI/Toolkit/ScreenContainer.cs +++ b/ConsoleUI/Toolkit/ScreenContainer.cs @@ -67,7 +67,11 @@ protected void AddObject(ScreenObject so) /// Action to bind to the key public void AddBinding(ConsoleKeyInfo k, KeyAction a) { - bindings.Add(k, a); + if (bindings.ContainsKey(k)) { + bindings[k] = a; + } else { + bindings.Add(k, a); + } } /// diff --git a/Core/Registry/AvailableModule.cs b/Core/Registry/AvailableModule.cs index d84ec4b9b0..3db3d2c350 100644 --- a/Core/Registry/AvailableModule.cs +++ b/Core/Registry/AvailableModule.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using System.Text; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -162,6 +164,26 @@ public List AllAvailable() return new List(module_version.Values.Reverse()); } + /// + /// Return the entire section of registry.json for this mod + /// + /// + /// Nicely formatted JSON string containing metadata for all of this mod's available versions + /// + public string FullMetadata() + { + StringWriter sw = new StringWriter(new StringBuilder()); + using (JsonTextWriter writer = new JsonTextWriter(sw) { + Formatting = Formatting.Indented, + Indentation = 4, + IndentChar = ' ' + }) + { + new JsonSerializer().Serialize(writer, this); + } + return sw.ToString(); + } + } } diff --git a/Core/Registry/IRegistryQuerier.cs b/Core/Registry/IRegistryQuerier.cs index 35331bc6d4..6dcd18fa7c 100644 --- a/Core/Registry/IRegistryQuerier.cs +++ b/Core/Registry/IRegistryQuerier.cs @@ -18,6 +18,15 @@ public interface IRegistryQuerier // TODO: This name is misleading. It's more a LatestAvailable's' List Available(KspVersionCriteria ksp_version); + /// + /// Get full JSON metadata string for a mod's available versions + /// + /// Name of the mod to look up + /// + /// JSON formatted string for all the available versions of the mod + /// + string GetAvailableMetadata(string identifier); + /// /// Returns the latest available version of a module that /// satisifes the specified version. diff --git a/Core/Registry/Registry.cs b/Core/Registry/Registry.cs index c860b2ef8e..d7632a1636 100644 --- a/Core/Registry/Registry.cs +++ b/Core/Registry/Registry.cs @@ -523,6 +523,25 @@ public List AllAvailable(string module) } } + /// + /// Get full JSON metadata string for a mod's available versions + /// + /// Name of the mod to look up + /// + /// JSON formatted string for all the available versions of the mod + /// + public string GetAvailableMetadata(string identifier) + { + try + { + return available_modules[identifier].FullMetadata(); + } + catch + { + return null; + } + } + /// /// Return the latest game version compatible with the given mod. /// diff --git a/Core/Registry/RegistryManager.cs b/Core/Registry/RegistryManager.cs index 778e574b3c..b8a86130fc 100644 --- a/Core/Registry/RegistryManager.cs +++ b/Core/Registry/RegistryManager.cs @@ -256,18 +256,6 @@ public static void DisposeAll() } } - /// - /// Call Dispose on all the registry managers in the cache. - /// Useful for exiting without Dispose-related exceptions. - /// - public static void DisposeAll() - { - foreach (RegistryManager rm in new List(registryCache.Values)) - { - rm.Dispose(); - } - } - /// /// Returns the currently installed modules in json format suitable for outputting to a ckan file. /// Defaults to using depends and with version numbers.