diff --git a/Core/Extensions/EnumerableExtensions.cs b/Core/Extensions/EnumerableExtensions.cs index d7fca006c..6af2d75bd 100644 --- a/Core/Extensions/EnumerableExtensions.cs +++ b/Core/Extensions/EnumerableExtensions.cs @@ -60,6 +60,10 @@ public static void RemoveWhere(this Dictionary source, } } + public static bool IntersectsWith(this IEnumerable source, + IEnumerable other) + => source.Intersect(other).Any(); + /// /// Sum a sequence of TimeSpans. /// Mysteriously not defined standardly. diff --git a/Core/Properties/Resources.resx b/Core/Properties/Resources.resx index b447ab648..33773c072 100644 --- a/Core/Properties/Resources.resx +++ b/Core/Properties/Resources.resx @@ -137,7 +137,7 @@ Install the `mono-complete` package or equivalent for your operating system. {0}: {1} has length {2}, should be {3} {0}: {1} is not a valid ZIP file: {2} - {0}: {1} has SHA256 {2}, should be {3} + {0}: SHA256 {2} should be {3} for {1} Refreshing game version data Checking for updates Already up to date diff --git a/GUI/Controls/ManageMods.cs b/GUI/Controls/ManageMods.cs index ff0b1e9ac..af798291d 100644 --- a/GUI/Controls/ManageMods.cs +++ b/GUI/Controls/ManageMods.cs @@ -96,14 +96,15 @@ private void ResizeColumnHeaders() private Dictionary? conflicts; private bool freezeChangeSet = false; - public event Action? RaiseMessage; - public event Action? RaiseError; - public event Action? SetStatusBar; - public event Action? ClearStatusBar; - public event Action? LaunchGame; - public event Action? EditCommandLines; + public event Action? RaiseMessage; + public event Action? RaiseError; + public event Action? SetStatusBar; + public event Action? ClearStatusBar; + public event Action? LaunchGame; + public event Action? EditCommandLines; + public event Action>? DownloadModules; - public ModList? mainModList + public ModList? MainModList { get; [ForbidGUICalls] @@ -184,9 +185,9 @@ private void ChangeSetUpdated() var removing = changeIdentifiersOfType(GUIModChangeType.Remove) .Except(changeIdentifiersOfType(GUIModChangeType.Install)) .ToHashSet(); - if (mainModList != null) + if (MainModList != null) { - foreach ((string ident, DataGridViewRow row) in mainModList.full_list_of_mod_rows) + foreach ((string ident, DataGridViewRow row) in MainModList.full_list_of_mod_rows) { if (removing.Contains(ident)) { @@ -260,7 +261,7 @@ private void SetUnsetRowConflicted(GUIMod guiMod, GameInstance inst, Registry registry) { - var row = mainModList?.ReapplyLabels(guiMod, conflicted, inst, registry); + var row = MainModList?.ReapplyLabels(guiMod, conflicted, inst, registry); if (row != null) { foreach (DataGridViewCell cell in row.Cells) @@ -402,16 +403,16 @@ private void editLabelsToolStripMenuItem_Click(object? sender, EventArgs? e) eld.Dispose(); ModuleLabelList.ModuleLabels.Save(ModuleLabelList.DefaultPath); var registry = RegistryManager.Instance(currentInstance, repoData).registry; - if (mainModList != null) + if (MainModList != null) { - foreach (var module in mainModList.Modules) + foreach (var module in MainModList.Modules) { - mainModList.ReapplyLabels(module, Conflicts?.ContainsKey(module) ?? false, + MainModList.ReapplyLabels(module, Conflicts?.ContainsKey(module) ?? false, currentInstance, registry); } UpdateHiddenTagsAndLabels(); UpdateCol.Visible = UpdateAllToolButton.Enabled = - mainModList.ResetHasUpdate(currentInstance, registry, ChangeSet, ModGrid.Rows); + MainModList.ResetHasUpdate(currentInstance, registry, ChangeSet, ModGrid.Rows); } } } @@ -524,7 +525,7 @@ public void SetSearches(List searches) { Util.Invoke(ModGrid, () => { - mainModList?.SetSearches(searches); + MainModList?.SetSearches(searches); EditModSearches.SetSearches(searches); ShowHideColumns(searches); }); @@ -544,9 +545,9 @@ private void ShowHideColumns(List searches) } // If these columns aren't hidden by the user, show them if the search includes installed modules - setInstalledColumnsVisible((mainModList?.HasAnyInstalled ?? false) + setInstalledColumnsVisible((MainModList?.HasAnyInstalled ?? false) && !SearchesExcludeInstalled(searches) - && mainModList.HasVisibleInstalled()); + && MainModList.HasVisibleInstalled()); } private static readonly string[] installedColumnNames = new string[] @@ -572,11 +573,11 @@ private static bool SearchesExcludeInstalled(List? searches) public void MarkAllUpdates() { - if (mainModList != null) + if (MainModList != null) { WithFrozenChangeset(() => { - var checkboxes = mainModList.full_list_of_mod_rows + var checkboxes = MainModList.full_list_of_mod_rows .Values .Where(row => row.Tag is GUIMod { Identifier: string ident} && (!Main.Instance?.LabelsHeld(ident) ?? false)) @@ -1040,8 +1041,8 @@ private void guiModule_PropertyChanged(object? sender, PropertyChangedEventArgs? { if (currentInstance != null && sender is GUIMod gmod - && mainModList != null - && mainModList.full_list_of_mod_rows.TryGetValue(gmod.Identifier, + && MainModList != null + && MainModList.full_list_of_mod_rows.TryGetValue(gmod.Identifier, out DataGridViewRow? row)) { switch (e?.PropertyName) @@ -1088,7 +1089,7 @@ private void guiModule_PropertyChanged(object? sender, PropertyChangedEventArgs? break; case nameof(GUIMod.IsCached): - row.Visible = mainModList.IsVisible(gmod, currentInstance, + row.Visible = MainModList.IsVisible(gmod, currentInstance, RegistryManager.Instance(currentInstance, repoData).registry); if (row.Visible && !ModGrid.Rows.Contains(row)) { @@ -1106,8 +1107,8 @@ public void RemoveChangesetItem(ModChange change) && currentChangeSet != null && currentChangeSet.Contains(change) && change.IsRemovable - && mainModList != null - && mainModList.full_list_of_mod_rows.TryGetValue(change.Mod.identifier, + && MainModList != null + && MainModList.full_list_of_mod_rows.TryGetValue(change.Mod.identifier, out DataGridViewRow? row) && row.Tag is GUIMod guiMod) { @@ -1198,10 +1199,10 @@ private void InstallAllCheckbox_CheckedChanged(object? sender, EventArgs? e) // Reset changeset ClearChangeSet(); } - else if (mainModList != null) + else if (MainModList != null) { // Uninstall all and cancel upgrades - foreach (var row in mainModList.full_list_of_mod_rows.Values) + foreach (var row in MainModList.full_list_of_mod_rows.Values) { if (row.Tag is GUIMod gmod) { @@ -1215,11 +1216,11 @@ private void InstallAllCheckbox_CheckedChanged(object? sender, EventArgs? e) public void ClearChangeSet() { - if (mainModList != null) + if (MainModList != null) { WithFrozenChangeset(() => { - foreach (DataGridViewRow row in mainModList.full_list_of_mod_rows.Values) + foreach (DataGridViewRow row in MainModList.full_list_of_mod_rows.Values) { if (row.Tag is GUIMod gmod) { @@ -1252,7 +1253,7 @@ public void ClearChangeSet() .ToArray(); foreach (var mod in noLongerUsed) { - if (mainModList.full_list_of_mod_rows.TryGetValue(mod.identifier, out DataGridViewRow? row) + if (MainModList.full_list_of_mod_rows.TryGetValue(mod.identifier, out DataGridViewRow? row) && row.Tag is GUIMod gmod) { gmod.SetAutoInstallChecked(row, AutoInstalled, false); @@ -1440,7 +1441,7 @@ private void reinstallToolStripMenuItem_Click(object? sender, EventArgs? e) [ForbidGUICalls] public Dictionary AllGUIMods() - => (mainModList?.Modules ?? Enumerable.Empty()) + => (MainModList?.Modules ?? Enumerable.Empty()) .ToDictionary(guiMod => guiMod.Identifier, guiMod => guiMod); @@ -1452,7 +1453,7 @@ private void purgeContentsToolStripMenuItem_Click(object? sender, EventArgs? e) && SelectedModules.Where(m => !m.Module.IsMetapackage && m.IsCached) .ToArray() - is { Length: > 0 } and var modules) + is { Length: > 0 } modules) { UseWaitCursor = true; var reg = RegistryManager.Instance(currentInstance, repoData).registry; @@ -1472,20 +1473,20 @@ private void downloadContentsToolStripMenuItem_Click(object? sender, EventArgs? if (SelectedModules.Where(m => !m.Module.IsMetapackage && !m.IsCached) .ToArray() - is { Length: > 0 } and var modules) + is { Length: > 0 } modules) { - Main.Instance?.StartDownloads(modules); + DownloadModules?.Invoke(modules); } } private void EditModSearches_ApplySearches(List searches) { - mainModList?.SetSearches(searches); + MainModList?.SetSearches(searches); // If these columns aren't hidden by the user, show them if the search includes installed modules - setInstalledColumnsVisible((mainModList?.HasAnyInstalled ?? false) + setInstalledColumnsVisible((MainModList?.HasAnyInstalled ?? false) && !SearchesExcludeInstalled(searches) - && mainModList.HasVisibleInstalled()); + && MainModList.HasVisibleInstalled()); } private void EditModSearches_SurrenderFocus() @@ -1501,7 +1502,7 @@ private void UpdateFilters() private void _UpdateFilters() { - if (ModGrid == null || mainModList?.full_list_of_mod_rows == null || currentInstance == null) + if (ModGrid == null || MainModList?.full_list_of_mod_rows == null || currentInstance == null) { return; } @@ -1510,8 +1511,8 @@ private void _UpdateFilters() // To make the filtering process faster, Copy the list of rows. Filter out the hidden and replace the // rows in DataGridView. - var rows = new DataGridViewRow[mainModList.full_list_of_mod_rows.Count]; - mainModList.full_list_of_mod_rows.Values.CopyTo(rows, 0); + var rows = new DataGridViewRow[MainModList.full_list_of_mod_rows.Count]; + MainModList.full_list_of_mod_rows.Values.CopyTo(rows, 0); // Try to remember the current scroll position and selected mod var scroll_col = Math.Max(0, ModGrid.FirstDisplayedScrollingColumnIndex); GUIMod? selected_mod = null; @@ -1526,7 +1527,7 @@ private void _UpdateFilters() var instGame = currentInstance.Game; rows.AsParallel().ForAll(row => row.Visible = row.Tag is GUIMod gmod - && mainModList.IsVisible(gmod, currentInstance, registry)); + && MainModList.IsVisible(gmod, currentInstance, registry)); ApplyHeaderGlyphs(); ModGrid.Rows.AddRange(Sort(rows.Where(row => row.Visible)).ToArray()); @@ -1584,7 +1585,7 @@ private bool _UpdateModsList(Dictionary? old_modules = null) ModuleLabelList.ModuleLabels, manager.Cache, guiConfig) .ToHashSet(); - foreach (var gmod in mainModList?.full_list_of_mod_rows + foreach (var gmod in MainModList?.full_list_of_mod_rows ?.Values .Select(row => row.Tag) .OfType() @@ -1619,10 +1620,10 @@ private bool _UpdateModsList(Dictionary? old_modules = null) } } } - else if (mainModList != null) + else if (MainModList != null) { // Copy the new mod flag from the old list. - var oldNewMods = mainModList.Modules.Where(m => m.IsNew) + var oldNewMods = MainModList.Modules.Where(m => m.IsNew) .ToHashSet(); foreach (var guiMod in guiMods.Intersect(oldNewMods)) { @@ -1633,39 +1634,39 @@ private bool _UpdateModsList(Dictionary? old_modules = null) RaiseMessage?.Invoke(Properties.Resources.MainModListPopulatingList); // Update our mod listing - mainModList = new ModList(guiMods, currentInstance, + MainModList = new ModList(guiMods, currentInstance, ModuleLabelList.ModuleLabels, ModuleTagList.ModuleTags, ServiceLocator.Container.Resolve(), guiConfig, ChangeSet); - mainModList.ModFiltersUpdated += UpdateFilters; + MainModList.ModFiltersUpdated += UpdateFilters; UpdateChangeSetAndConflicts(currentInstance, registry); RaiseMessage?.Invoke(Properties.Resources.MainModListUpdatingFilters); - var has_unheld_updates = mainModList.Modules.Any(mod => mod.HasUpdate + var has_unheld_updates = MainModList.Modules.Any(mod => mod.HasUpdate && (!Main.Instance?.LabelsHeld(mod.Identifier) ?? true)); Util.Invoke(Toolbar, () => { FilterCompatibleButton.Text = string.Format(Properties.Resources.MainModListCompatible, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.Compatible)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.Compatible)); FilterInstalledButton.Text = string.Format(Properties.Resources.MainModListInstalled, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.Installed)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.Installed)); FilterInstalledUpdateButton.Text = string.Format(Properties.Resources.MainModListUpgradeable, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.InstalledUpdateAvailable)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.InstalledUpdateAvailable)); FilterReplaceableButton.Text = string.Format(Properties.Resources.MainModListReplaceable, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.Replaceable)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.Replaceable)); FilterCachedButton.Text = string.Format(Properties.Resources.MainModListCached, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.Cached)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.Cached)); FilterUncachedButton.Text = string.Format(Properties.Resources.MainModListUncached, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.Uncached)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.Uncached)); FilterNewButton.Text = string.Format(Properties.Resources.MainModListNewlyCompatible, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.NewInRepository)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.NewInRepository)); FilterNotInstalledButton.Text = string.Format(Properties.Resources.MainModListNotInstalled, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.NotInstalled)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.NotInstalled)); FilterIncompatibleButton.Text = string.Format(Properties.Resources.MainModListIncompatible, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.Incompatible)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.Incompatible)); FilterAllButton.Text = string.Format(Properties.Resources.MainModListAll, - mainModList.CountModsByFilter(currentInstance, GUIModFilter.All)); + MainModList.CountModsByFilter(currentInstance, GUIModFilter.All)); UpdateAllToolButton.Enabled = has_unheld_updates; }); @@ -1678,7 +1679,7 @@ private bool _UpdateModsList(Dictionary? old_modules = null) Util.Invoke(ModGrid, () => { UpdateCol.Visible = has_unheld_updates; - ReplaceCol.Visible = mainModList.Modules.Any(mod => mod.IsInstalled && mod.HasReplacement); + ReplaceCol.Visible = MainModList.Modules.Any(mod => mod.IsInstalled && mod.HasReplacement); }); UpdateHiddenTagsAndLabels(); @@ -2034,7 +2035,7 @@ private void ToggleModuleLabel(ModuleLabel label, GameInstance instance, { label.Add(instance.Game, module.Identifier); } - mainModList?.ReapplyLabels(module, Conflicts?.ContainsKey(module) ?? false, + MainModList?.ReapplyLabels(module, Conflicts?.ContainsKey(module) ?? false, instance, registry); if (module == SelectedModule) { @@ -2046,7 +2047,7 @@ private void ToggleModuleLabel(ModuleLabel label, GameInstance instance, if (label.HoldVersion || label.IgnoreMissingFiles) { UpdateCol.Visible = UpdateAllToolButton.Enabled = - mainModList?.ResetHasUpdate(instance, registry, ChangeSet, ModGrid.Rows) ?? false; + MainModList?.ResetHasUpdate(instance, registry, ChangeSet, ModGrid.Rows) ?? false; } } @@ -2175,7 +2176,7 @@ public void InstanceUpdated() public HashSet ComputeUserChangeSet() => currentInstance == null ? new HashSet() - : mainModList?.ComputeUserChangeSet( + : MainModList?.ComputeUserChangeSet( RegistryManager.Instance(currentInstance, repoData).registry, currentInstance, UpdateCol, ReplaceCol) ?? new HashSet(); @@ -2189,7 +2190,7 @@ public void UpdateChangeSetAndConflicts(GameInstance inst, IRegistryQuerier regi return; } - if (mainModList == null || manager?.Cache == null) + if (MainModList == null || manager?.Cache == null) { return; } @@ -2197,13 +2198,13 @@ public void UpdateChangeSetAndConflicts(GameInstance inst, IRegistryQuerier regi List? full_change_set = null; Dictionary? new_conflicts = null; - var user_change_set = mainModList.ComputeUserChangeSet(registry, inst, UpdateCol, ReplaceCol); + var user_change_set = MainModList.ComputeUserChangeSet(registry, inst, UpdateCol, ReplaceCol); try { // Set the target versions of upgrading mods based on what's actually allowed foreach (var ch in user_change_set.OfType()) { - if (mainModList.full_list_of_mod_rows[ch.Mod.identifier].Tag is GUIMod gmod) + if (MainModList.full_list_of_mod_rows[ch.Mod.identifier].Tag is GUIMod gmod) { // This setter calls UpdateChangeSetAndConflicts, so there's a risk of // an infinite loop here. Tread lightly! @@ -2211,7 +2212,7 @@ public void UpdateChangeSetAndConflicts(GameInstance inst, IRegistryQuerier regi } } var gameVersion = inst.VersionCriteria(); - var tuple = mainModList.ComputeFullChangeSetFromUserChangeSet(registry, user_change_set, inst.Game, + var tuple = MainModList.ComputeFullChangeSetFromUserChangeSet(registry, user_change_set, inst.Game, inst.StabilityToleranceConfig, gameVersion); full_change_set = tuple.Item1.ToList(); new_conflicts = tuple.Item2.ToDictionary( @@ -2240,7 +2241,7 @@ public void UpdateChangeSetAndConflicts(GameInstance inst, IRegistryQuerier regi foreach (var ident in identifiers) { // Uncheck the box - if (mainModList.full_list_of_mod_rows[ident].Tag is GUIMod gmod) + if (MainModList.full_list_of_mod_rows[ident].Tag is GUIMod gmod) { gmod.SelectedMod = null; } diff --git a/GUI/Dialogs/DownloadsFailedDialog.Designer.cs b/GUI/Dialogs/DownloadsFailedDialog.Designer.cs index 1bca5af8f..5abe8fc4e 100644 --- a/GUI/Dialogs/DownloadsFailedDialog.Designer.cs +++ b/GUI/Dialogs/DownloadsFailedDialog.Designer.cs @@ -36,6 +36,8 @@ private void InitializeComponent() this.SkipColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.ModColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ErrorColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.GridContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(); + this.CopyErrorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.BottomButtonPanel = new CKAN.GUI.LeftRightRowPanel(); this.RetryButton = new System.Windows.Forms.Button(); this.AbortButton = new System.Windows.Forms.Button(); @@ -45,11 +47,13 @@ private void InitializeComponent() // // ExplanationLabel // + this.ExplanationLabel.AutoSize = true; + this.ExplanationLabel.MaximumSize = new System.Drawing.Size(490, 0); this.ExplanationLabel.Dock = System.Windows.Forms.DockStyle.Top; this.ExplanationLabel.Font = new System.Drawing.Font(System.Drawing.SystemFonts.DefaultFont.Name, 12, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Pixel); this.ExplanationLabel.Location = new System.Drawing.Point(5, 0); this.ExplanationLabel.Name = "ExplanationLabel"; - this.ExplanationLabel.Padding = new System.Windows.Forms.Padding(5,5,5,5); + this.ExplanationLabel.Padding = new System.Windows.Forms.Padding(0,0,0,8); this.ExplanationLabel.Size = new System.Drawing.Size(490, 60); resources.ApplyResources(this.ExplanationLabel, "ExplanationLabel"); // @@ -57,6 +61,7 @@ private void InitializeComponent() // this.DownloadsGrid.Dock = System.Windows.Forms.DockStyle.Fill; this.DownloadsGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; + this.DownloadsGrid.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells; this.DownloadsGrid.AllowUserToAddRows = false; this.DownloadsGrid.AllowUserToDeleteRows = false; this.DownloadsGrid.AllowUserToResizeRows = false; @@ -87,6 +92,7 @@ private void InitializeComponent() this.DownloadsGrid.CellMouseDoubleClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.DownloadsGrid_CellMouseDoubleClick); this.DownloadsGrid.CellEndEdit += new System.Windows.Forms.DataGridViewCellEventHandler(this.DownloadsGrid_CellEndEdit); this.DownloadsGrid.SelectionChanged += new System.EventHandler(DownloadsGrid_SelectionChanged); + this.DownloadsGrid.MouseDown += new System.Windows.Forms.MouseEventHandler(this.DownloadsGrid_MouseDown); // // RetryColumn // @@ -112,8 +118,7 @@ private void InitializeComponent() this.ModColumn.DataPropertyName = "Data"; this.ModColumn.ReadOnly = true; this.ModColumn.Width = 250; - this.ModColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; - this.ModColumn.FillWeight = 250; + this.ModColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; resources.ApplyResources(this.ModColumn, "ModColumn"); // // ErrorColumn @@ -124,8 +129,23 @@ private void InitializeComponent() this.ErrorColumn.Width = 500; this.ErrorColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; this.ErrorColumn.FillWeight = 500; + this.ErrorColumn.DefaultCellStyle.WrapMode = System.Windows.Forms.DataGridViewTriState.True; resources.ApplyResources(this.ErrorColumn, "ErrorColumn"); // + // GridContextMenuStrip + // + this.GridContextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.CopyErrorToolStripMenuItem}); + this.GridContextMenuStrip.Name = "GridContextMenuStrip"; + this.GridContextMenuStrip.Size = new System.Drawing.Size(180, 70); + // + // CopyErrorToolStripMenuItem + // + this.CopyErrorToolStripMenuItem.Name = "CopyErrorToolStripMenuItem"; + this.CopyErrorToolStripMenuItem.Size = new System.Drawing.Size(179, 22); + this.CopyErrorToolStripMenuItem.Click += new System.EventHandler(this.CopyErrorToolStripMenuItem_Click); + resources.ApplyResources(this.CopyErrorToolStripMenuItem, "CopyErrorToolStripMenuItem"); + // // BottomButtonPanel // this.BottomButtonPanel.RightControls.Add(this.AbortButton); @@ -187,6 +207,8 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewCheckBoxColumn SkipColumn; private System.Windows.Forms.DataGridViewTextBoxColumn ModColumn; private System.Windows.Forms.DataGridViewTextBoxColumn ErrorColumn; + private System.Windows.Forms.ContextMenuStrip GridContextMenuStrip; + private System.Windows.Forms.ToolStripMenuItem CopyErrorToolStripMenuItem; private CKAN.GUI.LeftRightRowPanel BottomButtonPanel; private System.Windows.Forms.Button RetryButton; private System.Windows.Forms.Button AbortButton; diff --git a/GUI/Dialogs/DownloadsFailedDialog.cs b/GUI/Dialogs/DownloadsFailedDialog.cs index 313bb4957..16bcb7d04 100644 --- a/GUI/Dialogs/DownloadsFailedDialog.cs +++ b/GUI/Dialogs/DownloadsFailedDialog.cs @@ -56,6 +56,10 @@ public DownloadsFailedDialog( Func rowsLinked) { InitializeComponent(); + if (Platform.IsMono) + { + GridContextMenuStrip.Renderer = new FlatToolStripRenderer(); + } ExplanationLabel.Text = TopLabelMessage; ModColumn.HeaderText = ModuleColumnHeader; AbortButton.Text = AbortButtonCaption; @@ -66,14 +70,14 @@ public DownloadsFailedDialog( .ToList(); DownloadsGrid.DataSource = new BindingList(rows); ClientSize = new Size(ClientSize.Width, - ExplanationLabel.Height - + ExplanationLabel.Padding.Vertical - + DownloadsGrid.ColumnHeadersHeight - + (DownloadsGrid.RowCount - * DownloadsGrid.RowTemplate.Height) - + DownloadsGrid.Margin.Vertical - + DownloadsGrid.Padding.Vertical - + BottomButtonPanel.Height); + ExplanationLabel.Height + + ExplanationLabel.Padding.Vertical + + DownloadsGrid.ColumnHeadersHeight + + DownloadsGrid.Rows.OfType() + .Sum(r => r.Height) + + DownloadsGrid.Margin.Vertical + + DownloadsGrid.Padding.Vertical + + BottomButtonPanel.Height); } [ForbidGUICalls] @@ -96,6 +100,13 @@ public DownloadsFailedDialog( .Select(r => r.Data) .ToArray(); + protected override void OnResize(EventArgs e) + { + ExplanationLabel.MaximumSize = new Size(ClientSize.Width - Padding.Horizontal, + ClientSize.Height - Padding.Vertical); + base.OnResize(e); + } + /// /// Open the user guide when the user presses F1 /// @@ -156,6 +167,25 @@ private void DownloadsGrid_CellEndEdit(object sender, DataGridViewCellEventArgs } } + private void DownloadsGrid_MouseDown(object? sender, MouseEventArgs e) + { + if (e is { Button: MouseButtons.Right}) + { + // Show the context menu + GridContextMenuStrip.Show(Cursor.Position); + } + } + + private void CopyErrorToolStripMenuItem_Click(object? sender, EventArgs? e) + { + Clipboard.SetText(string.Join(Environment.NewLine, + DownloadsGrid.Rows + .OfType() + .Select(r => r.DataBoundItem) + .OfType() + .Select(r => r.Error))); + } + private void RetryButton_Click(object? sender, EventArgs? e) { Abort = false; diff --git a/GUI/Dialogs/DownloadsFailedDialog.resx b/GUI/Dialogs/DownloadsFailedDialog.resx index 3537347e0..b3bddc9a0 100644 --- a/GUI/Dialogs/DownloadsFailedDialog.resx +++ b/GUI/Dialogs/DownloadsFailedDialog.resx @@ -121,5 +121,6 @@ Retry? Skip? Error + Copy errors to clipboard Retry diff --git a/GUI/Main/Main.Designer.cs b/GUI/Main/Main.Designer.cs index e266de4a8..757af49d9 100644 --- a/GUI/Main/Main.Designer.cs +++ b/GUI/Main/Main.Designer.cs @@ -542,6 +542,7 @@ private void InitializeComponent() this.ManageMods.ClearStatusBar += this.ManageMods_ClearStatusBar; this.ManageMods.LaunchGame += this.LaunchGame; this.ManageMods.EditCommandLines += this.EditCommandLines; + this.ManageMods.DownloadModules += this.StartDownloads; // // ChangesetTabPage // diff --git a/GUI/Main/MainDownload.cs b/GUI/Main/MainDownload.cs index 350ccdc2a..67c565e3a 100644 --- a/GUI/Main/MainDownload.cs +++ b/GUI/Main/MainDownload.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using CKAN.GUI.Attributes; +using CKAN.Extensions; namespace CKAN.GUI { @@ -18,7 +19,7 @@ private void ModInfo_OnDownloadClick(GUIMod gmod) StartDownload(gmod); } - public void StartDownloads(IEnumerable modules) + private void StartDownloads(IReadOnlyCollection modules) { ShowWaitDialog(); if (downloader != null) @@ -36,9 +37,9 @@ public void StartDownloads(IEnumerable modules) } } - public void StartDownload(GUIMod module) + private void StartDownload(GUIMod module) { - StartDownloads(Enumerable.Repeat(module, 1)); + StartDownloads(new GUIMod[] { module }); } [ForbidGUICalls] @@ -59,7 +60,8 @@ private void CacheMods(object? sender, DoWorkEventArgs? e) { try { - downloader.DownloadModules(modules.Select(m => m.Module)); + downloader.DownloadModules(modules.Where(m => !m.IsCached) + .Select(m => m.Module)); done = true; } catch (ModuleDownloadErrorsKraken k) @@ -70,9 +72,13 @@ private void CacheMods(object? sender, DoWorkEventArgs? e) dfd = new DownloadsFailedDialog( Properties.Resources.ModDownloadsFailedMessage, Properties.Resources.ModDownloadsFailedColHdr, - Properties.Resources.ModDownloadsFailedAbortBtn, + Properties.Resources.ModDownloadsFailedAbortBtnNotInstalling, k.Exceptions.Select(kvp => new KeyValuePair( - modules.Select(m => m.Module).ToArray(), kvp.Value)), + modules.Select(m => m.Module) + .Where(m => (m.download ?? Enumerable.Empty()) + .IntersectsWith(kvp.Key?.download ?? Enumerable.Empty())) + .ToArray(), + kvp.Value)), (m1, m2) => (m1 as CkanModule)?.download == (m2 as CkanModule)?.download); dfd.ShowDialog(this); }); diff --git a/GUI/Main/MainExport.cs b/GUI/Main/MainExport.cs index 3c96d7d90..2f9ab8899 100644 --- a/GUI/Main/MainExport.cs +++ b/GUI/Main/MainExport.cs @@ -48,8 +48,8 @@ private void EditModpack_OnSelectedItemsChanged(ListView.SelectedListViewItemCol { var ident = first.name; if (!string.IsNullOrEmpty(ident) - && ManageMods.mainModList != null - && ManageMods.mainModList.full_list_of_mod_rows.TryGetValue(ident, out DataGridViewRow? row)) + && ManageMods.MainModList != null + && ManageMods.MainModList.full_list_of_mod_rows.TryGetValue(ident, out DataGridViewRow? row)) { ActiveModInfo = row.Tag as GUIMod; } diff --git a/GUI/Main/MainHistory.cs b/GUI/Main/MainHistory.cs index 7bc7f0b22..66dbb20e7 100644 --- a/GUI/Main/MainHistory.cs +++ b/GUI/Main/MainHistory.cs @@ -24,10 +24,10 @@ private void InstallationHistoryToolStripMenuItem_Click(object? sender, EventArg private void InstallationHistory_Install(CkanModule[] modules) { - if (CurrentInstance != null && ManageMods.mainModList != null) + if (CurrentInstance != null && ManageMods.MainModList != null) { InstallationHistory_Done(); - var tuple = ManageMods.mainModList.ComputeFullChangeSetFromUserChangeSet( + var tuple = ManageMods.MainModList.ComputeFullChangeSetFromUserChangeSet( RegistryManager.Instance(CurrentInstance, repoData).registry, modules.Select(mod => new ModChange(mod, GUIModChangeType.Install, ServiceLocator.Container.Resolve())) diff --git a/GUI/Main/MainImport.cs b/GUI/Main/MainImport.cs index 8758acf43..aad8bbb00 100644 --- a/GUI/Main/MainImport.cs +++ b/GUI/Main/MainImport.cs @@ -44,14 +44,14 @@ private void ImportModules() Wait.StartWaiting( (sender, e) => { - if (e != null && Manager?.Cache != null && ManageMods.mainModList != null) + if (e != null && Manager?.Cache != null && ManageMods.MainModList != null) { e.Result = ModuleImporter.ImportFiles( GetFiles(dlg.FileNames), currentUser, mod => { - if (ManageMods.mainModList + if (ManageMods.MainModList .full_list_of_mod_rows .TryGetValue(mod.identifier, out DataGridViewRow? row) diff --git a/GUI/Main/MainInstall.cs b/GUI/Main/MainInstall.cs index e93fea466..343614537 100644 --- a/GUI/Main/MainInstall.cs +++ b/GUI/Main/MainInstall.cs @@ -14,6 +14,7 @@ using CKAN.IO; using CKAN.GUI.Attributes; using CKAN.Configuration; +using CKAN.Extensions; // Don't warn if we use our own obsolete properties #pragma warning disable 0618 @@ -286,7 +287,9 @@ private void InstallMods(object? sender, DoWorkEventArgs? e) Properties.Resources.ModDownloadsFailedColHdr, Properties.Resources.ModDownloadsFailedAbortBtn, k.Exceptions.Select(kvp => new KeyValuePair( - fullChangeset.Where(m => m.download == kvp.Key.download).ToArray(), + fullChangeset.Where(m => (m.download ?? Enumerable.Empty()) + .IntersectsWith(kvp.Key.download ?? Enumerable.Empty())) + .ToArray(), kvp.Value)), (m1, m2) => (m1 as CkanModule)?.download == (m2 as CkanModule)?.download); dfd.ShowDialog(this); diff --git a/GUI/Main/MainRepo.cs b/GUI/Main/MainRepo.cs index 3bc5d311e..1dce7f217 100644 --- a/GUI/Main/MainRepo.cs +++ b/GUI/Main/MainRepo.cs @@ -311,7 +311,7 @@ private void OnRefreshTimer(object? sender, ElapsedEventArgs e) private void UpgradeNotification() { - if (ManageMods.mainModList?.Modules.Count(mod => mod.HasUpdate) is > 0 and int numUpgradeable) + if (ManageMods.MainModList?.Modules.Count(mod => mod.HasUpdate) is > 0 and int numUpgradeable) { Util.Invoke(this, () => { diff --git a/GUI/Main/MainTrayIcon.cs b/GUI/Main/MainTrayIcon.cs index 58f52e0c8..e54cc3bce 100644 --- a/GUI/Main/MainTrayIcon.cs +++ b/GUI/Main/MainTrayIcon.cs @@ -55,9 +55,9 @@ private void UpdateTrayState() private void UpdateTrayInfo() { - if (CurrentInstance != null && ManageMods.mainModList != null) + if (CurrentInstance != null && ManageMods.MainModList != null) { - var count = ManageMods.mainModList.CountModsByFilter(CurrentInstance, + var count = ManageMods.MainModList.CountModsByFilter(CurrentInstance, GUIModFilter.InstalledUpdateAvailable); if (count == 0) { diff --git a/GUI/Properties/Resources.resx b/GUI/Properties/Resources.resx index 156a22f75..c916b0747 100644 --- a/GUI/Properties/Resources.resx +++ b/GUI/Properties/Resources.resx @@ -450,9 +450,10 @@ Or choose "Update repositories on launch" in the settings. Remove Update Replace - The following mods failed to download. You can retry or abort, and if you retry, you can choose to remove some mods (and any mods that depend on them) from the changeset. Mods that share the same download link will automatically be updated to the same choice. + The following mods failed to download. You can retry or abort, and if you retry, you can choose to remove some mods (and any mods that depend on them). Mods that share the same download link will automatically be updated to the same choice. Mod Abort whole changeset + Abort download The following repositories failed to download. You can retry or abort, and if you retry, you can choose to remove some repositories. Note that any repositories you skip will be PERMANENTLY removed from your configuration! Repository Abort whole update diff --git a/Tests/Core/Extensions/EnumerableExtensionsTests.cs b/Tests/Core/Extensions/EnumerableExtensionsTests.cs index c8cb2483b..751c8ebec 100644 --- a/Tests/Core/Extensions/EnumerableExtensionsTests.cs +++ b/Tests/Core/Extensions/EnumerableExtensionsTests.cs @@ -10,6 +10,15 @@ namespace Tests.Core.Extensions [TestFixture] public class EnumerableExtensionsTests { + [TestCase(new string[] { "A", "B", "C", "D" }, + new string[] { "A", "S", "D", "F" }, + ExpectedResult = true), + TestCase(new string[] { "A", "B", "C", "D" }, + new string[] { "E", "F", "G", "H" }, + ExpectedResult = false)] + public bool IntersectsWith_Examples_Works(string[] a, string[] b) + => a.IntersectsWith(b); + [TestCaseSource(nameof(TimeSpanCases))] public TimeSpan Sum_Timespans_Works(TimeSpan[] spans) => spans.Sum();