From 2fc0dbbf9a0afd17899a3c9b7d50856be4dd3479 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sun, 27 Nov 2022 00:37:39 +0000 Subject: [PATCH] Show conflicts in changeset --- GUI/Controls/Changeset.cs | 25 +++++--- GUI/Controls/ManageMods.Designer.cs | 2 + GUI/Controls/ManageMods.cs | 80 +++++++------------------ GUI/Main/Main.cs | 8 ++- GUI/Main/MainChangeset.cs | 7 ++- GUI/Model/ModList.cs | 90 +++++++---------------------- Tests/GUI/Model/ModList.cs | 2 +- 7 files changed, 71 insertions(+), 143 deletions(-) diff --git a/GUI/Controls/Changeset.cs b/GUI/Controls/Changeset.cs index c58d84efed..2fb0d6dfc9 100644 --- a/GUI/Controls/Changeset.cs +++ b/GUI/Controls/Changeset.cs @@ -15,7 +15,10 @@ public Changeset() InitializeComponent(); } - public void LoadChangeset(List changes, List AlertLabels) + public void LoadChangeset( + List changes, + List AlertLabels, + Dictionary conflicts) { changeset = changes; alertLabels = AlertLabels; @@ -25,11 +28,12 @@ public void LoadChangeset(List changes, List AlertLabels // Changeset sorting is handled upstream in the resolver ChangesListView.Items.AddRange(changes .Where(ch => ch.ChangeType != GUIModChangeType.None) - .Select(makeItem) + .Select(ch => makeItem(ch, conflicts)) .ToArray()); ChangesListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); ChangesListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); } + ConfirmChangesButton.Enabled = conflicts == null || !conflicts.Any(); } protected override void OnVisibleChanged(EventArgs e) @@ -71,7 +75,7 @@ private void BackButton_Click(object sender, EventArgs e) OnCancelChanges?.Invoke(false); } - private ListViewItem makeItem(ModChange change) + private ListViewItem makeItem(ModChange change, Dictionary conflicts) { var descr = change.Description; CkanModule m = change.Mod; @@ -80,16 +84,19 @@ private ListViewItem makeItem(ModChange change) { change.NameAndStatus, change.ChangeType.ToI18nString(), - warnLbl != null - ? string.Format( - Properties.Resources.MainChangesetWarningInstallingModuleWithLabel, - warnLbl.Name, - descr) - : descr + conflicts != null && conflicts.TryGetValue(m, out string confDescr) + ? string.Format("{0} ({1})", confDescr, descr) + : warnLbl != null + ? string.Format( + Properties.Resources.MainChangesetWarningInstallingModuleWithLabel, + warnLbl.Name, + descr) + : descr }) { Tag = m, ForeColor = warnLbl != null ? Color.Red : SystemColors.WindowText, + BackColor = conflicts != null && conflicts.ContainsKey(m) ? Color.LightCoral : Color.Empty, ToolTipText = descr, }; } diff --git a/GUI/Controls/ManageMods.Designer.cs b/GUI/Controls/ManageMods.Designer.cs index 3c328e54bd..224a9ed994 100644 --- a/GUI/Controls/ManageMods.Designer.cs +++ b/GUI/Controls/ManageMods.Designer.cs @@ -112,6 +112,7 @@ private void InitializeComponent() this.menuStrip2.CanOverflow = true; this.menuStrip2.Location = new System.Drawing.Point(0, 0); this.menuStrip2.Name = "menuStrip2"; + this.menuStrip2.ShowItemToolTips = true; this.menuStrip2.Size = new System.Drawing.Size(5876, 48); this.menuStrip2.TabStop = true; this.menuStrip2.TabIndex = 4; @@ -149,6 +150,7 @@ private void InitializeComponent() // // ApplyToolButton // + this.ApplyToolButton.AutoToolTip = false; this.ApplyToolButton.Image = global::CKAN.GUI.Properties.Resources.apply; this.ApplyToolButton.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; this.ApplyToolButton.Name = "ApplyToolButton"; diff --git a/GUI/Controls/ManageMods.cs b/GUI/Controls/ManageMods.cs index b5d639700f..75d9d0648f 100644 --- a/GUI/Controls/ManageMods.cs +++ b/GUI/Controls/ManageMods.cs @@ -84,16 +84,10 @@ private List sortColumns } } - private List descending - { - get - { - return Main.Instance.configuration.MultiSortDescending; - } - } + private List descending => Main.Instance.configuration.MultiSortDescending; public event Action OnSelectedModuleChanged; - public event Action> OnChangeSetChanged; + public event Action, Dictionary> OnChangeSetChanged; public event Action OnRegistryChanged; public event Action> StartChangeSet; @@ -101,7 +95,7 @@ private List descending private List ChangeSet { - get { return currentChangeSet; } + get => currentChangeSet; set { var orig = currentChangeSet; @@ -124,13 +118,13 @@ private void ChangeSetUpdated() ApplyToolButton.Enabled = false; InstallAllCheckbox.Checked = true; } - OnChangeSetChanged?.Invoke(ChangeSet); + OnChangeSetChanged?.Invoke(ChangeSet, Conflicts); }); } private Dictionary Conflicts { - get { return conflicts; } + get => conflicts; set { var orig = conflicts; @@ -1589,30 +1583,17 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) public bool AllowClose() { - if (Conflicts != null) + if (Conflicts != null && Conflicts.Any()) { - if (Conflicts.Any()) - { - // Ask if they want to resolve conflicts - string confDescrip = Conflicts - .Select(kvp => kvp.Value) - .Aggregate((a, b) => $"{a}, {b}"); - if (!Main.Instance.YesNoDialog(string.Format(Properties.Resources.MainQuitWithConflicts, confDescrip), - Properties.Resources.MainQuit, - Properties.Resources.MainGoBack)) - { - return false; - } - } - else + // Ask if they want to resolve conflicts + string confDescrip = Conflicts + .Select(kvp => kvp.Value) + .Aggregate((a, b) => $"{a}, {b}"); + if (!Main.Instance.YesNoDialog(string.Format(Properties.Resources.MainQuitWithConflicts, confDescrip), + Properties.Resources.MainQuit, + Properties.Resources.MainGoBack)) { - // The Conflicts dictionary is empty even when there are unmet dependencies. - if (!Main.Instance.YesNoDialog(Properties.Resources.MainQuitWithUnmetDeps, - Properties.Resources.MainQuit, - Properties.Resources.MainGoBack)) - { - return false; - } + return false; } } else if (ChangeSet?.Any() ?? false) @@ -1654,42 +1635,25 @@ public async Task UpdateChangeSetAndConflicts(GameInstance inst, IRegistryQuerie var user_change_set = mainModList.ComputeUserChangeSet(registry, inst.VersionCriteria()); try { + var gameVersion = inst.VersionCriteria(); var module_installer = new ModuleInstaller(inst, Main.Instance.Manager.Cache, Main.Instance.currentUser); - full_change_set = mainModList.ComputeChangeSetFromModList(registry, user_change_set, module_installer, inst.VersionCriteria()).ToList(); - } - catch (InconsistentKraken k) - { - // Need to be recomputed due to ComputeChangeSetFromModList possibly changing it with too many provides handling. - Main.Instance.AddStatusMessage(k.ShortDescription); - user_change_set = mainModList.ComputeUserChangeSet(registry, inst.VersionCriteria()); - new_conflicts = ModList.ComputeConflictsFromModList(registry, user_change_set, inst.VersionCriteria()); - full_change_set = null; - } - catch (TooManyModsProvideKraken) - { - // Can be thrown by ComputeChangeSetFromModList if the user cancels out of it. - // We can just rerun it as the ModInfo has been removed. - too_many_provides_thrown = true; + var tuple = mainModList.ComputeFullChangeSetFromUserChangeSet(registry, user_change_set, module_installer, gameVersion); + full_change_set = tuple.Item1.ToList(); + new_conflicts = tuple.Item2.ToDictionary( + item => new GUIMod(item.Key, registry, gameVersion), + item => item.Value); + Main.Instance.AddStatusMessage(string.Join(";", new_conflicts.Values)); } catch (DependencyNotSatisfiedKraken k) { Main.Instance.currentUser.RaiseError( Properties.Resources.MainDepNotSatisfied, - k.parent, - k.module - ); + k.parent, k.module); // Uncheck the box MarkModForInstall(k.parent.identifier, true); } - if (too_many_provides_thrown) - { - await UpdateChangeSetAndConflicts(inst, registry); - new_conflicts = Conflicts; - full_change_set = ChangeSet; - } - Conflicts = new_conflicts; ChangeSet = full_change_set; } diff --git a/GUI/Main/Main.cs b/GUI/Main/Main.cs index 5475429b2e..db48355234 100644 --- a/GUI/Main/Main.cs +++ b/GUI/Main/Main.cs @@ -701,12 +701,14 @@ private void ShowSelectionModInfo(ListView.SelectedListViewItemCollection select ); } - private void ManageMods_OnChangeSetChanged(List changeset) + private void ManageMods_OnChangeSetChanged(List changeset, Dictionary conflicts) { if (changeset != null && changeset.Any()) { tabController.ShowTab("ChangesetTabPage", 1, false); - UpdateChangesDialog(changeset); + UpdateChangesDialog( + changeset, + conflicts.ToDictionary(item => item.Key.ToCkanModule(), item => item.Value)); auditRecommendationsMenuItem.Enabled = false; } else @@ -862,7 +864,7 @@ private void GameExit(GameInstance inst) // This is used by Reinstall private void ManageMods_StartChangeSet(List changeset) { - UpdateChangesDialog(changeset); + UpdateChangesDialog(changeset, null); tabController.ShowTab("ChangesetTabPage", 1); } diff --git a/GUI/Main/MainChangeset.cs b/GUI/Main/MainChangeset.cs index f5dc30a9b9..c66006a1e9 100644 --- a/GUI/Main/MainChangeset.cs +++ b/GUI/Main/MainChangeset.cs @@ -7,13 +7,14 @@ namespace CKAN.GUI { public partial class Main { - private void UpdateChangesDialog(List changeset) + private void UpdateChangesDialog(List changeset, Dictionary conflicts) { Changeset.LoadChangeset( changeset, ManageMods.mainModList.ModuleLabels.LabelsFor(CurrentInstance.Name) .Where(l => l.AlertOnInstall) - .ToList()); + .ToList(), + conflicts); } private void Changeset_OnSelectedItemsChanged(ListView.SelectedListViewItemCollection items) @@ -26,7 +27,7 @@ private void Changeset_OnCancelChanges(bool reset) if (reset) { ManageMods.ClearChangeSet(); - UpdateChangesDialog(null); + UpdateChangesDialog(null, null); } tabController.ShowTab("ManageModsTabPage"); } diff --git a/GUI/Model/ModList.cs b/GUI/Model/ModList.cs index 128823287c..9cf372673e 100644 --- a/GUI/Model/ModList.cs +++ b/GUI/Model/ModList.cs @@ -112,15 +112,22 @@ public static SavedSearch FilterToSavedSearch(GUIModFilter filter, ModuleTag tag }; } + private static readonly RelationshipResolverOptions conflictOptions = new RelationshipResolverOptions() + { + without_toomanyprovides_kraken = true, + proceed_with_inconsistencies = true, + without_enforce_consistency = true, + with_recommends = false + }; + /// - /// This function returns a changeset based on the selections of the user. - /// Currently returns null if a conflict is detected. + /// Returns a changeset and conflicts based on the selections of the user. /// /// /// /// A module installer for the current game instance /// The version of the current game instance - public IEnumerable ComputeChangeSetFromModList( + public Tuple, Dictionary> ComputeFullChangeSetFromUserChangeSet( IRegistryQuerier registry, HashSet changeSet, ModuleInstaller installer, GameVersionCriteria version) { @@ -189,24 +196,20 @@ public IEnumerable ComputeChangeSetFromModList( } // Get as many dependencies as we can, but leave decisions and prompts for installation time - RelationshipResolverOptions opts = RelationshipResolver.DependsOnlyOpts(); - opts.without_toomanyprovides_kraken = true; - opts.without_enforce_consistency = true; - var resolver = new RelationshipResolver( - modules_to_install, - modules_to_remove, - opts, registry, version); + modules_to_install, modules_to_remove, + conflictOptions, registry, version); // Replace Install entries in changeset with the ones from resolver to get all the reasons - return changeSet - .Where(ch => !(ch.ChangeType is GUIModChangeType.Install)) - .OrderBy(ch => ch.Mod.identifier) - .Union(resolver.ModList() - // Changeset already contains Update changes for these - .Except(upgrading) - .Where(m => !m.IsMetapackage) - .Select(m => new ModChange(m, GUIModChangeType.Install, resolver.ReasonsFor(m)))); + return new Tuple, Dictionary>( + changeSet.Where(ch => !(ch.ChangeType is GUIModChangeType.Install)) + .OrderBy(ch => ch.Mod.identifier) + .Union(resolver.ModList() + // Changeset already contains Update changes for these + .Except(upgrading) + .Where(m => !m.IsMetapackage) + .Select(m => new ModChange(m, GUIModChangeType.Install, resolver.ReasonsFor(m)))), + resolver.ConflictList); } /// @@ -461,57 +464,6 @@ public string StripEpoch(string version) private static readonly Regex ContainsEpoch = new Regex(@"^[0-9][0-9]*:[^:]+$", RegexOptions.Compiled); private static readonly Regex RemoveEpoch = new Regex(@"^([^:]+):([^:]+)$", RegexOptions.Compiled); - public static Dictionary ComputeConflictsFromModList(IRegistryQuerier registry, - IEnumerable change_set, GameVersionCriteria ksp_version) - { - var modules_to_install = new HashSet(); - var modules_to_remove = new HashSet(); - var options = new RelationshipResolverOptions - { - without_toomanyprovides_kraken = true, - proceed_with_inconsistencies = true, - without_enforce_consistency = true, - with_recommends = false - }; - - foreach (var change in change_set) - { - switch (change.ChangeType) - { - case GUIModChangeType.None: - break; - case GUIModChangeType.Install: - modules_to_install.Add(change.Mod); - break; - case GUIModChangeType.Remove: - modules_to_remove.Add(change.Mod); - break; - case GUIModChangeType.Update: - break; - case GUIModChangeType.Replace: - ModuleReplacement repl = registry.GetReplacement(change.Mod, ksp_version); - if (repl != null) - { - modules_to_remove.Add(repl.ToReplace); - modules_to_install.Add(repl.ReplaceWith); - } - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - var resolver = new RelationshipResolver( - modules_to_install.Except(modules_to_remove), - modules_to_remove, - options, registry, ksp_version - ); - return resolver.ConflictList.ToDictionary( - item => new GUIMod(item.Key, registry, ksp_version), - item => item.Value - ); - } - public HashSet ComputeUserChangeSet(IRegistryQuerier registry, GameVersionCriteria crit) { log.Debug("Computing user changeset"); diff --git a/Tests/GUI/Model/ModList.cs b/Tests/GUI/Model/ModList.cs index 5e4493c6da..d93d1846e7 100644 --- a/Tests/GUI/Model/ModList.cs +++ b/Tests/GUI/Model/ModList.cs @@ -19,7 +19,7 @@ namespace Tests.GUI public class ModListTests { [Test] - public void ComputeChangeSetFromModList_WithEmptyList_HasEmptyChangeSet() + public void ComputeFullChangeSetFromUserChangeSet_WithEmptyList_HasEmptyChangeSet() { var item = new ModList(delegate { }); Assert.That(item.ComputeUserChangeSet(null, null), Is.Empty);