• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

KSP-CKAN / CKAN / 15432034930

04 Jun 2025 02:19AM UTC coverage: 30.322% (+0.04%) from 30.28%
15432034930

Pull #4386

github

web-flow
Merge f8b59bcd0 into 4cf303cc8
Pull Request #4386: Mod list multi-select

4063 of 14340 branches covered (28.33%)

Branch coverage included in aggregate %.

55 of 170 new or added lines in 12 files covered. (32.35%)

59 existing lines in 5 files now uncovered.

13712 of 44281 relevant lines covered (30.97%)

0.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/GUI/Controls/ChooseRecommendedMods.cs
1
using System;
2
using System.Drawing;
3
using System.Collections.Generic;
4
using System.Linq;
5
using System.Threading.Tasks;
6
using System.Windows.Forms;
7
#if NET5_0_OR_GREATER
8
using System.Runtime.Versioning;
9
#endif
10

11
using CKAN.Configuration;
12
using CKAN.Versioning;
13
using CKAN.Games;
14
using CKAN.GUI.Attributes;
15

16
namespace CKAN.GUI
17
{
18
    #if NET5_0_OR_GREATER
19
    [SupportedOSPlatform("windows")]
20
    #endif
21
    public partial class ChooseRecommendedMods : UserControl
22
    {
23
        public ChooseRecommendedMods()
×
24
        {
×
25
            InitializeComponent();
×
26
        }
×
27

28
        [ForbidGUICalls]
29
        public void LoadRecommendations(IRegistryQuerier        registry,
30
                                        ICollection<CkanModule> toInstall,
31
                                        ICollection<CkanModule> toUninstall,
32
                                        GameVersionCriteria     versionCrit,
33
                                        NetModuleCache          cache,
34
                                        IGame                   game,
35
                                        List<ModuleLabel>       labels,
36
                                        IConfiguration          coreConfig,
37
                                        GUIConfiguration        guiConfig,
38
                                        Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
39
                                        Dictionary<CkanModule, List<string>>              suggestions,
40
                                        Dictionary<CkanModule, HashSet<string>>           supporters)
41
        {
×
42
            this.registry    = registry;
×
43
            this.toInstall   = toInstall;
×
44
            this.toUninstall = toUninstall;
×
45
            this.versionCrit = versionCrit;
×
46
            this.guiConfig   = guiConfig;
×
47
            this.game        = game;
×
48
            Util.Invoke(this, () =>
×
49
            {
×
50
                AlwaysUncheckAllButton.Checked = guiConfig?.SuppressRecommendations ?? false;
×
51
                RecommendedModsListView.BeginUpdate();
×
52
                RecommendedModsListView.ItemChecked -= RecommendedModsListView_ItemChecked;
×
53
                RecommendedModsListView.Items.AddRange(
×
54
                    getRecSugRows(cache, game,
55
                                  labels.Where(mlbl => mlbl.HoldVersion || mlbl.Hide)
×
56
                                        .ToArray(),
57
                                  recommendations, suggestions, supporters,
58
                                  coreConfig).ToArray());
59
                MarkConflicts();
×
60
                EnableDisableButtons();
×
61
                RecommendedModsListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
×
62
                RecommendedModsListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
×
63
                RecommendedModsListView_ColumnWidthChanged(null, null);
×
64
                RecommendedModsListView.EndUpdate();
×
65
                // Don't set this before AddRange, it will fire for every row we add!
66
                RecommendedModsListView.ItemChecked += RecommendedModsListView_ItemChecked;
×
67
            });
×
68
        }
×
69

70
        [ForbidGUICalls]
71
        public HashSet<CkanModule>? Wait()
72
        {
×
73
            if (Platform.IsMono)
×
74
            {
×
75
                // Workaround: make sure the ListView headers are drawn
76
                Util.Invoke(this, RecommendedModsListView.EndUpdate);
×
77
            }
×
78
            task = new TaskCompletionSource<HashSet<CkanModule>?>();
×
79
            return task.Task.Result;
×
80
        }
×
81

82
        public ListView.SelectedListViewItemCollection SelectedItems
83
            => RecommendedModsListView.SelectedItems;
×
84

85
        public event Action<ListView.SelectedListViewItemCollection>? OnSelectedItemsChanged;
86

87
        public event Action<string>? OnConflictFound;
88

89
        protected override void OnResize(EventArgs e)
90
        {
×
91
            base.OnResize(e);
×
92
            RecommendedModsListView_ColumnWidthChanged(null, null);
×
93
        }
×
94

95
        private void RecommendedModsListView_ColumnWidthChanged(object? sender, ColumnWidthChangedEventArgs? args)
96
        {
×
97
            if (args?.ColumnIndex != DescriptionHeader.Index)
×
98
            {
×
99
                try
100
                {
×
101
                    DescriptionHeader.Width =
×
102
                        RecommendedModsListView.Width
103
                        - ModNameHeader.Width
104
                        - SourceModulesHeader.Width
105
                        - SystemInformation.VerticalScrollBarWidth
106
                        - (2 * SystemInformation.BorderSize.Width);
107
                }
×
108
                catch
×
109
                {
×
110
                    // Don't freak out if we get a negative width
111
                }
×
112
            }
×
113
        }
×
114

115
        private void RecommendedModsListView_SelectedIndexChanged(object? sender, EventArgs? e)
116
        {
×
117
            OnSelectedItemsChanged?.Invoke(RecommendedModsListView.SelectedItems);
×
118
        }
×
119

120
        private void RecommendedModsListView_ItemChecked(object? sender, ItemCheckedEventArgs? e)
121
        {
×
122
            var module = e?.Item.Tag as CkanModule;
×
123
            if (module?.IsDLC ?? false)
×
124
            {
×
125
                if (e != null && e.Item.Checked)
×
126
                {
×
127
                    e.Item.Checked = false;
×
128
                }
×
129
            }
×
130
            else
131
            {
×
132
                MarkConflicts();
×
133
                EnableDisableButtons();
×
134
            }
×
135
        }
×
136

137
        private void MarkConflicts()
138
        {
×
139
            if (registry != null && versionCrit != null && game != null
×
140
                && Main.Instance?.CurrentInstance is GameInstance inst)
141
            {
×
142
                try
143
                {
×
144
                    var resolver = new RelationshipResolver(
×
145
                        RecommendedModsListView.CheckedItems
146
                                               .OfType<ListViewItem>()
147
                                               .Select(item => item.Tag as CkanModule)
×
148
                                               .OfType<CkanModule>()
149
                                               .Concat(toInstall)
150
                                               .Distinct(),
151
                        toUninstall,
152
                        RelationshipResolverOptions.ConflictsOpts(inst.StabilityToleranceConfig),
153
                        registry, game, versionCrit);
154
                    var conflicts = resolver.ConflictList;
×
155
                    foreach (var item in RecommendedModsListView.Items.Cast<ListViewItem>()
×
156
                        // Apparently ListView handes AddRange by:
157
                        //   1. Expanding the Items list to the new size by filling it with nulls
158
                        //   2. One by one, replace each null with a real item and call _ItemChecked
159
                        // ... so the Items list can contain null!!
160
                        .OfType<ListViewItem>())
161
                    {
×
162
                        item.BackColor = item.Tag is CkanModule m && conflicts.ContainsKey(m)
×
163
                            ? Color.LightCoral
164
                            : Color.Empty;
165
                    }
×
166
                    RecommendedModsContinueButton.Enabled = conflicts.Count == 0;
×
167
                    OnConflictFound?.Invoke(string.Join("; ", resolver.ConflictDescriptions));
×
168
                }
×
169
                catch (DependenciesNotSatisfiedKraken k)
×
170
                {
×
171
                    var rows = RecommendedModsListView.Items
×
172
                                                      .OfType<ListViewItem>()
173
                                                      .Where(item => item.Tag is CkanModule mod
×
174
                                                                     && k.unsatisfied.Any(stack =>
175
                                                                         stack.Any(rr => rr.Contains(mod))));
×
176
                    foreach (var row in rows)
×
177
                    {
×
178
                        row.BackColor = Color.LightCoral;
×
179
                    }
×
180
                    RecommendedModsContinueButton.Enabled = false;
×
181
                    OnConflictFound?.Invoke(k.Message);
×
182
                }
×
183
            }
×
184
        }
×
185

186
        private IEnumerable<ListViewItem> getRecSugRows(
187
            NetModuleCache                                    cache,
188
            IGame                                             game,
189
            ModuleLabel[]                                     uncheckLabels,
190
            Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
191
            Dictionary<CkanModule, List<string>>              suggestions,
192
            Dictionary<CkanModule, HashSet<string>>           supporters,
193
            IConfiguration                                    coreConfig)
194
            => recommendations.Select(kvp => getRecSugItem(cache,
×
195
                                                           kvp.Key,
196
                                                           string.Join(", ", kvp.Value.Item2),
197
                                                           RecommendationsGroup,
198
                                                           (!guiConfig?.SuppressRecommendations ?? true)
199
                                                               && kvp.Value.Item1
200
                                                               && !uncheckLabels.Any(mlbl =>
201
                                                                   mlbl.ContainsModule(game, kvp.Key.identifier)),
×
202
                                                           coreConfig))
203
                              .OrderBy(SecondColumn)
204
                              .Concat(suggestions.Select(kvp => getRecSugItem(cache,
×
205
                                                                              kvp.Key,
206
                                                                              string.Join(", ", kvp.Value),
207
                                                                              SuggestionsGroup,
208
                                                                              false,
209
                                                                              coreConfig))
210
                                                 .OrderBy(SecondColumn))
211
                              .Concat(supporters.Select(kvp => getRecSugItem(cache,
×
212
                                                                             kvp.Key,
213
                                                                             string.Join(", ", kvp.Value.OrderBy(s => s)),
×
214
                                                                             SupportedByGroup,
215
                                                                             false,
216
                                                                             coreConfig))
217
                                                .OrderBy(SecondColumn));
218

219
        private string SecondColumn(ListViewItem item)
220
            => //item.SubItems is [_, {Text: string text}, ..]
221
               item.SubItems.Count > 1
×
222
               && item.SubItems[0].Text is string text
223
                   ? text
224
                   : "";
225

226
        private static ListViewItem getRecSugItem(NetModuleCache cache,
227
                                                  CkanModule     module,
228
                                                  string         descrip,
229
                                                  ListViewGroup  group,
230
                                                  bool           check,
231
                                                  IConfiguration config)
232
        => new ListViewItem(new string[]
×
233
            {
234
                module.IsDLC ? module.name : cache.DescribeAvailability(config, module),
235
                descrip,
236
                module.@abstract
237
            })
238
            {
239
                Tag     = module,
240
                Checked = check && !module.IsDLC,
241
                Group   = group
242
            };
243

244
        private void UncheckAllButton_Click(object? sender, EventArgs? e)
245
        {
×
246
            CheckUncheckRows(RecommendedModsListView.Items, false);
×
247
        }
×
248

249
        private void AlwaysUncheckAllButton_CheckedChanged(object? sender, EventArgs? e)
250
        {
×
NEW
251
            if (guiConfig != null && guiConfig.SuppressRecommendations != AlwaysUncheckAllButton.Checked
×
252
                && Main.Instance?.CurrentInstance is GameInstance inst)
253
            {
×
254
                guiConfig.SuppressRecommendations = AlwaysUncheckAllButton.Checked;
×
NEW
255
                guiConfig.Save(inst);
×
256
                if (guiConfig.SuppressRecommendations)
×
257
                {
×
258
                    UncheckAllButton_Click(null, null);
×
259
                }
×
260
            }
×
261
        }
×
262

263
        private void CheckAllButton_Click(object? sender, EventArgs? e)
264
        {
×
265
            CheckUncheckRows(RecommendedModsListView.Items, true);
×
266
        }
×
267

268
        private void CheckRecommendationsButton_Click(object? sender, EventArgs? e)
269
        {
×
270
            CheckUncheckRows(RecommendationsGroup.Items, true);
×
271
        }
×
272

273
        private void CheckSuggestionsButton_Click(object? sender, EventArgs? e)
274
        {
×
275
            CheckUncheckRows(SuggestionsGroup.Items, true);
×
276
        }
×
277

278
        private void CheckUncheckRows(ListView.ListViewItemCollection items,
279
                                      bool check)
280
        {
×
281
            RecommendedModsListView.BeginUpdate();
×
282
            RecommendedModsListView.ItemChecked -= RecommendedModsListView_ItemChecked;
×
283
            foreach (ListViewItem item in items)
×
284
            {
×
285
                if (item.Checked != check && NotDLC(item))
×
286
                {
×
287
                    item.Checked = check;
×
288
                }
×
289
            }
×
290
            MarkConflicts();
×
291
            EnableDisableButtons();
×
292
            RecommendedModsListView.EndUpdate();
×
293
            RecommendedModsListView.ItemChecked += RecommendedModsListView_ItemChecked;
×
294
        }
×
295

296
        private void EnableDisableButtons()
297
        {
×
298
            CheckAllButton.Enabled = RecommendedModsListView.Items
×
299
                                                            .OfType<ListViewItem>()
300
                                                            .Where(NotDLC)
301
                                                            .Any(lvi => !lvi.Checked);
×
302
            CheckRecommendationsButton.Enabled = RecommendationsGroup.Items
×
303
                                                                     .OfType<ListViewItem>()
304
                                                                     .Where(NotDLC)
305
                                                                     .Any(lvi => !lvi.Checked);
×
306
        }
×
307

308
        private bool NotDLC(ListViewItem item)
309
            => item.Tag is CkanModule mod && !mod.IsDLC;
×
310

311
        private void RecommendedModsCancelButton_Click(object? sender, EventArgs? e)
312
        {
×
313
            task?.SetResult(null);
×
314
            RecommendedModsListView.Items.Clear();
×
315
            RecommendedModsListView.ItemChecked -= RecommendedModsListView_ItemChecked;
×
316
        }
×
317

318
        private void RecommendedModsContinueButton_Click(object? sender, EventArgs? e)
319
        {
×
320
            task?.SetResult(RecommendedModsListView.CheckedItems
×
321
                                                   .OfType<ListViewItem>()
322
                                                   .Select(item => item.Tag as CkanModule)
×
323
                                                   .OfType<CkanModule>()
324
                                                   .ToHashSet());
325
            RecommendedModsListView.Items.Clear();
×
326
            RecommendedModsListView.ItemChecked -= RecommendedModsListView_ItemChecked;
×
327
        }
×
328

329
        private IRegistryQuerier?       registry;
330
        private ICollection<CkanModule> toInstall   = Array.Empty<CkanModule>();
×
331
        private ICollection<CkanModule> toUninstall = Array.Empty<CkanModule>();
×
332
        private GameVersionCriteria?    versionCrit;
333
        private GUIConfiguration?       guiConfig;
334
        private IGame?                  game;
335
        private TaskCompletionSource<HashSet<CkanModule>?>? task;
336
    }
337
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc