diff --git a/Cmdline/ConsoleUser.cs b/Cmdline/ConsoleUser.cs
index 12a733fcf0..2d42320a49 100644
--- a/Cmdline/ConsoleUser.cs
+++ b/Cmdline/ConsoleUser.cs
@@ -4,32 +4,53 @@
namespace CKAN.CmdLine
{
- public class ConsoleUser : NullUser
+ ///
+ /// The commandline implementation of the IUser interface.
+ ///
+ public class ConsoleUser : IUser
{
+ ///
+ /// A logger for this class.
+ /// ONLY FOR INTERNAL USE!
+ ///
private static readonly ILog log = LogManager.GetLogger(typeof(ConsoleUser));
- private bool m_Headless = false;
- public ConsoleUser(bool headless)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// If set to true, supress interactive dialogs like Yes/No-Dialog or SelectionDialog.
+ public ConsoleUser (bool headless)
{
- m_Headless = headless;
+ Headless = headless;
}
- public override bool Headless
+ ///
+ /// Gets a value indicating whether this is headless.
+ ///
+ /// true if headless; otherwise, false.
+ public bool Headless { get; }
+
+ ///
+ /// Gets a value indicating whether this
+ /// should show confirmation prompts. Depends on .
+ ///
+ public bool ConfirmPrompt
{
- get
- {
- return m_Headless;
- }
+ get { return !Headless; }
}
- protected override bool DisplayYesNoDialog(string message)
+ ///
+ /// Ask the user for a yes or no input.
+ ///
+ /// Question.
+ public bool RaiseYesNoDialog(string question)
{
- if (m_Headless)
+ if (Headless)
{
return true;
}
- Console.Write("{0} [Y/n] ", message);
+ Console.Write("{0} [Y/n] ", question);
while (true)
{
var input = Console.In.ReadLine();
@@ -60,22 +81,20 @@ protected override bool DisplayYesNoDialog(string message)
}
}
- protected override void DisplayMessage(string message, params object[] args)
- {
- Console.WriteLine(message, args);
- }
-
- protected override void DisplayError(string message, params object[] args)
- {
- Console.Error.WriteLine(message, args);
- }
-
- protected override int DisplaySelectionDialog(string message, params object[] args)
+ ///
+ /// Ask the user to select one of the elements of the array.
+ /// The output is index 0 based.
+ /// To supply a default option, make the first option an integer indicating the index of it.
+ ///
+ /// The selection dialog.
+ /// Message.
+ /// Array of available options.
+ public int RaiseSelectionDialog(string message, params object[] args)
{
const int return_cancel = -1;
// Check for the headless flag.
- if (m_Headless)
+ if (Headless)
{
// Return that the user cancelled the selection process.
return return_cancel;
@@ -227,17 +246,33 @@ protected override int DisplaySelectionDialog(string message, params object[] ar
return result;
}
- protected override void ReportProgress(string format, int percent)
+ ///
+ /// Write an error to the console.
+ ///
+ /// Message.
+ /// Possible arguments to format the message.
+ public void RaiseError(string message, params object[] args)
+ {
+ Console.Error.WriteLine(message, args);
+ }
+
+ ///
+ /// Write a progress message including the percentage to the console.
+ /// Rewrites the line, so the console is not cluttered by progress messages.
+ ///
+ /// Message.
+ /// Progress in percent.
+ public void RaiseProgress(string message, int percent)
{
- if (Regex.IsMatch(format, "download", RegexOptions.IgnoreCase))
+ if (Regex.IsMatch(message, "download", RegexOptions.IgnoreCase))
{
// In headless mode, only print a new message if the percent has changed,
// to reduce clutter in Jenkins for large downloads
- if (!m_Headless || percent != previousPercent)
+ if (!Headless || percent != previousPercent)
{
// The \r at the front here causes download messages to *overwrite* each other.
Console.Write(
- "\r{0} - {1}% ", format, percent);
+ "\r{0} - {1}% ", message, percent);
previousPercent = percent;
}
}
@@ -246,10 +281,23 @@ protected override void ReportProgress(string format, int percent)
// The percent looks weird on non-download messages.
// The leading newline makes sure we don't end up with a mess from previous
// download messages.
- Console.Write("\r\n{0}", format);
+ Console.Write("\r\n{0}", message);
}
}
+ ///
+ /// Needed for
+ ///
private int previousPercent = -1;
+
+ ///
+ /// Writes a message to the console.
+ ///
+ /// Message.
+ /// Arguments to format the message.
+ public void RaiseMessage(string message, params object[] args)
+ {
+ Console.WriteLine(message, args);
+ }
}
}
diff --git a/ConsoleUI/Toolkit/ConsoleScreen.cs b/ConsoleUI/Toolkit/ConsoleScreen.cs
index 41f3693733..62558ec7ee 100644
--- a/ConsoleUI/Toolkit/ConsoleScreen.cs
+++ b/ConsoleUI/Toolkit/ConsoleScreen.cs
@@ -72,13 +72,19 @@ protected virtual string MenuTip()
///
protected ConsolePopupMenu mainMenu = null;
- // IUser
+ #region IUser
///
/// Tell IUser clients that we have the ability to interact with the user
///
public bool Headless { get { return false; } }
+ ///
+ /// Show confirmation prompts.
+ /// Atm used to ask for confirmation prior to installing mods.
+ ///
+ public bool ConfirmPrompt { get { return true; } }
+
// These functions can be implemented the same on all screens,
// so they are not virtual.
@@ -207,7 +213,7 @@ public void RaiseProgress(string message, int percent)
/// Value from 0 to 100 representing task completion
protected virtual void Progress(string message, int percent) { }
- // End IUser
+ #endregion IUser
private void DrawSelectedHamburger()
{
diff --git a/Core/ModuleInstaller.cs b/Core/ModuleInstaller.cs
index 3b2048af6b..4d0c9b4bf1 100644
--- a/Core/ModuleInstaller.cs
+++ b/Core/ModuleInstaller.cs
@@ -177,7 +177,15 @@ public void InstallList(ICollection modules, RelationshipResolverOpt
}
}
- bool ok = User.RaiseYesNoDialog("\r\nContinue?");
+ bool ok;
+ if (User.ConfirmPrompt)
+ {
+ ok = User.RaiseYesNoDialog("\r\nContinue?");
+ }
+ else
+ {
+ ok = true;
+ }
if (!ok)
{
@@ -770,7 +778,15 @@ public void UninstallList(IEnumerable mods)
User.RaiseMessage(" * {0} {1}", module.Module.name, module.Module.version);
}
- bool ok = User.RaiseYesNoDialog("\r\nContinue?");
+ bool ok;
+ if (User.ConfirmPrompt)
+ {
+ ok = User.RaiseYesNoDialog("\r\nContinue?");
+ }
+ else
+ {
+ ok = true;
+ }
if (!ok)
{
diff --git a/Core/User.cs b/Core/User.cs
index 7db134e765..3dfa6595d4 100644
--- a/Core/User.cs
+++ b/Core/User.cs
@@ -1,80 +1,73 @@
-// Communicate with the user (status messages, yes/no questions, etc)
-// This class will proxy to either the GUI or cmdline functionality.
-
namespace CKAN
{
-
+ ///
+ /// This interface holds all methods which communicate with the user in some way.
+ /// Every CKAN interface (GUI, cmdline, consoleUI) has an implementation of the IUser interface.
+ /// The implementations define HOW we interact with the user.
+ ///
public interface IUser
{
bool Headless { get; }
+ bool ConfirmPrompt { get; }
bool RaiseYesNoDialog(string question);
int RaiseSelectionDialog(string message, params object[] args);
void RaiseError(string message, params object[] args);
void RaiseProgress(string message, int percent);
- void RaiseMessage(string message, params object[] url);
+ void RaiseMessage(string message, params object[] args);
}
- //Can be used in tests to supress output or as a base class for other types of user.
- //It supplies no op event handlers so that subclasses can avoid null checks.
+ ///
+ /// To be used in tests.
+ /// Supresses all output.
+ ///
public class NullUser : IUser
{
- public static readonly IUser User = new NullUser();
-
- public NullUser() { }
-
- public virtual bool Headless
- {
- get { return false; }
- }
-
- protected virtual bool DisplayYesNoDialog(string message)
+ ///
+ /// NullUser is headless. Variable not used for NullUser.
+ ///
+ public bool Headless
{
- return true;
+ get { return true; }
}
- protected virtual int DisplaySelectionDialog(string message, params object[] args)
- {
- return 0;
- }
-
- protected virtual void DisplayError(string message, params object[] args)
- {
- }
-
- protected virtual void ReportProgress(string format, int percent)
- {
- }
-
- protected virtual void DisplayMessage(string message, params object[] args)
+ ///
+ /// Indicates if a confirmation prompt should be shown for this type of User.
+ /// NullUser returns false.
+ ///
+ /// true if confirm prompt should be shown; false if not.
+ public bool ConfirmPrompt
{
+ get { return false; }
}
+ ///
+ /// NullUser returns true.
+ ///
public bool RaiseYesNoDialog(string question)
{
- return DisplayYesNoDialog(question);
+ return true;
}
+ ///
+ /// NullUser returns 0.
+ ///
public int RaiseSelectionDialog(string message, params object[] args)
{
- return DisplaySelectionDialog(message, args);
+ return 0;
}
public void RaiseError(string message, params object[] args)
{
- DisplayError(message, args);
}
public void RaiseProgress(string message, int percent)
{
- ReportProgress(message, percent);
}
public void RaiseMessage(string message, params object[] args)
{
- DisplayMessage(message, args);
}
-
}
}
diff --git a/GUI/CKAN-GUI.csproj b/GUI/CKAN-GUI.csproj
index 03ead9787e..de268760d6 100644
--- a/GUI/CKAN-GUI.csproj
+++ b/GUI/CKAN-GUI.csproj
@@ -238,6 +238,12 @@
YesNoDialog.cs
+
+ Form
+
+
+ SelectionDialog.cs
+
@@ -294,6 +300,9 @@
YesNoDialog.cs
+
+ SelectionDialog.cs
+
diff --git a/GUI/ChooseKSPInstance.cs b/GUI/ChooseKSPInstance.cs
index 90932997a1..0e6101655a 100644
--- a/GUI/ChooseKSPInstance.cs
+++ b/GUI/ChooseKSPInstance.cs
@@ -87,7 +87,7 @@ private void AddNewButton_Click(object sender, EventArgs e)
}
catch (NotKSPDirKraken k)
{
- GUI.user.displayError("Directory {0} is not valid KSP directory.",
+ GUI.user.RaiseError("Directory {0} is not valid KSP directory.",
new object[] { k.path });
return;
}
@@ -125,7 +125,7 @@ private void UseSelectedInstance()
}
catch (NotKSPDirKraken k)
{
- GUI.user.displayError("Directory {0} is not valid KSP directory.",
+ GUI.user.RaiseError("Directory {0} is not valid KSP directory.",
new object[] { k.path });
}
}
diff --git a/GUI/GUIUser.cs b/GUI/GUIUser.cs
index ee32da91e7..653747a92d 100644
--- a/GUI/GUIUser.cs
+++ b/GUI/GUIUser.cs
@@ -2,44 +2,83 @@
namespace CKAN
{
-
- public class GUIUser : NullUser
+ ///
+ /// The GUI implementation of the IUser interface.
+ ///
+ public class GUIUser : IUser
{
- public delegate bool DisplayYesNo(string message);
-
- public Action displayMessage;
- public Action displayError;
- public DisplayYesNo displayYesNo;
+ ///
+ /// A GUIUser is obviously not headless. Returns false.
+ ///
+ public bool Headless
+ {
+ get { return false; }
+ }
- protected override bool DisplayYesNoDialog(string message)
+ ///
+ /// Indicates if a confirmation prompt should be shown for this type of User.
+ /// E.g. don't ask for confirmation prior to installing mods in the GUI,
+ /// because it's already done in a different way.
+ ///
+ /// true if confirm prompt should be shown; false if not.
+ public bool ConfirmPrompt
{
- if (displayYesNo == null)
- return true;
+ get { return false; }
+ }
- return displayYesNo(message);
+ ///
+ /// Shows a small form with the question.
+ /// User can select yes or no (ya dont say).
+ ///
+ /// true if user pressed yes, false if no.
+ /// Question.
+ public bool RaiseYesNoDialog (string question)
+ {
+ return Main.Instance.YesNoDialog(question);
}
- protected override void DisplayMessage(string message, params object[] args)
+ ///
+ /// Will show a small form with the message and a list to choose from.
+ ///
+ /// The index of the selection in the args array. 0-based!
+ /// Message.
+ /// Array of offered options.
+ public int RaiseSelectionDialog(string message, params object[] args)
{
- if (displayMessage != null)
- {
- displayMessage(message, args);
- }
+ return Main.Instance.SelectionDialog(message, args);
}
- protected override void DisplayError(string message, params object[] args)
+ ///
+ /// Shows a message box containing the formatted error message.
+ ///
+ /// Message.
+ /// Arguments to format the message.
+ public void RaiseError (string message, params object[] args)
{
- if (displayError != null)
- {
- displayError(message, args);
- }
+ Main.Instance.ErrorDialog(message, args);
}
- protected override void ReportProgress(string format, int percent)
+ ///
+ /// Sets the progress bars and the message box of the current WaitTabPage.
+ ///
+ /// Message.
+ /// Progress in percent.
+ public void RaiseProgress( string message, int percent)
{
- Main.Instance.SetDescription($"{format} - {percent}%");
+ Main.Instance.SetDescription($"{message} - {percent}%");
Main.Instance.SetProgress(percent);
}
+
+ ///
+ /// Displays the formatted message in the lower StatusStrip.
+ /// Removes any newline strings.
+ ///
+ /// Message.
+ /// Arguments to fromat the message.
+ public void RaiseMessage (string message, params object[] args)
+ {
+ Main.Instance.AddStatusMessage(message, args);
+ }
}
}
diff --git a/GUI/Main.cs b/GUI/Main.cs
index 4d0e76d6f1..11b13d2a1b 100644
--- a/GUI/Main.cs
+++ b/GUI/Main.cs
@@ -170,10 +170,6 @@ public Main(string[] cmdlineArgs, KSPManager mgr, GUIUser user, bool showConsole
log.Info("Starting the GUI");
commandLineArgs = cmdlineArgs;
- // These are used by KSPManager's constructor to show messages about directory creation
- user.displayMessage = AddStatusMessage;
- user.displayError = ErrorDialog;
-
manager = mgr ?? new KSPManager(user);
currentUser = user;
@@ -389,10 +385,7 @@ protected override void OnLoad(EventArgs e)
installWorker.RunWorkerCompleted += PostInstallMods;
installWorker.DoWork += InstallMods;
- var old_YesNoDialog = currentUser.displayYesNo;
- currentUser.displayYesNo = YesNoDialog;
URLHandlers.RegisterURLHandler(configuration, currentUser);
- currentUser.displayYesNo = old_YesNoDialog;
ApplyToolButton.Enabled = false;
diff --git a/GUI/MainDialogs.cs b/GUI/MainDialogs.cs
index 553f166779..298f5c9d07 100644
--- a/GUI/MainDialogs.cs
+++ b/GUI/MainDialogs.cs
@@ -11,6 +11,7 @@ public partial class Main
private SettingsDialog settingsDialog;
private PluginsDialog pluginsDialog;
private YesNoDialog yesNoDialog;
+ private SelectionDialog selectionDialog;
public void RecreateDialogs()
{
@@ -18,6 +19,7 @@ public void RecreateDialogs()
settingsDialog = controlFactory.CreateControl();
pluginsDialog = controlFactory.CreateControl();
yesNoDialog = controlFactory.CreateControl();
+ selectionDialog = controlFactory.CreateControl();
}
public void AddStatusMessage(string text, params object[] args)
@@ -40,6 +42,11 @@ public bool YesNoDialog(string text)
return yesNoDialog.ShowYesNoDialog(text) == DialogResult.Yes;
}
+ public int SelectionDialog(string message, params object[] args)
+ {
+ return selectionDialog.ShowSelectionDialog(message, args);
+ }
+
// Ugly Hack. Possible fix is to alter the relationship provider so we can use a loop
// over reason for to find a user requested mod. Or, you know, pass in a handler to it.
private readonly ConcurrentStack last_mod_to_have_install_toggled = new ConcurrentStack();
diff --git a/GUI/MainImport.cs b/GUI/MainImport.cs
index 6f0bc56de6..30b8de4ce2 100644
--- a/GUI/MainImport.cs
+++ b/GUI/MainImport.cs
@@ -31,10 +31,7 @@ private void ImportModules()
if (dlg.ShowDialog() == DialogResult.OK
&& dlg.FileNames.Length > 0)
{
-
- // Set up GUI to respond to IUser calls...
- GUIUser.DisplayYesNo old_YesNoDialog = currentUser.displayYesNo;
- currentUser.displayYesNo = YesNoDialog;
+ // Show WaitTabPage (status page) and lock it.
tabController.RenameTab("WaitTabPage", "Status log");
tabController.ShowTab("WaitTabPage");
tabController.SetTabLock(true);
@@ -50,7 +47,6 @@ private void ImportModules()
finally
{
// Put GUI back the way we found it
- currentUser.displayYesNo = old_YesNoDialog;
tabController.SetTabLock(false);
tabController.HideTab("WaitTabPage");
}
diff --git a/GUI/MainRepo.cs b/GUI/MainRepo.cs
index f2e8181abd..ba307a7c4e 100644
--- a/GUI/MainRepo.cs
+++ b/GUI/MainRepo.cs
@@ -26,9 +26,6 @@ public static RepositoryList FetchMasterRepositoryList(Uri master_uri = null)
public void UpdateRepo()
{
- var old_dialog = currentUser.displayYesNo;
- currentUser.displayYesNo = YesNoDialog;
-
tabController.RenameTab("WaitTabPage", "Updating repositories");
CurrentInstance.ScanGameData();
@@ -37,10 +34,7 @@ public void UpdateRepo()
{
m_UpdateRepoWorker.RunWorkerAsync();
}
- finally
- {
- currentUser.displayYesNo = old_dialog;
- }
+ catch { }
Util.Invoke(this, SwitchEnabledState);
@@ -84,8 +78,6 @@ private void UpdateRepo(object sender, DoWorkEventArgs e)
{
errorDialog.ShowErrorDialog("Failed to connect to repository. Exception: " + ex.Message);
}
-
- currentUser.displayYesNo = null;
}
private void PostUpdateRepo(object sender, RunWorkerCompletedEventArgs e)
@@ -112,14 +104,12 @@ private void ShowRefreshQuestion()
{
if (!configuration.RefreshOnStartupNoNag)
{
- currentUser.displayYesNo = YesNoDialog;
configuration.RefreshOnStartupNoNag = true;
- if (!currentUser.displayYesNo("Would you like CKAN to refresh the modlist every time it is loaded? (You can always manually refresh using the button up top.)"))
+ if (!currentUser.RaiseYesNoDialog("Would you like CKAN to refresh the modlist every time it is loaded? (You can always manually refresh using the button up top.)"))
{
configuration.RefreshOnStartup = false;
}
configuration.Save();
- currentUser.displayYesNo = null;
}
}
diff --git a/GUI/SelectionDialog.Designer.cs b/GUI/SelectionDialog.Designer.cs
new file mode 100644
index 0000000000..7c5367d850
--- /dev/null
+++ b/GUI/SelectionDialog.Designer.cs
@@ -0,0 +1,129 @@
+using System;
+
+namespace CKAN
+{
+ partial class SelectionDialog
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose (bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent ()
+ {
+ this.panel1 = new System.Windows.Forms.Panel();
+ this.MessageLabel = new System.Windows.Forms.Label();
+ this.SelectButton = new System.Windows.Forms.Button();
+ this.DefaultButton = new System.Windows.Forms.Button();
+ this.CancelButton = new System.Windows.Forms.Button();
+ this.OptionsList = new System.Windows.Forms.ListBox();
+ this.panel1.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // panel1
+ //
+ this.panel1.Controls.Add(this.MessageLabel);
+ this.panel1.Controls.Add(this.OptionsList);
+ this.panel1.Controls.Add(this.CancelButton);
+ this.panel1.Controls.Add(this.DefaultButton);
+ this.panel1.Controls.Add(this.SelectButton);
+ this.panel1.Location = new System.Drawing.Point(10, 10);
+ this.panel1.Size = new System.Drawing.Size(400, 400);
+ this.panel1.Name = "panel1";
+ this.OptionsList.TabStop = false;
+ this.DefaultButton.UseVisualStyleBackColor = true;
+ //
+ // MessageLabel
+ //
+ this.MessageLabel.Location = new System.Drawing.Point(5, 5);
+ this.MessageLabel.Size = new System.Drawing.Size(390, 40);
+ this.MessageLabel.Name = "MessageLabel";
+ this.OptionsList.TabStop = false;
+ this.MessageLabel.Text = "Please select:";
+ this.MessageLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+ this.DefaultButton.UseVisualStyleBackColor = true;
+ //
+ // OptionsList
+ //
+ this.OptionsList.Location = new System.Drawing.Point(5, 55);
+ this.OptionsList.Size = new System.Drawing.Size(390, 315);
+ this.OptionsList.SelectionMode = System.Windows.Forms.SelectionMode.One;
+ this.OptionsList.MultiColumn = false;
+ this.OptionsList.SelectedIndexChanged += new System.EventHandler(OptionsList_SelectedIndexChanged);
+ this.OptionsList.Name = "OptionsList";
+ this.DefaultButton.UseVisualStyleBackColor = true;
+ //
+ // SelectButton
+ //
+ this.SelectButton.Location = new System.Drawing.Point(325, 375);
+ this.SelectButton.Size = new System.Drawing.Size(60, 20);
+ this.SelectButton.DialogResult = System.Windows.Forms.DialogResult.OK;
+ this.SelectButton.Name = "SelectButton";
+ this.SelectButton.TabIndex = 1;
+ this.SelectButton.Text = "Select";
+ this.SelectButton.UseVisualStyleBackColor = true;
+ //
+ // DefaultButton
+ //
+ this.DefaultButton.Location = new System.Drawing.Point(160, 375);
+ this.DefaultButton.Size = new System.Drawing.Size(60, 20);
+ this.DefaultButton.DialogResult = System.Windows.Forms.DialogResult.Yes;
+ this.DefaultButton.Name = "SelectButton";
+ this.DefaultButton.TabIndex = 0;
+ this.DefaultButton.Text = "Default";
+ this.DefaultButton.UseVisualStyleBackColor = true;
+ //
+ // CancelButton
+ //
+ this.CancelButton.Location = new System.Drawing.Point(5, 375);
+ this.CancelButton.Size = new System.Drawing.Size(60, 20);
+ this.CancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.CancelButton.Name = "CancelButton";
+ this.CancelButton.TabIndex = 2;
+ this.CancelButton.Text = "Cancel";
+ this.CancelButton.UseVisualStyleBackColor = true;
+ //
+ // SelectionDialog
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(420, 420);
+ this.Controls.Add(this.panel1);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
+ this.Name = "SelectionDialog";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "CKAN Selection Dialog";
+ this.panel1.ResumeLayout(false);
+ this.ResumeLayout(false);
+ this.PerformLayout();
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Panel panel1;
+ private System.Windows.Forms.Label MessageLabel;
+ private System.Windows.Forms.Button SelectButton;
+ private System.Windows.Forms.Button DefaultButton;
+ private new System.Windows.Forms.Button CancelButton;
+ private System.Windows.Forms.ListBox OptionsList;
+ }
+}
diff --git a/GUI/SelectionDialog.cs b/GUI/SelectionDialog.cs
new file mode 100644
index 0000000000..cb04ff19ed
--- /dev/null
+++ b/GUI/SelectionDialog.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Windows.Forms;
+
+namespace CKAN
+{
+ public partial class SelectionDialog : FormCompatibility
+ {
+ int currentSelected;
+
+ public SelectionDialog ()
+ {
+ InitializeComponent();
+ currentSelected = 0;
+ }
+
+ ///
+ /// Shows the selection dialog.
+ ///
+ /// The selected index, -1 if canceled.
+ /// Message.
+ /// Array of items to select from.
+ public int ShowSelectionDialog (string message, params object[] args)
+ {
+ int defaultSelection = -1;
+ int return_cancel = -1;
+
+ // Validate input.
+ if (String.IsNullOrWhiteSpace(message))
+ {
+ throw new Kraken("Passed message string must be non-empty.");
+ }
+
+ if (args.Length == 0)
+ {
+ throw new Kraken("Passed list of selection candidates must be non-empty.");
+ }
+
+ // Hide the default button unless we have a default option
+ Util.Invoke(DefaultButton, DefaultButton.Hide);
+ // Clear the item list.
+ Util.Invoke(OptionsList, OptionsList.Items.Clear);
+
+ // Check if we have a default option.
+ if (args[0] is int)
+ {
+ // Check that the default selection makes sense.
+ defaultSelection = (int)args[0];
+
+ if (defaultSelection < 0 || defaultSelection > args.Length - 1)
+ {
+ throw new Kraken("Passed default arguments is out of range of the selection candidates.");
+ }
+
+ // Extract the relevant arguments.
+ object[] newArgs = new object[args.Length - 1];
+
+ for (int i = 1; i < args.Length; i++)
+ {
+ newArgs[i - 1] = args[i];
+ }
+
+ args = newArgs;
+
+ // Show the defaultButton.
+ Util.Invoke(DefaultButton, DefaultButton.Show);
+ }
+
+ // Further data validation.
+ foreach (object argument in args)
+ {
+ if (String.IsNullOrWhiteSpace(argument.ToString()))
+ {
+ throw new Kraken("Candidate may not be empty.");
+ }
+ }
+
+ // Add all items to the OptionsList.
+ for (int i = 0; i < args.Length; i++)
+ {
+ if (defaultSelection == i)
+ {
+ Util.Invoke(OptionsList, () => OptionsList.Items.Add(String.Concat(args[i].ToString(), " -- Default")));
+
+ }
+ else
+ {
+ Util.Invoke(OptionsList, () => OptionsList.Items.Add(args[i].ToString()));
+ }
+ }
+
+ // Write the message to the label.
+ Util.Invoke(MessageLabel, () => MessageLabel.Text = message);
+
+ // Now show the dialog and get the return values.
+ DialogResult result = ShowDialog();
+ if (result == DialogResult.Yes)
+ {
+ // If pressed Defaultbutton
+ return defaultSelection;
+ }
+ else if (result == DialogResult.Cancel)
+ {
+ // If pressed CancelButton
+ return return_cancel;
+ }
+ else
+ {
+ return currentSelected;
+ }
+ }
+
+ public void HideYesNoDialog ()
+ {
+ Util.Invoke(this, Close);
+ }
+
+ private void OptionsList_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ currentSelected = OptionsList.SelectedIndex;
+ }
+ }
+}
diff --git a/GUI/SelectionDialog.resx b/GUI/SelectionDialog.resx
new file mode 100644
index 0000000000..1af7de150c
--- /dev/null
+++ b/GUI/SelectionDialog.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Netkan/ConsoleUser.cs b/Netkan/ConsoleUser.cs
index 028f9d59f1..15cfa70c72 100644
--- a/Netkan/ConsoleUser.cs
+++ b/Netkan/ConsoleUser.cs
@@ -4,24 +4,55 @@
namespace CKAN
{
- public class ConsoleUser : NullUser
+ ///
+ /// The commandline implementation of the IUser interface.
+ /// It is exactly the same as the one of the CKAN-cmdline.
+ /// At least at the time of this commit (git blame is your friend).
+ ///
+ public class ConsoleUser : IUser
{
+ ///
+ /// A logger for this class.
+ /// ONLY FOR INTERNAL USE!
+ ///
private static readonly ILog log = LogManager.GetLogger(typeof(ConsoleUser));
- private bool m_Headless = false;
- public ConsoleUser(bool headless)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// If set to true, supress interactive dialogs like Yes/No-Dialog or SelectionDialog.
+ public ConsoleUser (bool headless)
{
- m_Headless = headless;
+ Headless = headless;
}
- protected override bool DisplayYesNoDialog(string message)
+ ///
+ /// Gets a value indicating whether this
+ /// should show confirmation prompts. Depends on .
+ ///
+ public bool ConfirmPrompt
{
- if (m_Headless)
+ get { return !Headless; }
+ }
+
+ ///
+ /// Gets a value indicating whether this is headless.
+ ///
+ /// true if headless; otherwise, false.
+ public bool Headless { get; }
+
+ ///
+ /// Ask the user for a yes or no input.
+ ///
+ /// Question.
+ public bool RaiseYesNoDialog (string question)
+ {
+ if (Headless)
{
return true;
}
- Console.Write("{0} [Y/N] ", message);
+ Console.Write("{0} [Y/n] ", question);
while (true)
{
var input = Console.In.ReadLine();
@@ -42,31 +73,208 @@ protected override bool DisplayYesNoDialog(string message)
{
return false;
}
+ if (input.Equals(string.Empty))
+ {
+ // User pressed enter without any text, assuming default choice.
+ return true;
+ }
+
Console.Write("Invalid input. Please enter yes or no");
}
}
- protected override void DisplayMessage(string message, params object[] args)
+ ///
+ /// Ask the user to select one of the elements of the array.
+ /// The output is index 0 based.
+ /// To supply a default option, make the first option an integer indicating the index of it.
+ ///
+ /// The selection dialog.
+ /// Message.
+ /// Array of available options.
+ public int RaiseSelectionDialog (string message, params object[] args)
{
- Console.WriteLine(message, args);
+ const int return_cancel = -1;
+
+ // Check for the headless flag.
+ if (Headless)
+ {
+ // Return that the user cancelled the selection process.
+ return return_cancel;
+ }
+
+ // Validate input.
+ if (String.IsNullOrWhiteSpace(message))
+ {
+ throw new Kraken("Passed message string must be non-empty.");
+ }
+
+ if (args.Length == 0)
+ {
+ throw new Kraken("Passed list of selection candidates must be non-empty.");
+ }
+
+ // Check if we have a default selection.
+ int defaultSelection = -1;
+
+ if (args[0] is int)
+ {
+ // Check that the default selection makes sense.
+ defaultSelection = (int)args[0];
+
+ if (defaultSelection < 0 || defaultSelection > args.Length - 1)
+ {
+ throw new Kraken("Passed default arguments is out of range of the selection candidates.");
+ }
+
+ // Extract the relevant arguments.
+ object[] newArgs = new object[args.Length - 1];
+
+ for (int i = 1; i < args.Length; i++)
+ {
+ newArgs[i - 1] = args[i];
+ }
+
+ args = newArgs;
+ }
+
+ // Further data validation.
+ foreach (object argument in args)
+ {
+ if (String.IsNullOrWhiteSpace(argument.ToString()))
+ {
+ throw new Kraken("Candidate may not be empty.");
+ }
+ }
+
+ // List options.
+ for (int i = 0; i < args.Length; i++)
+ {
+ string CurrentRow = String.Format("{0}", i + 1);
+
+ if (i == defaultSelection)
+ {
+ CurrentRow += "*";
+ }
+
+ CurrentRow += String.Format(") {0}", args[i]);
+
+ RaiseMessage(CurrentRow);
+ }
+
+ // Create message string.
+ string output = String.Format("Enter a number between {0} and {1} (To cancel press \"c\" or \"n\".", 1, args.Length);
+
+ if (defaultSelection >= 0)
+ {
+ output += String.Format(" \"Enter\" will select {0}.", defaultSelection + 1);
+ }
+
+ output += "): ";
+
+ RaiseMessage(output);
+
+ bool valid = false;
+ int result = 0;
+
+ while (!valid)
+ {
+ // Wait for input from the command line.
+ string input = Console.In.ReadLine();
+
+ if (input == null)
+ {
+ // No console present, cancel the process.
+ return return_cancel;
+ }
+
+ input = input.Trim().ToLower();
+
+ // Check for default selection.
+ if (String.IsNullOrEmpty(input))
+ {
+ if (defaultSelection >= 0)
+ {
+ return defaultSelection;
+ }
+ }
+
+ // Check for cancellation characters.
+ if (input == "c" || input == "n")
+ {
+ RaiseMessage("Selection cancelled.");
+
+ return return_cancel;
+ }
+
+ // Attempt to parse the input.
+ try
+ {
+ result = Convert.ToInt32(input);
+ }
+ catch (FormatException)
+ {
+ RaiseMessage("The input is not a number.");
+ continue;
+ }
+ catch (OverflowException)
+ {
+ RaiseMessage("The number in the input is too large.");
+ continue;
+ }
+
+ // Check the input against the boundaries.
+ if (result > args.Length)
+ {
+ RaiseMessage("The number in the input is too large.");
+ RaiseMessage(output);
+
+ continue;
+ }
+ else if (result < 1)
+ {
+ RaiseMessage("The number in the input is too small.");
+ RaiseMessage(output);
+
+ continue;
+ }
+
+ // The list we provide is index 1 based, but the array is index 0 based.
+ result--;
+
+ // We have checked for all errors and have gotten a valid result. Stop the input loop.
+ valid = true;
+ }
+
+ return result;
}
- protected override void DisplayError(string message, params object[] args)
+ ///
+ /// Write an error to the console.
+ ///
+ /// Message.
+ /// Possible arguments to format the message.
+ public void RaiseError (string message, params object[] args)
{
Console.Error.WriteLine(message, args);
}
- protected override void ReportProgress(string format, int percent)
+ ///
+ /// Write a progress message including the percentage to the console.
+ /// Rewrites the line, so the console is not cluttered by progress messages.
+ ///
+ /// Message.
+ /// Progress in percent.
+ public void RaiseProgress (string message, int percent)
{
- if (Regex.IsMatch(format, "download", RegexOptions.IgnoreCase))
+ if (Regex.IsMatch(message, "download", RegexOptions.IgnoreCase))
{
// In headless mode, only print a new message if the percent has changed,
// to reduce clutter in Jenkins for large downloads
- if (!m_Headless || percent != previousPercent)
+ if (!Headless || percent != previousPercent)
{
// The \r at the front here causes download messages to *overwrite* each other.
Console.Write(
- "\r{0} - {1}% ", format, percent);
+ "\r{0} - {1}% ", message, percent);
previousPercent = percent;
}
}
@@ -75,10 +283,23 @@ protected override void ReportProgress(string format, int percent)
// The percent looks weird on non-download messages.
// The leading newline makes sure we don't end up with a mess from previous
// download messages.
- Console.Write("\r\n{0}", format);
+ Console.Write("\r\n{0}", message);
}
}
+ ///
+ /// Needed for
+ ///
private int previousPercent = -1;
+
+ ///
+ /// Writes a message to the console.
+ ///
+ /// Message.
+ /// Arguments to format the message.
+ public void RaiseMessage (string message, params object[] args)
+ {
+ Console.WriteLine(message, args);
+ }
}
}
diff --git a/Tests/Core/KSP.cs b/Tests/Core/KSP.cs
index 4199c83a80..9d0a416dd3 100644
--- a/Tests/Core/KSP.cs
+++ b/Tests/Core/KSP.cs
@@ -12,13 +12,15 @@ public class KSP
{
private CKAN.KSP ksp;
private string ksp_dir;
+ private IUser nullUser;
[SetUp]
public void Setup()
{
ksp_dir = TestData.NewTempDir();
+ nullUser = new NullUser();
TestData.CopyDirectory(TestData.good_ksp_dir(), ksp_dir);
- ksp = new CKAN.KSP(ksp_dir, "test", NullUser.User);
+ ksp = new CKAN.KSP(ksp_dir, "test", nullUser);
}
[TearDown]
@@ -138,7 +140,7 @@ public void Valid_MissingVersionData_False()
File.WriteAllText(jsonpath, compatible_ksp_versions_json);
// Act
- CKAN.KSP my_ksp = new CKAN.KSP(gamedir, "missing-ver-test", NullUser.User);
+ CKAN.KSP my_ksp = new CKAN.KSP(gamedir, "missing-ver-test", nullUser);
// Assert
Assert.IsFalse(my_ksp.Valid);
@@ -170,7 +172,7 @@ public void Constructor_NullMainCompatVer_NoCrash()
// Act & Assert
Assert.DoesNotThrow(() =>
{
- CKAN.KSP my_ksp = new CKAN.KSP(gamedir, "null-compat-ver-test", NullUser.User);
+ CKAN.KSP my_ksp = new CKAN.KSP(gamedir, "null-compat-ver-test", nullUser);
});
}
diff --git a/Tests/Core/ModuleInstaller.cs b/Tests/Core/ModuleInstaller.cs
index 92b0bd7659..b6010a206d 100644
--- a/Tests/Core/ModuleInstaller.cs
+++ b/Tests/Core/ModuleInstaller.cs
@@ -25,6 +25,8 @@ public class ModuleInstaller
private string mission_zip;
private CkanModule mission_mod;
+ private IUser nullUser;
+
[SetUp]
public void Setup()
{
@@ -40,6 +42,8 @@ public void Setup()
mission_zip = TestData.MissionZip();
mission_mod = TestData.MissionModule();
+
+ nullUser = new NullUser();
}
[Test]
@@ -434,7 +438,7 @@ public void UninstallModNotFound()
Assert.Throws(delegate
{
// This should throw, as our tidy KSP has no mods installed.
- CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, NullUser.User).UninstallList("Foo");
+ CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, nullUser).UninstallList("Foo");
});
manager.CurrentInstance = null; // I weep even more.
@@ -480,7 +484,7 @@ public void CanInstallMod()
// Attempt to install it.
List modules = new List { TestData.DogeCoinFlag_101_module().identifier };
- CKAN.ModuleInstaller.GetInstance(ksp.KSP, manager.Cache, NullUser.User).InstallList(modules, new RelationshipResolverOptions());
+ CKAN.ModuleInstaller.GetInstance(ksp.KSP, manager.Cache, nullUser).InstallList(modules, new RelationshipResolverOptions());
// Check that the module is installed.
Assert.IsTrue(File.Exists(mod_file_path));
@@ -511,13 +515,13 @@ public void CanUninstallMod()
List modules = new List { TestData.DogeCoinFlag_101_module().identifier };
- CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, NullUser.User).InstallList(modules, new RelationshipResolverOptions());
+ CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, nullUser).InstallList(modules, new RelationshipResolverOptions());
// Check that the module is installed.
Assert.IsTrue(File.Exists(mod_file_path));
// Attempt to uninstall it.
- CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, NullUser.User).UninstallList(modules);
+ CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, nullUser).UninstallList(modules);
// Check that the module is not installed.
Assert.IsFalse(File.Exists(mod_file_path));
@@ -549,7 +553,7 @@ public void UninstallEmptyDirs()
List modules = new List { TestData.DogeCoinFlag_101_module().identifier };
- CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, NullUser.User).InstallList(modules, new RelationshipResolverOptions());
+ CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, nullUser).InstallList(modules, new RelationshipResolverOptions());
modules.Clear();
@@ -559,7 +563,7 @@ public void UninstallEmptyDirs()
modules.Add(TestData.DogeCoinPlugin_module().identifier);
- CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, NullUser.User).InstallList(modules, new RelationshipResolverOptions());
+ CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, nullUser).InstallList(modules, new RelationshipResolverOptions());
modules.Clear();
@@ -571,7 +575,7 @@ public void UninstallEmptyDirs()
modules.Add(TestData.DogeCoinFlag_101_module().identifier);
modules.Add(TestData.DogeCoinPlugin_module().identifier);
- CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, NullUser.User).UninstallList(modules);
+ CKAN.ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, nullUser).UninstallList(modules);
// Check that the directory has been deleted.
Assert.IsFalse(Directory.Exists(directoryPath));
@@ -607,7 +611,7 @@ public void ModuleManagerInstancesAreDecoupled()
// Attempt to install it.
List modules = new List { TestData.DogeCoinFlag_101_module().identifier };
- CKAN.ModuleInstaller.GetInstance(ksp.KSP, manager.Cache, NullUser.User).InstallList(modules, new RelationshipResolverOptions());
+ CKAN.ModuleInstaller.GetInstance(ksp.KSP, manager.Cache, nullUser).InstallList(modules, new RelationshipResolverOptions());
// Check that the module is installed.
string mod_file_path = Path.Combine(ksp.KSP.GameData(), mod_file_name);
diff --git a/Tests/Core/ModuleInstallerDirTest.cs b/Tests/Core/ModuleInstallerDirTest.cs
index 08eff847ce..6cd28bd691 100644
--- a/Tests/Core/ModuleInstallerDirTest.cs
+++ b/Tests/Core/ModuleInstallerDirTest.cs
@@ -23,6 +23,7 @@ public class ModuleInstallerDirTest
private CKAN.ModuleInstaller _installer;
private CkanModule _testModule;
private string _gameDataDir;
+ private IUser _nullUser;
///
/// Prep environment by setting up a single mod in
@@ -33,10 +34,12 @@ public void SetUp()
{
_testModule = TestData.DogeCoinFlag_101_module();
- _manager = new KSPManager(NullUser.User);
+ _nullUser = new NullUser();
+
+ _manager = new KSPManager(_nullUser);
_instance = new DisposableKSP();
_registry = CKAN.RegistryManager.Instance(_instance.KSP).registry;
- _installer = CKAN.ModuleInstaller.GetInstance(_instance.KSP, _manager.Cache, NullUser.User);
+ _installer = CKAN.ModuleInstaller.GetInstance(_instance.KSP, _manager.Cache, _nullUser);
_gameDataDir = _instance.KSP.GameData();
_registry.AddAvailable(_testModule);
diff --git a/Tests/Data/DisposableKSP.cs b/Tests/Data/DisposableKSP.cs
index 47ba2b84fd..42bd00cacb 100644
--- a/Tests/Data/DisposableKSP.cs
+++ b/Tests/Data/DisposableKSP.cs
@@ -38,7 +38,7 @@ public DisposableKSP(string directoryToClone = null, string registryFile = null)
File.Copy(registryFile, registryPath, true);
}
- KSP = new KSP(_disposableDir, "disposable", NullUser.User);
+ KSP = new KSP(_disposableDir, "disposable", new NullUser());
Logging.Initialize();
}