diff --git a/CHANGELOG.md b/CHANGELOG.md
index 08851ef3c3..d636d6a4d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Features
- [GUI] Replace New in repository filter with Newly compatible filter (#2494 by: HebaruSan; reviewed: Olympic1, politas)
+- [GUI] Add Install Date column to GUI mod list (#2514 by: HebaruSan; reviewed: politas)
### Bugfixes
diff --git a/GUI/GUIMod.cs b/GUI/GUIMod.cs
index 461847e9ad..ecdd50b6b7 100644
--- a/GUI/GUIMod.cs
+++ b/GUI/GUIMod.cs
@@ -11,17 +11,14 @@ public sealed class GUIMod
{
private CkanModule Mod { get; set; }
- public string Name
- {
- get { return Mod.name.Trim(); }
- }
-
+ public string Name { get; private set; }
public bool IsInstalled { get; private set; }
public bool HasUpdate { get; private set; }
public bool IsIncompatible { get; private set; }
public bool IsAutodetected { get; private set; }
public string Authors { get; private set; }
public string InstalledVersion { get; private set; }
+ public DateTime? InstallDate { get; private set; }
public string LatestVersion { get; private set; }
public string DownloadSize { get; private set; }
public bool IsCached { get; private set; }
@@ -32,7 +29,6 @@ public string Name
public string KSPCompatibility { get; private set; }
public string KSPCompatibilityLong { get; private set; }
- public string KSPversion { get; private set; }
public string Abstract { get; private set; }
public string Homepage { get; private set; }
public string Identifier { get; private set; }
@@ -65,36 +61,82 @@ public string Version
get { return IsInstalled ? InstalledVersion : LatestVersion; }
}
+ ///
+ /// Initialize a GUIMod based on an InstalledModule
+ ///
+ /// The installed module to represent
+ /// CKAN registry object for current game instance
+ /// Current game version
+ /// If true, mark this module as incompatible
+ public GUIMod(InstalledModule instMod, IRegistryQuerier registry, KspVersionCriteria current_ksp_version, bool incompatible = false)
+ : this(instMod.Module, registry, current_ksp_version, incompatible)
+ {
+ IsInstalled = true;
+ IsInstallChecked = true;
+ InstallDate = instMod.InstallTime;
+ InstalledVersion = instMod.Module.version.ToString();
+ if (LatestVersion == null || LatestVersion.Equals("-"))
+ {
+ LatestVersion = InstalledVersion;
+ }
+ }
+
+ ///
+ /// Initialize a GUIMod based on a CkanModule
+ ///
+ /// The module to represent
+ /// CKAN registry object for current game instance
+ /// Current game version
+ /// If true, mark this module as incompatible
public GUIMod(CkanModule mod, IRegistryQuerier registry, KspVersionCriteria current_ksp_version, bool incompatible = false)
+ : this(mod.identifier, registry, current_ksp_version, incompatible)
{
- IsCKAN = mod is CkanModule;
- //Currently anything which could alter these causes a full reload of the modlist
- // If this is ever changed these could be moved into the properties
- Mod = mod;
- IsInstalled = registry.IsInstalled(mod.identifier, false);
- IsInstallChecked = IsInstalled;
- HasUpdate = registry.HasUpdate(mod.identifier, current_ksp_version);
- IsIncompatible = incompatible || !mod.IsCompatibleKSP(current_ksp_version);
- IsAutodetected = registry.IsAutodetected(mod.identifier);
- Authors = mod.author == null ? "N/A" : String.Join(",", mod.author);
-
- var installed_version = registry.InstalledVersion(mod.identifier);
- ModuleVersion latest_version = null;
- var ksp_version = mod.ksp_version;
+ Mod = mod;
+ IsCKAN = mod is CkanModule;
+
+ Name = mod.name.Trim();
+ Abstract = mod.@abstract.Trim();
+ Abbrevation = new string(Name.Split(' ').Where(s => s.Length > 0).Select(s => s[0]).ToArray());
+ Authors = mod.author == null ? "N/A" : String.Join(",", mod.author);
+ HasUpdate = registry.HasUpdate(mod.identifier, current_ksp_version);
+ DownloadSize = mod.download_size == 0 ? "N/A" : CkanModule.FmtSize(mod.download_size);
+ IsIncompatible = IsIncompatible || !mod.IsCompatibleKSP(current_ksp_version);
+
+ if (mod.resources != null)
+ {
+ Homepage = mod.resources.homepage?.ToString()
+ ?? mod.resources.spacedock?.ToString()
+ ?? mod.resources.curse?.ToString()
+ ?? mod.resources.repository?.ToString()
+ ?? "N/A";
+ }
+
+ UpdateIsCached();
+ }
+
+ ///
+ /// Initialize a GUIMod based on just an identifier
+ ///
+ /// The id of the module to represent
+ /// CKAN registry object for current game instance
+ /// Current game version
+ /// If true, mark this module as incompatible
+ public GUIMod(string identifier, IRegistryQuerier registry, KspVersionCriteria current_ksp_version, bool incompatible = false)
+ {
+ Identifier = identifier;
+ IsIncompatible = incompatible;
+ IsAutodetected = registry.IsAutodetected(identifier);
+
+ ModuleVersion latest_version = null;
try
{
- var latest_available = registry.LatestAvailable(mod.identifier, current_ksp_version);
- if (latest_available != null)
- latest_version = latest_available.version;
+ latest_version = registry.LatestAvailable(identifier, current_ksp_version)?.version;
}
catch (ModuleNotFoundKraken)
{
- latest_version = installed_version;
}
- InstalledVersion = installed_version != null ? installed_version.ToString() : "-";
-
// Let's try to find the compatibility for this mod. If it's not in the registry at
// all (because it's a DarkKAN mod) then this might fail.
@@ -102,35 +144,18 @@ public GUIMod(CkanModule mod, IRegistryQuerier registry, KspVersionCriteria curr
try
{
- latest_available_for_any_ksp = registry.LatestAvailable(mod.identifier, null);
+ latest_available_for_any_ksp = registry.LatestAvailable(identifier, null);
}
catch
- {
- // If we can't find the mod in the CKAN, but we've a CkanModule installed, then
- // use that.
- if (IsCKAN)
- latest_available_for_any_ksp = (CkanModule) mod;
- }
+ { }
// If there's known information for this mod in any form, calculate the highest compatible
// KSP.
if (latest_available_for_any_ksp != null)
{
- KSPCompatibility = registry.LatestCompatibleKSP(mod.identifier)?.ToYalovString()
+ KSPCompatibility = registry.LatestCompatibleKSP(identifier)?.ToYalovString()
?? "Unknown";
-
- // If the mod we have installed is *not* the mod we have installed, or we don't know
- // what we have installed, indicate that an upgrade would be needed.
- if (installed_version == null || !latest_available_for_any_ksp.version.IsEqualTo(installed_version))
- {
- KSPCompatibilityLong = string.Format("{0} (using mod version {1})",
- KSPCompatibility, latest_available_for_any_ksp.version);
-
- }
- else
- {
- KSPCompatibilityLong = KSPCompatibility;
- }
+ KSPCompatibilityLong = $"{KSPCompatibility} (using mod version {latest_available_for_any_ksp.version})";
}
else
{
@@ -151,43 +176,9 @@ public GUIMod(CkanModule mod, IRegistryQuerier registry, KspVersionCriteria curr
LatestVersion = "-";
}
- KSPversion = ksp_version != null ? ksp_version.ToString() : "-";
-
- Abstract = mod.@abstract;
-
// If we have a homepage provided, use that; otherwise use the spacedock page, curse page or the github repo so that users have somewhere to get more info than just the abstract.
Homepage = "N/A";
- if (mod.resources != null)
- {
- if (mod.resources.homepage != null)
- {
- Homepage = mod.resources.homepage.ToString();
- }
- else if (mod.resources.spacedock != null)
- {
- Homepage = mod.resources.spacedock.ToString();
- }
- else if (mod.resources.curse != null)
- {
- Homepage = mod.resources.curse.ToString();
- }
- else if (mod.resources.repository != null)
- {
- Homepage = mod.resources.repository.ToString();
- }
- }
-
- Identifier = mod.identifier;
-
- DownloadSize = (mod.download_size == 0)
- ? "N/A"
- : CkanModule.FmtSize(mod.download_size);
-
- Abbrevation = new string(mod.name.Split(' ').
- Where(s => s.Length > 0).Select(s => s[0]).ToArray());
-
- UpdateIsCached();
}
public void UpdateIsCached()
diff --git a/GUI/Main.Designer.cs b/GUI/Main.Designer.cs
index 9dccb6d63d..2e53872269 100644
--- a/GUI/Main.Designer.cs
+++ b/GUI/Main.Designer.cs
@@ -74,6 +74,7 @@ private void InitializeComponent()
this.LatestVersion = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.KSPCompatibility = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.SizeCol = new System.Windows.Forms.DataGridViewTextBoxColumn();
+ this.InstallDate = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Description = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.ModListContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components);
this.reinstallToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -481,6 +482,7 @@ private void InitializeComponent()
this.LatestVersion,
this.KSPCompatibility,
this.SizeCol,
+ this.InstallDate,
this.Description});
this.ModList.ContextMenuStrip = this.ModListContextMenuStrip;
this.ModList.Dock = System.Windows.Forms.DockStyle.Fill;
@@ -561,6 +563,14 @@ private void InitializeComponent()
this.SizeCol.ReadOnly = true;
this.SizeCol.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic;
//
+ // InstallDate
+ //
+ this.InstallDate.HeaderText = "Install Date";
+ this.InstallDate.Name = "InstallDate";
+ this.InstallDate.ReadOnly = true;
+ this.InstallDate.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic;
+ this.InstallDate.Width = 140;
+ //
// Description
//
this.Description.HeaderText = "Description";
@@ -1147,6 +1157,7 @@ private void InitializeComponent()
private System.Windows.Forms.DataGridViewTextBoxColumn LatestVersion;
private System.Windows.Forms.DataGridViewTextBoxColumn KSPCompatibility;
private System.Windows.Forms.DataGridViewTextBoxColumn SizeCol;
+ private System.Windows.Forms.DataGridViewTextBoxColumn InstallDate;
private System.Windows.Forms.DataGridViewTextBoxColumn Description;
private System.Windows.Forms.ContextMenuStrip ModListContextMenuStrip;
private System.Windows.Forms.ToolStripMenuItem reinstallToolStripMenuItem;
diff --git a/GUI/MainModList.cs b/GUI/MainModList.cs
index f8bd12ec46..a01f413494 100644
--- a/GUI/MainModList.cs
+++ b/GUI/MainModList.cs
@@ -21,14 +21,12 @@ private void UpdateFilters(Main control)
private IEnumerable _SortRowsByColumn(IEnumerable rows)
{
- // XXX: There should be a better way to identify checkbox columns than hardcoding their indices here
- if (this.configuration.SortByColumnIndex < 2)
+ switch (this.configuration.SortByColumnIndex)
{
- return Sort(rows, CheckboxSorter);
- }
- else if (this.configuration.SortByColumnIndex == 7)
- {
- return Sort(rows, DownloadSizeSorter);
+ // XXX: There should be a better way to identify checkbox columns than hardcoding their indices here
+ case 0: case 1: return Sort(rows, CheckboxSorter);
+ case 7: return Sort(rows, DownloadSizeSorter);
+ case 8: return Sort(rows, InstallDateSorter);
}
return Sort(rows, DefaultSorter);
}
@@ -87,6 +85,17 @@ private long DownloadSizeSorter(DataGridViewRow row)
return (row.Tag as GUIMod)?.ToCkanModule()?.download_size ?? 0;
}
+ ///
+ /// Transforms a DataGridViewRow into a long representing the install date,
+ /// suitable for sorting.
+ /// The grid's default on first click is ascending, and sorting uninstalled mods to
+ /// the top is kind of useless, so we'll make this negative so ascending is useful.
+ ///
+ private long InstallDateSorter(DataGridViewRow row)
+ {
+ return -(row.Tag as GUIMod)?.InstallDate?.Ticks ?? 0;
+ }
+
private void _UpdateFilters()
{
if (ModList == null) return;
@@ -139,20 +148,21 @@ private void _UpdateModsList(bool repo_updated, List mc)
KspVersionCriteria versionCriteria = CurrentInstance.VersionCriteria();
IRegistryQuerier registry = RegistryManager.Instance(CurrentInstance).registry;
- var gui_mods = new HashSet(registry.Available(versionCriteria)
- .Select(m => new GUIMod(m, registry, versionCriteria)));
- gui_mods.UnionWith(registry.Incompatible(versionCriteria)
- .Select(m => new GUIMod(m, registry, versionCriteria, true)));
- var installed = registry.InstalledModules
- .Select(m => new GUIMod(m.Module, registry, versionCriteria));
- //Hashset does not define if add/unionwith replaces existing elements.
- //In this case that could cause a CkanModule to be replaced by a Module.
- //Hence the explicit checking
- foreach (var mod in installed.Where(mod => !gui_mods.Contains(mod)))
- {
- gui_mods.Add(mod);
- }
+ var gui_mods = new HashSet();
+ gui_mods.UnionWith(
+ registry.InstalledModules
+ .Select(instMod => new GUIMod(instMod, registry, versionCriteria))
+ );
+ gui_mods.UnionWith(
+ registry.Available(versionCriteria)
+ .Select(m => new GUIMod(m, registry, versionCriteria))
+ );
+ gui_mods.UnionWith(
+ registry.Incompatible(versionCriteria)
+ .Select(m => new GUIMod(m, registry, versionCriteria, true))
+ );
+
var old_modules = mainModList.Modules.ToDictionary(m => m, m => m.IsIncompatible);
if (repo_updated)
{
@@ -696,63 +706,77 @@ public IEnumerable ConstructModList(IEnumerable modules
foreach (var mod in rowsToUpdate)
{
full_list_of_mod_rows.Remove(mod.Identifier);
- var item = new DataGridViewRow {Tag = mod};
-
- ModChange myChange = mc?.FindLast((ModChange ch) => ch.Mod.Identifier == mod.Identifier);
-
- var selecting = mod.IsInstallable()
- ? (DataGridViewCell) new DataGridViewCheckBoxCell() {
- Value = myChange == null ? mod.IsInstalled
- : myChange.ChangeType == GUIModChangeType.Install ? true
- : myChange.ChangeType == GUIModChangeType.Remove ? false
- : mod.IsInstalled
- } : new DataGridViewTextBoxCell() {
- Value = mod.IsAutodetected ? "AD" : "-"
- };
-
- var updating = mod.IsInstallable() && mod.HasUpdate
- ? (DataGridViewCell) new DataGridViewCheckBoxCell() {
- Value = myChange == null ? false
- : myChange.ChangeType == GUIModChangeType.Update ? true
- : false
- } : new DataGridViewTextBoxCell() {
- Value = "-"
- };
-
- var name = new DataGridViewTextBoxCell {Value = mod.Name};
- var author = new DataGridViewTextBoxCell {Value = mod.Authors};
-
- var installVersion = new DataGridViewTextBoxCell {
- Value =
- hideEpochs ?
- (hideV ? ModuleInstaller.StripEpoch(ModuleInstaller.StripV(mod.InstalledVersion))
- : ModuleInstaller.StripEpoch(mod.InstalledVersion))
- : (hideV ? ModuleInstaller.StripV(mod.InstalledVersion)
- : mod.InstalledVersion)
+ full_list_of_mod_rows.Add(mod.Identifier, MakeRow(mod, mc, hideEpochs, hideV));
+ }
+ return full_list_of_mod_rows.Values;
+ }
+
+ private DataGridViewRow MakeRow(GUIMod mod, List changes, bool hideEpochs = false, bool hideV = false)
+ {
+ DataGridViewRow item = new DataGridViewRow() {Tag = mod};
+
+ ModChange myChange = changes?.FindLast((ModChange ch) => ch.Mod.Identifier == mod.Identifier);
+
+ var selecting = mod.IsInstallable()
+ ? (DataGridViewCell) new DataGridViewCheckBoxCell()
+ {
+ Value = myChange == null ? mod.IsInstalled
+ : myChange.ChangeType == GUIModChangeType.Install ? true
+ : myChange.ChangeType == GUIModChangeType.Remove ? false
+ : mod.IsInstalled
+ }
+ : new DataGridViewTextBoxCell()
+ {
+ Value = mod.IsAutodetected ? "AD" : "-"
};
- var latestVersion = new DataGridViewTextBoxCell
+ var updating = mod.IsInstallable() && mod.HasUpdate
+ ? (DataGridViewCell) new DataGridViewCheckBoxCell()
+ {
+ Value = myChange == null ? false
+ : myChange.ChangeType == GUIModChangeType.Update ? true
+ : false
+ }
+ : new DataGridViewTextBoxCell()
{
- Value =
- hideEpochs ?
- (hideV ? ModuleInstaller.StripEpoch(ModuleInstaller.StripV(mod.LatestVersion))
- : ModuleInstaller.StripEpoch(mod.LatestVersion))
- : (hideV ? ModuleInstaller.StripV(mod.LatestVersion)
- : mod.LatestVersion)
+ Value = "-"
};
- var desc = new DataGridViewTextBoxCell {Value = mod.Abstract};
- var compat = new DataGridViewTextBoxCell {Value = mod.KSPCompatibility};
- var size = new DataGridViewTextBoxCell {Value = mod.DownloadSize};
+ var name = new DataGridViewTextBoxCell() {Value = mod.Name};
+ var author = new DataGridViewTextBoxCell() {Value = mod.Authors};
+
+ var installVersion = new DataGridViewTextBoxCell()
+ {
+ Value = hideEpochs
+ ? (hideV
+ ? ModuleInstaller.StripEpoch(ModuleInstaller.StripV(mod.InstalledVersion ?? ""))
+ : ModuleInstaller.StripEpoch(mod.InstalledVersion ?? ""))
+ : (hideV
+ ? ModuleInstaller.StripV(mod.InstalledVersion ?? "")
+ : mod.InstalledVersion ?? "")
+ };
- item.Cells.AddRange(selecting, updating, name, author, installVersion, latestVersion, compat, size, desc);
+ var latestVersion = new DataGridViewTextBoxCell()
+ {
+ Value =
+ hideEpochs ?
+ (hideV ? ModuleInstaller.StripEpoch(ModuleInstaller.StripV(mod.LatestVersion))
+ : ModuleInstaller.StripEpoch(mod.LatestVersion))
+ : (hideV ? ModuleInstaller.StripV(mod.LatestVersion)
+ : mod.LatestVersion)
+ };
- selecting.ReadOnly = selecting is DataGridViewTextBoxCell;
- updating.ReadOnly = updating is DataGridViewTextBoxCell;
+ var compat = new DataGridViewTextBoxCell() { Value = mod.KSPCompatibility };
+ var size = new DataGridViewTextBoxCell() { Value = mod.DownloadSize };
+ var installDate = new DataGridViewTextBoxCell() { Value = mod.InstallDate };
+ var desc = new DataGridViewTextBoxCell() { Value = mod.Abstract };
- full_list_of_mod_rows.Add(mod.Identifier, item);
- }
- return full_list_of_mod_rows.Values;
+ item.Cells.AddRange(selecting, updating, name, author, installVersion, latestVersion, compat, size, installDate, desc);
+
+ selecting.ReadOnly = selecting is DataGridViewTextBoxCell;
+ updating.ReadOnly = updating is DataGridViewTextBoxCell;
+
+ return item;
}
private bool IsNameInNameFilter(GUIMod mod)