From 3537cb0f99ae59f04b6770d92f6ce3cec57fd2c6 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 29 Sep 2024 10:17:47 -0500 Subject: [PATCH 1/9] Check reinstall checkboxes via Update all --- GUI/Controls/ManageMods.cs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/GUI/Controls/ManageMods.cs b/GUI/Controls/ManageMods.cs index 80cb6207c..2353fb604 100644 --- a/GUI/Controls/ManageMods.cs +++ b/GUI/Controls/ManageMods.cs @@ -538,19 +538,18 @@ public void MarkAllUpdates() { WithFrozenChangeset(() => { - foreach (var gmod in mainModList.full_list_of_mod_rows - .Values - .Select(row => row.Tag) - .OfType()) - { - if (gmod?.HasUpdate ?? false) - { - if (!Main.Instance?.LabelsHeld(gmod.Identifier) ?? false) - { - gmod.SelectedMod = gmod.LatestCompatibleMod; - } - } - } + var checkboxes = mainModList.full_list_of_mod_rows + .Values + .Where(row => row.Tag is GUIMod {Identifier: string ident} + && (!Main.Instance?.LabelsHeld(ident) ?? false)) + .SelectWithCatch(row => row.Cells[UpdateCol.Index], + (row, exc) => null) + .OfType(); + foreach (var checkbox in checkboxes) + { + checkbox.Value = true; + } + ModGrid.CommitEdit(DataGridViewDataErrorContexts.Commit); // only sort by Update column if checkbox in settings checked if (guiConfig?.AutoSortByUpdate ?? false) From 7d8fc7aaa928722106e4694aae786a99b86f2a1d Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 29 Sep 2024 10:18:03 -0500 Subject: [PATCH 2/9] Recalculate changeset on unchecking update checkbox --- GUI/Controls/ManageMods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GUI/Controls/ManageMods.cs b/GUI/Controls/ManageMods.cs index 2353fb604..755c8f1e6 100644 --- a/GUI/Controls/ManageMods.cs +++ b/GUI/Controls/ManageMods.cs @@ -934,7 +934,7 @@ private void ModGrid_CellValueChanged(object? sender, DataGridViewCellEventArgs? : gmod.LatestCompatibleMod : gmod.InstalledMod?.Module; - if (nowChecked && gmod.SelectedMod == gmod.LatestCompatibleMod) + if (gmod.SelectedMod == gmod.LatestCompatibleMod) { // Reinstall, force update without change UpdateChangeSetAndConflicts(currentInstance, From 71f3b3209815fdeb1a6c996eb0bdee8a25271775 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 29 Sep 2024 10:58:47 -0500 Subject: [PATCH 3/9] Don't treat filtered files as missing --- Core/Registry/IRegistryQuerier.cs | 24 ++++++++++++++++++++---- GUI/Model/ModList.cs | 12 ++++++------ Tests/Core/Registry/Registry.cs | 4 ++-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/Core/Registry/IRegistryQuerier.cs b/Core/Registry/IRegistryQuerier.cs index c8e34ad6f..cbd157bd5 100644 --- a/Core/Registry/IRegistryQuerier.cs +++ b/Core/Registry/IRegistryQuerier.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; +using Autofac; using log4net; +using CKAN.Configuration; using CKAN.Versioning; using CKAN.Games; #if NETSTANDARD2_0 @@ -174,6 +176,7 @@ public static bool IsAutodetected(this IRegistryQuerier querier, string identifi public static bool HasUpdate(this IRegistryQuerier querier, string identifier, GameInstance? instance, + HashSet filters, out CkanModule? latestMod, ICollection? installed = null) { @@ -205,6 +208,8 @@ public static bool HasUpdate(this IRegistryQuerier querier, && (instance == null || (querier.InstalledModule(identifier) ?.Files + // Don't make them reinstall files they've filtered out since installing + .Where(f => !filters.Any(filt => f.Contains(filt))) .Select(instance.ToAbsoluteGameDir) .All(p => Directory.Exists(p) || File.Exists(p)) // Manually installed, consider up to date @@ -224,12 +229,17 @@ public static Dictionary> CheckUpgradeable(this IRegistry GameInstance? instance, HashSet heldIdents) { + var filters = ServiceLocator.Container.Resolve() + .GlobalInstallFilters + .Concat(instance?.InstallFilters + ?? Enumerable.Empty()) + .ToHashSet(); // Get the absolute latest versions ignoring restrictions, // to break out of mutual version-depending deadlocks var unlimited = querier.Installed(false) .Keys .Select(ident => !heldIdents.Contains(ident) - && querier.HasUpdate(ident, instance, + && querier.HasUpdate(ident, instance, filters, out CkanModule? latest) && latest is not null && !latest.IsDLC @@ -237,21 +247,27 @@ public static Dictionary> CheckUpgradeable(this IRegistry : querier.GetInstalledVersion(ident)) .OfType() .ToList(); - return querier.CheckUpgradeable(instance, heldIdents, unlimited); + return querier.CheckUpgradeable(instance, heldIdents, unlimited, filters); } public static Dictionary> CheckUpgradeable(this IRegistryQuerier querier, GameInstance? instance, HashSet heldIdents, - List initial) + List initial, + HashSet? filters = null) { + filters ??= ServiceLocator.Container.Resolve() + .GlobalInstallFilters + .Concat(instance?.InstallFilters + ?? Enumerable.Empty()) + .ToHashSet(); // Use those as the installed modules var upgradeable = new List(); var notUpgradeable = new List(); foreach (var ident in initial.Select(module => module.identifier)) { if (!heldIdents.Contains(ident) - && querier.HasUpdate(ident, instance, + && querier.HasUpdate(ident, instance, filters, out CkanModule? latest, initial) && latest is not null && !latest.IsDLC) diff --git a/GUI/Model/ModList.cs b/GUI/Model/ModList.cs index be46fbf74..ac26d006e 100644 --- a/GUI/Model/ModList.cs +++ b/GUI/Model/ModList.cs @@ -583,12 +583,12 @@ public IEnumerable GetGUIMods(IRegistryQuerier registry, config?.HideEpochs ?? false, config?.HideV ?? false); private static IEnumerable GetGUIMods(IRegistryQuerier registry, - RepositoryDataManager repoData, - GameInstance inst, - GameVersionCriteria versionCriteria, - HashSet installedIdents, - bool hideEpochs, - bool hideV) + RepositoryDataManager repoData, + GameInstance inst, + GameVersionCriteria versionCriteria, + HashSet installedIdents, + bool hideEpochs, + bool hideV) => registry.CheckUpgradeable(inst, ModuleLabelList.ModuleLabels.HeldIdentifiers(inst) .ToHashSet()) diff --git a/Tests/Core/Registry/Registry.cs b/Tests/Core/Registry/Registry.cs index 277f76352..cd6aa9958 100644 --- a/Tests/Core/Registry/Registry.cs +++ b/Tests/Core/Registry/Registry.cs @@ -271,7 +271,7 @@ public void HasUpdate_WithUpgradeableManuallyInstalledMod_ReturnsTrue() }); // Act - bool has = registry.HasUpdate(mod.identifier, gameInst, out _); + bool has = registry.HasUpdate(mod.identifier, gameInst, new HashSet(), out _); // Assert Assert.IsTrue(has, "Can't upgrade manually installed DLL"); @@ -327,7 +327,7 @@ public void HasUpdate_OtherModDependsOnCurrent_ReturnsFalse() GameVersionCriteria crit = new GameVersionCriteria(olderDepMod?.ksp_version); // Act - bool has = registry.HasUpdate(olderDepMod?.identifier!, gameInst, out _, + bool has = registry.HasUpdate(olderDepMod?.identifier!, gameInst, new HashSet(), out _, registry.InstalledModules .Select(im => im.Module) .ToList()); From f1f06d317647f3cff77e406f5e3bcdc7a2e605b3 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 29 Sep 2024 15:06:32 -0500 Subject: [PATCH 4/9] Ignore missing files option for labels --- Core/Registry/IRegistryQuerier.cs | 14 +++++++--- GUI/Controls/ManageMods.cs | 17 +++++++----- GUI/Dialogs/EditLabelsDialog.Designer.cs | 34 +++++++++++++++++++---- GUI/Dialogs/EditLabelsDialog.cs | 35 +++++++++++++----------- GUI/Dialogs/EditLabelsDialog.resx | 1 + GUI/Labels/ModuleLabel.cs | 4 +++ GUI/Labels/ModuleLabelList.cs | 5 ++++ GUI/Main/MainLabels.cs | 5 ++++ GUI/Model/ModList.cs | 11 ++++++-- GUI/Properties/Resources.resx | 1 + Tests/Core/Registry/Registry.cs | 5 ++-- 11 files changed, 96 insertions(+), 36 deletions(-) diff --git a/Core/Registry/IRegistryQuerier.cs b/Core/Registry/IRegistryQuerier.cs index cbd157bd5..00d723432 100644 --- a/Core/Registry/IRegistryQuerier.cs +++ b/Core/Registry/IRegistryQuerier.cs @@ -177,6 +177,7 @@ public static bool HasUpdate(this IRegistryQuerier querier, string identifier, GameInstance? instance, HashSet filters, + bool checkMissingFiles, out CkanModule? latestMod, ICollection? installed = null) { @@ -205,7 +206,8 @@ public static bool HasUpdate(this IRegistryQuerier querier, if (comp == -1 || (comp == 0 && !querier.MetadataChanged(identifier) // Check if any of the files or directories are missing - && (instance == null + && (!checkMissingFiles + || instance == null || (querier.InstalledModule(identifier) ?.Files // Don't make them reinstall files they've filtered out since installing @@ -227,7 +229,8 @@ public static bool HasUpdate(this IRegistryQuerier querier, public static Dictionary> CheckUpgradeable(this IRegistryQuerier querier, GameInstance? instance, - HashSet heldIdents) + HashSet heldIdents, + HashSet? ignoreMissingIdents = null) { var filters = ServiceLocator.Container.Resolve() .GlobalInstallFilters @@ -240,6 +243,7 @@ public static Dictionary> CheckUpgradeable(this IRegistry .Keys .Select(ident => !heldIdents.Contains(ident) && querier.HasUpdate(ident, instance, filters, + !ignoreMissingIdents?.Contains(ident) ?? true, out CkanModule? latest) && latest is not null && !latest.IsDLC @@ -247,14 +251,15 @@ public static Dictionary> CheckUpgradeable(this IRegistry : querier.GetInstalledVersion(ident)) .OfType() .ToList(); - return querier.CheckUpgradeable(instance, heldIdents, unlimited, filters); + return querier.CheckUpgradeable(instance, heldIdents, unlimited, filters, ignoreMissingIdents); } public static Dictionary> CheckUpgradeable(this IRegistryQuerier querier, GameInstance? instance, HashSet heldIdents, List initial, - HashSet? filters = null) + HashSet? filters = null, + HashSet? ignoreMissingIdents = null) { filters ??= ServiceLocator.Container.Resolve() .GlobalInstallFilters @@ -268,6 +273,7 @@ public static Dictionary> CheckUpgradeable(this IRegistry { if (!heldIdents.Contains(ident) && querier.HasUpdate(ident, instance, filters, + !ignoreMissingIdents?.Contains(ident) ?? true, out CkanModule? latest, initial) && latest is not null && !latest.IsDLC) diff --git a/GUI/Controls/ManageMods.cs b/GUI/Controls/ManageMods.cs index 755c8f1e6..d6fca7c0c 100644 --- a/GUI/Controls/ManageMods.cs +++ b/GUI/Controls/ManageMods.cs @@ -335,24 +335,27 @@ private void LabelsContextMenuStrip_Opening(object? sender, CancelEventArgs? e) private void labelMenuItem_Click(object? sender, EventArgs? e) { - if (user != null && manager != null && currentInstance != null && SelectedModule != null) + if (user != null + && manager != null + && currentInstance != null + && SelectedModule != null + && sender is ToolStripMenuItem item + && item.Tag is ModuleLabel mlbl) { - var item = sender as ToolStripMenuItem; - var mlbl = item?.Tag as ModuleLabel; - if (item?.Checked ?? false) + if (item.Checked) { - mlbl?.Add(currentInstance.game, SelectedModule.Identifier); + mlbl.Add(currentInstance.game, SelectedModule.Identifier); } else { - mlbl?.Remove(currentInstance.game, SelectedModule.Identifier); + mlbl.Remove(currentInstance.game, SelectedModule.Identifier); } var registry = RegistryManager.Instance(currentInstance, repoData).registry; mainModList.ReapplyLabels(SelectedModule, Conflicts?.ContainsKey(SelectedModule) ?? false, currentInstance.Name, currentInstance.game, registry); ModuleLabelList.ModuleLabels.Save(ModuleLabelList.DefaultPath); UpdateHiddenTagsAndLabels(); - if (mlbl?.HoldVersion ?? false) + if (mlbl.HoldVersion || mlbl.IgnoreMissingFiles) { UpdateCol.Visible = UpdateAllToolButton.Enabled = mainModList.ResetHasUpdate(currentInstance, registry, ChangeSet, ModGrid.Rows); diff --git a/GUI/Dialogs/EditLabelsDialog.Designer.cs b/GUI/Dialogs/EditLabelsDialog.Designer.cs index f82ad13a9..415d3c4a5 100644 --- a/GUI/Dialogs/EditLabelsDialog.Designer.cs +++ b/GUI/Dialogs/EditLabelsDialog.Designer.cs @@ -46,6 +46,7 @@ private void InitializeComponent() this.AlertOnInstallCheckBox = new System.Windows.Forms.CheckBox(); this.RemoveOnInstallCheckBox = new System.Windows.Forms.CheckBox(); this.HoldVersionCheckBox = new System.Windows.Forms.CheckBox(); + this.IgnoreMissingFilesCheckBox = new System.Windows.Forms.CheckBox(); this.CreateButton = new System.Windows.Forms.Button(); this.CloseButton = new System.Windows.Forms.Button(); this.SaveButton = new System.Windows.Forms.Button(); @@ -67,6 +68,8 @@ private void InitializeComponent() // this.CreateButton.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)); + this.CreateButton.AutoSize = true; + this.CreateButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; this.CreateButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.CreateButton.Location = new System.Drawing.Point(10, 10); this.CreateButton.Name = "CreateButton"; @@ -121,6 +124,7 @@ private void InitializeComponent() this.EditDetailsPanel.Controls.Add(this.AlertOnInstallCheckBox); this.EditDetailsPanel.Controls.Add(this.RemoveOnInstallCheckBox); this.EditDetailsPanel.Controls.Add(this.HoldVersionCheckBox); + this.EditDetailsPanel.Controls.Add(this.IgnoreMissingFilesCheckBox); this.EditDetailsPanel.Controls.Add(this.SaveButton); this.EditDetailsPanel.Controls.Add(this.CancelEditButton); this.EditDetailsPanel.Controls.Add(this.DeleteButton); @@ -192,6 +196,8 @@ private void InitializeComponent() // this.ColorButton.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)); + this.ColorButton.AutoSize = true; + this.ColorButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; this.ColorButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.ColorButton.Location = new System.Drawing.Point(118, 40); this.ColorButton.Name = "ColorButton"; @@ -231,7 +237,7 @@ private void InitializeComponent() // this.NotifyOnChangesCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)); this.NotifyOnChangesCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.NotifyOnChangesCheckBox.Location = new System.Drawing.Point(118, 130); + this.NotifyOnChangesCheckBox.Location = new System.Drawing.Point(118, 124); this.NotifyOnChangesCheckBox.Name = "NotifyOnChangesCheckBox"; this.NotifyOnChangesCheckBox.Size = new System.Drawing.Size(200, 23); resources.ApplyResources(this.NotifyOnChangesCheckBox, "NotifyOnChangesCheckBox"); @@ -240,7 +246,7 @@ private void InitializeComponent() // this.RemoveOnChangesCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)); this.RemoveOnChangesCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.RemoveOnChangesCheckBox.Location = new System.Drawing.Point(118, 160); + this.RemoveOnChangesCheckBox.Location = new System.Drawing.Point(118, 148); this.RemoveOnChangesCheckBox.Name = "RemoveOnChangesCheckBox"; this.RemoveOnChangesCheckBox.Size = new System.Drawing.Size(200, 23); resources.ApplyResources(this.RemoveOnChangesCheckBox, "RemoveOnChangesCheckBox"); @@ -249,7 +255,7 @@ private void InitializeComponent() // this.AlertOnInstallCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)); this.AlertOnInstallCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.AlertOnInstallCheckBox.Location = new System.Drawing.Point(118, 190); + this.AlertOnInstallCheckBox.Location = new System.Drawing.Point(118, 172); this.AlertOnInstallCheckBox.Name = "AlertOnInstallCheckBox"; this.AlertOnInstallCheckBox.Size = new System.Drawing.Size(200, 23); resources.ApplyResources(this.AlertOnInstallCheckBox, "AlertOnInstallCheckBox"); @@ -258,7 +264,7 @@ private void InitializeComponent() // this.RemoveOnInstallCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)); this.RemoveOnInstallCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.RemoveOnInstallCheckBox.Location = new System.Drawing.Point(118, 220); + this.RemoveOnInstallCheckBox.Location = new System.Drawing.Point(118, 196); this.RemoveOnInstallCheckBox.Name = "RemoveOnInstallCheckBox"; this.RemoveOnInstallCheckBox.Size = new System.Drawing.Size(200, 23); resources.ApplyResources(this.RemoveOnInstallCheckBox, "RemoveOnInstallCheckBox"); @@ -267,15 +273,26 @@ private void InitializeComponent() // this.HoldVersionCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)); this.HoldVersionCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.HoldVersionCheckBox.Location = new System.Drawing.Point(118, 250); + this.HoldVersionCheckBox.Location = new System.Drawing.Point(118, 220); this.HoldVersionCheckBox.Name = "HoldVersionCheckBox"; this.HoldVersionCheckBox.Size = new System.Drawing.Size(200, 23); resources.ApplyResources(this.HoldVersionCheckBox, "HoldVersionCheckBox"); + // + // IgnoreMissingFilesCheckBox + // + this.IgnoreMissingFilesCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)); + this.IgnoreMissingFilesCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.IgnoreMissingFilesCheckBox.Location = new System.Drawing.Point(118, 244); + this.IgnoreMissingFilesCheckBox.Name = "IgnoreMissingFilesCheckBox"; + this.IgnoreMissingFilesCheckBox.Size = new System.Drawing.Size(200, 23); + resources.ApplyResources(this.IgnoreMissingFilesCheckBox, "IgnoreMissingFilesCheckBox"); // // SaveButton // this.SaveButton.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)); + this.SaveButton.AutoSize = true; + this.SaveButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; this.SaveButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.SaveButton.Location = new System.Drawing.Point(38, 320); this.SaveButton.Name = "SaveButton"; @@ -289,6 +306,8 @@ private void InitializeComponent() // this.CancelEditButton.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)); + this.CancelEditButton.AutoSize = true; + this.CancelEditButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; this.CancelEditButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.CancelEditButton.Location = new System.Drawing.Point(118, 320); this.CancelEditButton.Name = "CancelEditButton"; @@ -302,6 +321,8 @@ private void InitializeComponent() // this.DeleteButton.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)); + this.DeleteButton.AutoSize = true; + this.DeleteButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; this.DeleteButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.DeleteButton.Location = new System.Drawing.Point(198, 320); this.DeleteButton.Name = "DeleteButton"; @@ -315,6 +336,8 @@ private void InitializeComponent() // this.CloseButton.Anchor = ((System.Windows.Forms.AnchorStyles)(System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)); + this.CloseButton.AutoSize = true; + this.CloseButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; this.CloseButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.CloseButton.Location = new System.Drawing.Point(10, 397); this.CloseButton.Name = "CloseButton"; @@ -362,6 +385,7 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox AlertOnInstallCheckBox; private System.Windows.Forms.CheckBox RemoveOnInstallCheckBox; private System.Windows.Forms.CheckBox HoldVersionCheckBox; + private System.Windows.Forms.CheckBox IgnoreMissingFilesCheckBox; private System.Windows.Forms.Label ColorLabel; private System.Windows.Forms.Button ColorButton; private System.Windows.Forms.Button CreateButton; diff --git a/GUI/Dialogs/EditLabelsDialog.cs b/GUI/Dialogs/EditLabelsDialog.cs index f791a148b..9867ddcb1 100644 --- a/GUI/Dialogs/EditLabelsDialog.cs +++ b/GUI/Dialogs/EditLabelsDialog.cs @@ -33,6 +33,7 @@ public EditLabelsDialog(IUser user, GameInstanceManager manager, ModuleLabelList ToolTip.SetToolTip(AlertOnInstallCheckBox, Properties.Resources.EditLabelsToolTipAlertOnInstall); ToolTip.SetToolTip(RemoveOnInstallCheckBox, Properties.Resources.EditLabelsToolTipRemoveOnInstall); ToolTip.SetToolTip(HoldVersionCheckBox, Properties.Resources.EditLabelsToolTipHoldVersion); + ToolTip.SetToolTip(IgnoreMissingFilesCheckBox, Properties.Resources.EditLabelsToolTipIgnoreMissingFiles); ToolTip.SetToolTip(MoveUpButton, Properties.Resources.EditLabelsToolTipMoveUp); ToolTip.SetToolTip(MoveDownButton, Properties.Resources.EditLabelsToolTipMoveDown); } @@ -204,6 +205,7 @@ private void StartEdit(ModuleLabel lbl) AlertOnInstallCheckBox.Checked = lbl.AlertOnInstall; RemoveOnInstallCheckBox.Checked = lbl.RemoveOnInstall; HoldVersionCheckBox.Checked = lbl.HoldVersion; + IgnoreMissingFilesCheckBox.Checked = lbl.IgnoreMissingFiles; DeleteButton.Enabled = labels.Labels.Contains(lbl); EnableDisableUpDownButtons(); @@ -300,12 +302,13 @@ private bool TrySave(out string errMsg) || string.IsNullOrWhiteSpace(InstanceNameComboBox.SelectedItem.ToString()) ? null : InstanceNameComboBox.SelectedItem.ToString(); - currentlyEditing.Hide = HideFromOtherFiltersCheckBox.Checked; - currentlyEditing.NotifyOnChange = NotifyOnChangesCheckBox.Checked; - currentlyEditing.RemoveOnChange = RemoveOnChangesCheckBox.Checked; - currentlyEditing.AlertOnInstall = AlertOnInstallCheckBox.Checked; - currentlyEditing.RemoveOnInstall = RemoveOnInstallCheckBox.Checked; - currentlyEditing.HoldVersion = HoldVersionCheckBox.Checked; + currentlyEditing.Hide = HideFromOtherFiltersCheckBox.Checked; + currentlyEditing.NotifyOnChange = NotifyOnChangesCheckBox.Checked; + currentlyEditing.RemoveOnChange = RemoveOnChangesCheckBox.Checked; + currentlyEditing.AlertOnInstall = AlertOnInstallCheckBox.Checked; + currentlyEditing.RemoveOnInstall = RemoveOnInstallCheckBox.Checked; + currentlyEditing.HoldVersion = HoldVersionCheckBox.Checked; + currentlyEditing.IgnoreMissingFiles = IgnoreMissingFilesCheckBox.Checked; if (!labels.Labels.Contains(currentlyEditing)) { labels.Labels = labels.Labels @@ -367,16 +370,16 @@ private bool HasChanges() ? null : InstanceNameComboBox.SelectedItem.ToString(); return EditDetailsPanel.Visible && currentlyEditing != null - && ( currentlyEditing.Name != NameTextBox.Text - || currentlyEditing.Color != ColorButton.BackColor - || currentlyEditing.InstanceName != newInst - || currentlyEditing.Hide != HideFromOtherFiltersCheckBox.Checked - || currentlyEditing.NotifyOnChange != NotifyOnChangesCheckBox.Checked - || currentlyEditing.RemoveOnChange != RemoveOnChangesCheckBox.Checked - || currentlyEditing.AlertOnInstall != AlertOnInstallCheckBox.Checked - || currentlyEditing.RemoveOnInstall != RemoveOnInstallCheckBox.Checked - || currentlyEditing.HoldVersion != HoldVersionCheckBox.Checked - ); + && ( currentlyEditing.Name != NameTextBox.Text + || currentlyEditing.Color != ColorButton.BackColor + || currentlyEditing.InstanceName != newInst + || currentlyEditing.Hide != HideFromOtherFiltersCheckBox.Checked + || currentlyEditing.NotifyOnChange != NotifyOnChangesCheckBox.Checked + || currentlyEditing.RemoveOnChange != RemoveOnChangesCheckBox.Checked + || currentlyEditing.AlertOnInstall != AlertOnInstallCheckBox.Checked + || currentlyEditing.RemoveOnInstall != RemoveOnInstallCheckBox.Checked + || currentlyEditing.HoldVersion != HoldVersionCheckBox.Checked + || currentlyEditing.IgnoreMissingFiles != IgnoreMissingFilesCheckBox.Checked); } private ModuleLabel? currentlyEditing; diff --git a/GUI/Dialogs/EditLabelsDialog.resx b/GUI/Dialogs/EditLabelsDialog.resx index 46082b6fb..d48c141b4 100644 --- a/GUI/Dialogs/EditLabelsDialog.resx +++ b/GUI/Dialogs/EditLabelsDialog.resx @@ -130,6 +130,7 @@ Alert on install Remove on install Don't upgrade + Ignore missing files Close Save Cancel diff --git a/GUI/Labels/ModuleLabel.cs b/GUI/Labels/ModuleLabel.cs index e8b1b154e..99e5ebfad 100644 --- a/GUI/Labels/ModuleLabel.cs +++ b/GUI/Labels/ModuleLabel.cs @@ -57,6 +57,10 @@ public ModuleLabel(string name) [DefaultValue(false)] public bool HoldVersion; + [JsonProperty("ignore_missing_files", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DefaultValue(false)] + public bool IgnoreMissingFiles; + [JsonProperty("module_identifiers_by_game", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(JsonToGamesDictionaryConverter))] private readonly Dictionary> ModuleIdentifiers = diff --git a/GUI/Labels/ModuleLabelList.cs b/GUI/Labels/ModuleLabelList.cs index 902d97c12..2bd084199 100644 --- a/GUI/Labels/ModuleLabelList.cs +++ b/GUI/Labels/ModuleLabelList.cs @@ -83,5 +83,10 @@ public IEnumerable HeldIdentifiers(GameInstance inst) => LabelsFor(inst.Name).Where(l => l.HoldVersion) .SelectMany(l => l.IdentifiersFor(inst.game)) .Distinct(); + + public IEnumerable IgnoreMissingIdentifiers(GameInstance inst) + => LabelsFor(inst.Name).Where(l => l.IgnoreMissingFiles) + .SelectMany(l => l.IdentifiersFor(inst.game)) + .Distinct(); } } diff --git a/GUI/Main/MainLabels.cs b/GUI/Main/MainLabels.cs index c08a2591e..f7089c261 100644 --- a/GUI/Main/MainLabels.cs +++ b/GUI/Main/MainLabels.cs @@ -68,6 +68,11 @@ public bool LabelsHeld(string identifier) && ModuleLabelList.ModuleLabels.LabelsFor(CurrentInstance.Name) .Any(l => l.HoldVersion && l.ContainsModule(CurrentInstance.game, identifier)); + public bool LabelsIgnoreMissing(string identifier) + => CurrentInstance != null + && ModuleLabelList.ModuleLabels.LabelsFor(CurrentInstance.Name) + .Any(l => l.IgnoreMissingFiles && l.ContainsModule(CurrentInstance.game, identifier)); + #endregion } } diff --git a/GUI/Model/ModList.cs b/GUI/Model/ModList.cs index ac26d006e..013135c45 100644 --- a/GUI/Model/ModList.cs +++ b/GUI/Model/ModList.cs @@ -470,14 +470,17 @@ public HashSet ComputeUserChangeSet(IRegistryQuerier registry, // Skip reinstalls .Where(upg => upg.Mod != upg.targetMod) .ToArray(); - if (upgrades.Length > 0) + if (upgrades.Length > 0 && instance != null) { var upgradeable = registry.CheckUpgradeable(instance, // Hold identifiers not chosen for upgrading registry.Installed(false) .Select(kvp => kvp.Key) .Except(upgrades.Select(ch => ch.Mod.identifier)) - .ToHashSet()) + .ToHashSet(), + ModuleLabelList.ModuleLabels + .IgnoreMissingIdentifiers(instance) + .ToHashSet()) [true] .ToDictionary(m => m.identifier, m => m); @@ -523,6 +526,8 @@ public bool ResetHasUpdate(GameInstance inst, { var upgGroups = registry.CheckUpgradeable(inst, ModuleLabelList.ModuleLabels.HeldIdentifiers(inst) + .ToHashSet(), + ModuleLabelList.ModuleLabels.IgnoreMissingIdentifiers(inst) .ToHashSet()); var dlls = registry.InstalledDlls.ToList(); foreach ((var upgradeable, var mods) in upgGroups) @@ -591,6 +596,8 @@ private static IEnumerable GetGUIMods(IRegistryQuerier registry, bool hideV) => registry.CheckUpgradeable(inst, ModuleLabelList.ModuleLabels.HeldIdentifiers(inst) + .ToHashSet(), + ModuleLabelList.ModuleLabels.IgnoreMissingIdentifiers(inst) .ToHashSet()) .SelectMany(kvp => kvp.Value .Select(mod => registry.IsAutodetected(mod.identifier) diff --git a/GUI/Properties/Resources.resx b/GUI/Properties/Resources.resx index 4a496cb43..deb956b23 100644 --- a/GUI/Properties/Resources.resx +++ b/GUI/Properties/Resources.resx @@ -417,6 +417,7 @@ Are you sure you want to skip this change? If checked, the change set screen will alert you if this mod is about to be installed If checked, modules will be removed from this label if they are installed If checked, modules will not be upgraded + If checked, you will not be prompted to re-install missing files for modules with this label Move up Move down Some of your watched mods have updated: diff --git a/Tests/Core/Registry/Registry.cs b/Tests/Core/Registry/Registry.cs index cd6aa9958..a590d8861 100644 --- a/Tests/Core/Registry/Registry.cs +++ b/Tests/Core/Registry/Registry.cs @@ -271,7 +271,7 @@ public void HasUpdate_WithUpgradeableManuallyInstalledMod_ReturnsTrue() }); // Act - bool has = registry.HasUpdate(mod.identifier, gameInst, new HashSet(), out _); + bool has = registry.HasUpdate(mod.identifier, gameInst, new HashSet(), false, out _); // Assert Assert.IsTrue(has, "Can't upgrade manually installed DLL"); @@ -327,7 +327,8 @@ public void HasUpdate_OtherModDependsOnCurrent_ReturnsFalse() GameVersionCriteria crit = new GameVersionCriteria(olderDepMod?.ksp_version); // Act - bool has = registry.HasUpdate(olderDepMod?.identifier!, gameInst, new HashSet(), out _, + bool has = registry.HasUpdate(olderDepMod?.identifier!, gameInst, new HashSet(), false, + out _, registry.InstalledModules .Select(im => im.Module) .ToList()); From 25a219044f45f6f9d344c0408078f8831c4ee294 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 29 Sep 2024 10:08:29 -0500 Subject: [PATCH 5/9] Installed then installable in Contents --- Core/Extensions/EnumerableExtensions.cs | 3 +- Core/ModuleInstaller.cs | 57 +++++++++++++++++++++---- Core/Registry/InstalledModule.cs | 8 ++-- GUI/Controls/ModInfoTabs/Contents.cs | 33 ++++++++------ 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/Core/Extensions/EnumerableExtensions.cs b/Core/Extensions/EnumerableExtensions.cs index 64a38025b..f5da93761 100644 --- a/Core/Extensions/EnumerableExtensions.cs +++ b/Core/Extensions/EnumerableExtensions.cs @@ -170,8 +170,9 @@ public static IEnumerable DistinctBy(this IEnumerable seq, FuncFunction to go from one node to the next /// All the nodes in the list as a sequence public static IEnumerable TraverseNodes(this T start, Func getNext) + where T : class { - for (T? t = start; t != null; t = getNext(t)) + for (T? t = start; t != null; t = Utilities.DefaultIfThrows(() => getNext(t))) { yield return t; } diff --git a/Core/ModuleInstaller.cs b/Core/ModuleInstaller.cs index de8d2047f..d440a00e3 100644 --- a/Core/ModuleInstaller.cs +++ b/Core/ModuleInstaller.cs @@ -582,23 +582,62 @@ public static List FindInstallableFiles(CkanModule module, stri } } + /// + /// Returns contents of an installed module + /// + public static IEnumerable<(string path, bool dir, bool exists)> GetModuleContents( + GameInstance instance, + IReadOnlyCollection installed, + HashSet filters) + => GetModuleContents(instance, installed, + installed.SelectMany(f => f.TraverseNodes(Path.GetDirectoryName) + .Skip(1) + .Where(s => s.Length > 0) + .Select(CKANPathUtils.NormalizePath)) + .ToHashSet(), + filters); + + private static IEnumerable<(string path, bool dir, bool exists)> GetModuleContents( + GameInstance instance, + IReadOnlyCollection installed, + HashSet parents, + HashSet filters) + => installed.Where(f => !filters.Any(filt => f.Contains(filt))) + .GroupBy(parents.Contains) + .SelectMany(grp => + grp.Select(p => (path: p, + dir: grp.Key, + exists: grp.Key ? Directory.Exists(instance.ToAbsoluteGameDir(p)) + : File.Exists(instance.ToAbsoluteGameDir(p))))); + /// /// Returns the module contents if and only if we have it /// available in our cache, empty sequence otherwise. /// /// Intended for previews. /// - public static IEnumerable GetModuleContents(NetModuleCache Cache, - GameInstance instance, - CkanModule module, - HashSet filters) + public static IEnumerable<(string path, bool dir, bool exists)> GetModuleContents( + NetModuleCache Cache, + GameInstance instance, + CkanModule module, + HashSet filters) => (Cache.GetCachedFilename(module) is string filename - ? Utilities.DefaultIfThrows(() => FindInstallableFiles(module, filename, instance) - .Where(instF => !filters.Any(filt => - instF.destination != null - && instF.destination.Contains(filt)))) + ? GetModuleContents(instance, + Utilities.DefaultIfThrows( + () => FindInstallableFiles(module, filename, instance)), + filters) : null) - ?? Enumerable.Empty(); + ?? Enumerable.Empty<(string path, bool dir, bool exists)>(); + + private static IEnumerable<(string path, bool dir, bool exists)>? GetModuleContents( + GameInstance instance, + IEnumerable? installable, + HashSet filters) + => installable?.Where(instF => !filters.Any(filt => instF.destination != null + && instF.destination.Contains(filt))) + .Select(f => (path: instance.ToRelativeGameDir(f.destination), + dir: f.source.IsDirectory, + exists: true)); #endregion diff --git a/Core/Registry/InstalledModule.cs b/Core/Registry/InstalledModule.cs index 883375438..fc1e546a1 100644 --- a/Core/Registry/InstalledModule.cs +++ b/Core/Registry/InstalledModule.cs @@ -45,10 +45,10 @@ public class InstalledModule [JsonProperty] private Dictionary installed_files; - public IEnumerable Files => installed_files.Keys; - public string identifier => source_module.identifier; - public CkanModule Module => source_module; - public DateTime InstallTime => install_time; + public IReadOnlyCollection Files => installed_files.Keys; + public string identifier => source_module.identifier; + public CkanModule Module => source_module; + public DateTime InstallTime => install_time; public bool AutoInstalled { diff --git a/GUI/Controls/ModInfoTabs/Contents.cs b/GUI/Controls/ModInfoTabs/Contents.cs index 898c87996..cd0a8bbb8 100644 --- a/GUI/Controls/ModInfoTabs/Contents.cs +++ b/GUI/Controls/ModInfoTabs/Contents.cs @@ -32,7 +32,8 @@ public GUIMod? SelectedModule if (value != selectedModule) { selectedModule = value; - Util.Invoke(ContentsPreviewTree, () => _UpdateModContentsTree(selectedModule?.ToModule())); + Util.Invoke(ContentsPreviewTree, () => _UpdateModContentsTree(selectedModule?.InstalledMod, + selectedModule?.ToModule())); } } get => selectedModule; @@ -41,9 +42,11 @@ public GUIMod? SelectedModule [ForbidGUICalls] public void RefreshModContentsTree() { - if (currentModContentsModule != null) + if (currentModContentsInstalledModule != null + || currentModContentsModule != null) { - Util.Invoke(ContentsPreviewTree, () => _UpdateModContentsTree(currentModContentsModule, true)); + Util.Invoke(ContentsPreviewTree, () => _UpdateModContentsTree(currentModContentsInstalledModule, + currentModContentsModule, true)); } } @@ -51,9 +54,10 @@ public void RefreshModContentsTree() private static GameInstanceManager? manager => Main.Instance?.Manager; - private GUIMod? selectedModule; - private CkanModule? currentModContentsModule; - private bool cancelExpandCollapse; + private GUIMod? selectedModule; + private InstalledModule? currentModContentsInstalledModule; + private CkanModule? currentModContentsModule; + private bool cancelExpandCollapse; private void ContentsPreviewTree_NodeMouseDoubleClick(object? sender, TreeNodeMouseClickEventArgs? e) { @@ -104,7 +108,8 @@ private void ContentsOpenButton_Click(object? sender, EventArgs? e) } } - private void _UpdateModContentsTree(CkanModule? module, bool force = false) + private void _UpdateModContentsTree(InstalledModule? instMod, CkanModule? module, + bool force = false) { if (module == null) { @@ -126,6 +131,7 @@ private void _UpdateModContentsTree(CkanModule? module, bool force = false) else { currentModContentsModule = module; + currentModContentsInstalledModule = instMod; } if (module.IsMetapackage) { @@ -165,13 +171,12 @@ private void _UpdateModContentsTree(CkanModule? module, bool force = false) var filters = ServiceLocator.Container.Resolve().GlobalInstallFilters .Concat(inst.InstallFilters) .ToHashSet(); - var tuples = ModuleInstaller.GetModuleContents(manager.Cache, inst, module, filters) - .Select(f => (path: inst.ToRelativeGameDir(f.destination), - dir: f.source.IsDirectory, - exists: !selectedModule.IsInstalled - || File.Exists(f.destination) - || Directory.Exists(f.destination))) - .ToArray(); + var tuples = (instMod != null + ? ModuleInstaller.GetModuleContents(inst, instMod.Files, filters) + : ModuleInstaller.GetModuleContents(manager.Cache, inst, + module, filters)) + // Load fully in bg + .ToArray(); // Stop if user switched to another mod if (rootNode.TreeView != null) { From 926052d097a285c3233670c068985ce9a4c5856b Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 29 Sep 2024 12:16:45 -0500 Subject: [PATCH 6/9] Alert icon for Contents tab when files are missing --- Core/Registry/IRegistryQuerier.cs | 7 +------ Core/Registry/InstalledModule.cs | 8 ++++++++ GUI/Controls/ModInfo.cs | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Core/Registry/IRegistryQuerier.cs b/Core/Registry/IRegistryQuerier.cs index 00d723432..7e31111d6 100644 --- a/Core/Registry/IRegistryQuerier.cs +++ b/Core/Registry/IRegistryQuerier.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -209,11 +208,7 @@ public static bool HasUpdate(this IRegistryQuerier querier, && (!checkMissingFiles || instance == null || (querier.InstalledModule(identifier) - ?.Files - // Don't make them reinstall files they've filtered out since installing - .Where(f => !filters.Any(filt => f.Contains(filt))) - .Select(instance.ToAbsoluteGameDir) - .All(p => Directory.Exists(p) || File.Exists(p)) + ?.AllFilesExist(instance, filters) // Manually installed, consider up to date ?? true)))) { diff --git a/Core/Registry/InstalledModule.cs b/Core/Registry/InstalledModule.cs index fc1e546a1..f6d23727b 100644 --- a/Core/Registry/InstalledModule.cs +++ b/Core/Registry/InstalledModule.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.ComponentModel; using System.Collections.Generic; using System.IO; @@ -132,6 +133,13 @@ public void Renormalise(GameInstance ksp) #endregion + public bool AllFilesExist(GameInstance instance, + HashSet filters) + // Don't make them reinstall files they've filtered out since installing + => Files.Where(f => !filters.Any(filt => f.Contains(filt))) + .Select(instance.ToAbsoluteGameDir) + .All(p => Directory.Exists(p) || File.Exists(p)); + public override string ToString() => string.Format(AutoInstalled ? Properties.Resources.InstalledModuleToStringAutoInstalled : Properties.Resources.InstalledModuleToString, diff --git a/GUI/Controls/ModInfo.cs b/GUI/Controls/ModInfo.cs index fb5c77bc0..c1fe525e8 100644 --- a/GUI/Controls/ModInfo.cs +++ b/GUI/Controls/ModInfo.cs @@ -7,6 +7,7 @@ using Autofac; +using CKAN.Configuration; using CKAN.Versioning; using CKAN.GUI.Attributes; @@ -138,6 +139,19 @@ private void UpdateHeaderInfo(GUIMod gmod, GameVersionCriteria crit) var pageToAlert = module.IsCompatible(crit) ? RelationshipTabPage : VersionsTabPage; pageToAlert.ImageKey = "Stop"; } + if (manager?.CurrentInstance is GameInstance inst) + { + var filters = ServiceLocator.Container.Resolve() + .GlobalInstallFilters + .Concat(inst.InstallFilters) + .ToHashSet(); + ContentTabPage.ImageKey = ModuleLabels.IgnoreMissingIdentifiers(inst) + .Contains(gmod.Identifier) + || (gmod.InstalledMod?.AllFilesExist(inst, filters) + ?? true) + ? "" + : "Stop"; + } ModInfoTabControl.ResumeLayout(); }); From 7731619f2e290b3158ac13f74a3989aa17642722 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 29 Sep 2024 12:18:24 -0500 Subject: [PATCH 7/9] Refresh mod list and Contents tab after filters change --- GUI/Dialogs/InstallFiltersDialog.cs | 13 +++++++++---- GUI/Main/Main.cs | 6 ++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/GUI/Dialogs/InstallFiltersDialog.cs b/GUI/Dialogs/InstallFiltersDialog.cs index e9e1a10d6..6c312d163 100644 --- a/GUI/Dialogs/InstallFiltersDialog.cs +++ b/GUI/Dialogs/InstallFiltersDialog.cs @@ -22,6 +22,8 @@ public InstallFiltersDialog(IConfiguration globalConfig, GameInstance instance) this.instance = instance; } + public bool Changed { get; private set; } = false; + /// /// Open the user guide when the user presses F1 /// @@ -48,8 +50,12 @@ private void InstallFiltersDialog_Load(object? sender, EventArgs? e) private void InstallFiltersDialog_Closing(object? sender, CancelEventArgs? e) { - globalConfig.GlobalInstallFilters = GlobalFiltersTextBox.Text.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); - instance.InstallFilters = InstanceFiltersTextBox.Text.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); + var newGlobal = GlobalFiltersTextBox.Text.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); + var newInstance = InstanceFiltersTextBox.Text.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); + Changed = !globalConfig.GlobalInstallFilters.SequenceEqual(newGlobal) + || !instance.InstallFilters.SequenceEqual(newInstance); + globalConfig.GlobalInstallFilters = newGlobal; + instance.InstallFilters = newInstance; } private void AddMiniAVCButton_Click(object? sender, EventArgs? e) @@ -57,8 +63,7 @@ private void AddMiniAVCButton_Click(object? sender, EventArgs? e) GlobalFiltersTextBox.Text = string.Join(Environment.NewLine, GlobalFiltersTextBox.Text.Split(delimiters, StringSplitOptions.RemoveEmptyEntries) .Concat(miniAVC) - .Distinct() - ); + .Distinct()); } private readonly IConfiguration globalConfig; diff --git a/GUI/Main/Main.cs b/GUI/Main/Main.cs index 2211e12aa..ebb4f2384 100644 --- a/GUI/Main/Main.cs +++ b/GUI/Main/Main.cs @@ -724,6 +724,12 @@ private void installFiltersToolStripMenuItem_Click(object? sender, EventArgs? e) var dlg = new InstallFiltersDialog(ServiceLocator.Container.Resolve(), CurrentInstance); dlg.ShowDialog(this); Enabled = true; + if (dlg.Changed) + { + // The Update checkbox might appear or disappear if missing files were or are filtered out + RefreshModList(false); + ModInfo.RefreshModContentsTree(); + } } } From 3fb96ef43a34f1864faf7b5099b05757a6470ad5 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 29 Sep 2024 17:30:20 -0500 Subject: [PATCH 8/9] Scroll to top before first red node in Contents --- GUI/Controls/ModInfoTabs/Contents.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/GUI/Controls/ModInfoTabs/Contents.cs b/GUI/Controls/ModInfoTabs/Contents.cs index cd0a8bbb8..6b0a8df84 100644 --- a/GUI/Controls/ModInfoTabs/Contents.cs +++ b/GUI/Controls/ModInfoTabs/Contents.cs @@ -190,10 +190,11 @@ private void _UpdateModContentsTree(InstalledModule? instMod, CkanModule? module dir, exists); } rootNode.ExpandAll(); - var initialFocus = FirstMatching(rootNode, - n => n.ForeColor == Color.Red) - ?? rootNode; - initialFocus.EnsureVisible(); + // First scroll to the top + rootNode.EnsureVisible(); + // Then scroll down to the first red node + FirstMatching(rootNode, n => n.ForeColor == Color.Red) + ?.EnsureVisible(); ContentsPreviewTree.EndUpdate(); UseWaitCursor = false; }); From b883868c6eea80fd39944962b90b5df59adeb11d Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 29 Sep 2024 18:42:52 -0500 Subject: [PATCH 9/9] Keep row selected when changing labels --- GUI/Model/ModList.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/GUI/Model/ModList.cs b/GUI/Model/ModList.cs index 013135c45..2a12b8bc7 100644 --- a/GUI/Model/ModList.cs +++ b/GUI/Model/ModList.cs @@ -564,8 +564,13 @@ private void CheckRowUpgradeable(GameInstance inst, full_list_of_mod_rows[ident] = MakeRow(gmod, ChangeSet, inst.Name, inst.game); var rowIndex = row.Index; + var selected = row.Selected; rows.Remove(row); rows.Insert(rowIndex, newRow); + if (selected) + { + rows[rowIndex].Selected = true; + } } } }