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

KSP-CKAN / CKAN / 18890346257

28 Oct 2025 09:56PM UTC coverage: 85.347% (+3.5%) from 81.873%
18890346257

Pull #4454

github

HebaruSan
Build on Windows, upload multi-platform coverage
Pull Request #4454: Build on Windows, upload multi-platform coverage

2005 of 2167 branches covered (92.52%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 3 files covered. (100.0%)

27 existing lines in 19 files now uncovered.

11974 of 14212 relevant lines covered (84.25%)

1.76 hits per line

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

74.44
/GUI/Model/ModList.cs
1
using System;
2
using System.Drawing;
3
using System.Collections.Generic;
4
using System.Linq;
5
using System.Windows.Forms;
6
#if NET5_0_OR_GREATER
7
using System.Runtime.Versioning;
8
#endif
9

10
using Autofac;
11
using log4net;
12

13
using CKAN.Configuration;
14
using CKAN.Versioning;
15
using CKAN.Games;
16
using CKAN.Extensions;
17

18
namespace CKAN.GUI
19
{
20
    /// <summary>
21
    /// The holder of the list of mods to be shown.
22
    /// Should be a pure data model and avoid UI stuff, but it's not there yet.
23
    /// </summary>
24
    #if NET5_0_OR_GREATER
25
    [SupportedOSPlatform("windows")]
26
    #endif
27
    public class ModList
28
    {
29
        /// <summary>
30
        /// Constructs the mod list suitable for display to the user.
31
        /// </summary>
32
        /// <param name="modules">A list of modules that may require updating</param>
33
        /// <param name="instance">Game instance for getting labels</param>
34
        /// <param name="allLabels">All label definitions</param>
35
        /// <param name="allTags">All tag definitions</param>
36
        /// <param name="coreConfig">Core configuration</param>
37
        /// <param name="guiConfig">GUI configuration</param>
38
        /// <param name="mc">Changes the user has made</param>
39
        /// <returns>The mod list</returns>
40
        public ModList(IReadOnlyCollection<GUIMod> modules,
2✔
41
                       GameInstance                instance,
42
                       ModuleLabelList             allLabels,
43
                       ModuleTagList               allTags,
44
                       IConfiguration              coreConfig,
45
                       GUIConfiguration            guiConfig,
46
                       List<ModChange>?            mc = null)
47
        {
2✔
48
            this.allLabels        = allLabels;
2✔
49
            this.allTags          = allTags;
2✔
50
            this.coreConfig       = coreConfig;
2✔
51
            this.guiConfig        = guiConfig;
2✔
52
            activeSearches        = guiConfig.DefaultSearches
2✔
53
                                             ?.Select(s => ModSearch.Parse(allLabels, instance, s))
×
54
                                              .OfType<ModSearch>()
55
                                              .ToArray()
56
                                             ?? Array.Empty<ModSearch>();
57
            Modules               = modules;
2✔
58
            HasAnyInstalled       = Modules.Any(m => m.IsInstalled);
2✔
59
            full_list_of_mod_rows = Modules.AsParallel()
2✔
60
                                           .ToDictionary(gm => gm.Identifier,
2✔
61
                                                         gm => MakeRow(gm, mc, instance));
2✔
62
        }
2✔
63

64
        // identifier => row
65
        public readonly IReadOnlyCollection<GUIMod>         Modules;
66
        public readonly bool                                HasAnyInstalled;
67
        public readonly Dictionary<string, DataGridViewRow> full_list_of_mod_rows;
68
        public event    Action?                             ModFiltersUpdated;
69

70
        // Unlike GUIMod.IsInstalled, DataGridViewRow.Visible can change on the fly without notifying us
71
        public bool HasVisibleInstalled()
72
            => full_list_of_mod_rows.Values.Any(row => ((row.Tag as GUIMod)?.IsInstalled ?? false)
2✔
73
                                                       && row.Visible);
74

75
        public void SetSearches(List<ModSearch> newSearches)
76
        {
2✔
77
            if (!SearchesEqual(activeSearches, newSearches))
2✔
78
            {
2✔
79
                activeSearches = newSearches;
2✔
80
                guiConfig.DefaultSearches = activeSearches.Select(s => s.Combined ?? "")
2✔
81
                                                          .ToList();
82
                ModFiltersUpdated?.Invoke();
2✔
83
            }
2✔
84
        }
2✔
85

86
        public static SavedSearch FilterToSavedSearch(GameInstance    instance,
87
                                                      GUIModFilter    filter,
88
                                                      ModuleLabelList allLabels,
89
                                                      ModuleTag?      tag   = null,
90
                                                      ModuleLabel?    label = null)
91
            => new SavedSearch()
2✔
92
            {
93
                Name   = FilterName(filter, tag, label),
94
                Values = new List<string>()
95
                         {
96
                             new ModSearch(allLabels, instance,
97
                                           filter, tag, label)
98
                                 .Combined
99
                             ?? ""
100
                         },
101
            };
102

103
        public bool IsVisible(GUIMod mod, GameInstance instance, Registry registry)
UNCOV
104
            => activeSearches.IsEmptyOrAny(s => s.Matches(mod))
✔
105
               && !HiddenByTagsOrLabels(mod, instance, registry);
106

107
        public int CountModsBySearches(List<ModSearch> searches)
UNCOV
108
            => Modules.Count(mod => searches?.Any(s => s?.Matches(mod) ?? true) ?? true);
×
109

110
        public int CountModsByFilter(GameInstance inst, GUIModFilter filter)
111
            => CountModsBySearches(new List<ModSearch>() { new ModSearch(allLabels, inst, filter, null, null) });
2✔
112

113
        private Color GetRowBackground(GUIMod mod, bool conflicted, GameInstance instance)
114
            => conflicted
2✔
115
                   ? conflictColor
116
                   : Util.BlendColors(allLabels.LabelsFor(instance.Name)
117
                                               .Where(l => l.ContainsModule(instance.Game,
2✔
118
                                                                            mod.Identifier))
119
                                               .Select(l => l.Color)
2✔
120
                                               .OfType<Color>()
121
                                               // No transparent blending
122
                                               .Where(c => c.A == byte.MaxValue)
2✔
123
                                               .ToArray());
124

125
        /// <summary>
126
        /// Update the color and visible state of the given row
127
        /// after it has been added to or removed from a label group
128
        /// </summary>
129
        /// <param name="mod">The mod that needs an update</param>
130
        /// <param name="conflicted">True if mod should have a red background</param>
131
        /// <param name="instance">Game instance for finding labels</param>
132
        /// <param name="registry">Registry for finding mods</param>
133
        public DataGridViewRow? ReapplyLabels(GUIMod mod, bool conflicted,
134
                                              GameInstance instance, Registry registry)
135
        {
2✔
136
            if (full_list_of_mod_rows.TryGetValue(mod.Identifier, out DataGridViewRow? row))
2✔
137
            {
2✔
138
                row.DefaultCellStyle.BackColor = GetRowBackground(mod, conflicted, instance);
2✔
139
                row.DefaultCellStyle.ForeColor = row.DefaultCellStyle.BackColor.ForeColorForBackColor()
2✔
140
                                                 ?? SystemColors.WindowText;
141
                row.DefaultCellStyle.SelectionBackColor = SelectionBlend(row.DefaultCellStyle.BackColor);
2✔
142
                row.DefaultCellStyle.SelectionForeColor = row.DefaultCellStyle.SelectionBackColor.ForeColorForBackColor()
2✔
143
                                                          ?? SystemColors.HighlightText;
144
                row.Visible = IsVisible(mod, instance, registry);
2✔
145
                return row;
2✔
146
            }
147
            return null;
×
148
        }
2✔
149

150
        public HashSet<ModChange> ComputeUserChangeSet(IRegistryQuerier     registry,
151
                                                       GameInstance         instance,
152
                                                       DataGridViewColumn?  upgradeCol,
153
                                                       DataGridViewColumn?  replaceCol)
154
        {
2✔
155
            log.Debug("Computing user changeset");
2✔
156
            var modChanges = (full_list_of_mod_rows?.Values
2✔
157
                                                    .SelectMany(row => rowChanges(registry, row, upgradeCol, replaceCol))
2✔
158
                                                   ?? Enumerable.Empty<ModChange>())
159
                                                   .ToList();
160

161
            // Inter-mod dependencies can block some upgrades, which can sometimes but not always
162
            // be overcome by upgrading both mods. Try to pick the right target versions.
163
            var upgrades = modChanges.OfType<ModUpgrade>()
2✔
164
                                     // Skip reinstalls
165
                                     .Where(upg => upg.Mod != upg.targetMod)
2✔
166
                                     .ToArray();
167
            if (upgrades.Length > 0)
2✔
168
            {
2✔
169
                var upgradeable = registry.CheckUpgradeable(instance,
2✔
170
                                                            // Hold identifiers not chosen for upgrading
171
                                                            registry.Installed(false)
172
                                                                    .Select(kvp => kvp.Key)
2✔
173
                                                                    .Except(upgrades.Select(ch => ch.Mod.identifier))
2✔
174
                                                                    .ToHashSet(),
175
                                                            allLabels.IgnoreMissingIdentifiers(instance)
176
                                                                     .ToHashSet())
177
                                          [true]
178
                                          .ToDictionary(m => m.identifier,
2✔
179
                                                        m => m);
2✔
180
                foreach (var change in upgrades)
8✔
181
                {
2✔
182
                    change.targetMod = upgradeable.TryGetValue(change.Mod.identifier,
2✔
183
                                                               out CkanModule? allowedMod)
184
                        // Upgrade to the version the registry says we should
185
                        ? allowedMod
186
                        // Not upgradeable!
187
                        : change.Mod;
188
                    if (change.Mod == change.targetMod)
2✔
189
                    {
×
190
                        // This upgrade was voided by dependencies or conflicts
191
                        modChanges.Remove(change);
×
192
                    }
×
193
                }
2✔
194
            }
2✔
195

196
            return modChanges.Union(
2✔
197
                    registry.FindRemovableAutoInstalled(InstalledAfterChanges(registry, modChanges).ToArray(),
198
                                                        Array.Empty<CkanModule>(),
199
                                                        instance.Game, instance.StabilityToleranceConfig,
200
                                                        instance.VersionCriteria())
201
                        .Select(im => new ModChange(
×
202
                            im.Module, GUIModChangeType.Remove,
203
                            new SelectionReason.NoLongerUsed(),
204
                            coreConfig)))
205
                .ToHashSet();
206
        }
2✔
207

208
        /// <summary>
209
        /// Returns a changeset and conflicts based on the selections of the user.
210
        /// </summary>
211
        /// <param name="registry">The registry for getting available mods</param>
212
        /// <param name="changeSet">User's choices of installation and removal</param>
213
        /// <param name="game">Game of the game instance</param>
214
        /// <param name="stabilityTolerance">Prerelease configuration</param>
215
        /// <param name="version">The version of the current game instance</param>
216
        public Tuple<ICollection<ModChange>, Dictionary<CkanModule, string>, List<string>> ComputeFullChangeSetFromUserChangeSet(
217
            IRegistryQuerier         registry,
218
            HashSet<ModChange>       changeSet,
219
            IGame                    game,
220
            StabilityToleranceConfig stabilityTolerance,
221
            GameVersionCriteria      version)
222
        {
2✔
223
            var modules_to_install = new List<CkanModule>();
2✔
224
            var modules_to_remove = new HashSet<CkanModule>();
2✔
225
            var extraInstalls = new HashSet<CkanModule>();
2✔
226

227
            changeSet.UnionWith(changeSet.Where(ch => ch.ChangeType == GUIModChangeType.Replace)
2✔
228
                                         .Select(ch => registry.GetReplacement(ch.Mod, stabilityTolerance, version))
×
229
                                         .OfType<ModuleReplacement>()
230
                                         .GroupBy(repl => repl.ReplaceWith)
×
231
                                         .Select(grp => new ModChange(grp.Key, GUIModChangeType.Install,
×
232
                                                                      grp.Select(repl => new SelectionReason.Replacement(repl.ToReplace)),
×
233
                                                                      coreConfig))
234
                                         .ToHashSet());
235

236
            foreach (var change in changeSet)
8✔
237
            {
2✔
238
                switch (change.ChangeType)
2✔
239
                {
240
                    case GUIModChangeType.None:
241
                        break;
×
242
                    case GUIModChangeType.Update:
243
                        var mod = (change as ModUpgrade)?.targetMod ?? change.Mod;
×
244
                        modules_to_install.Add(mod);
×
245
                        extraInstalls.Add(mod);
×
246
                        break;
×
247
                    case GUIModChangeType.Install:
248
                        modules_to_install.Add(change.Mod);
2✔
249
                        break;
2✔
250
                    case GUIModChangeType.Remove:
251
                        modules_to_remove.Add(change.Mod);
×
252
                        break;
×
253
                    case GUIModChangeType.Replace:
254
                        if (registry.GetReplacement(change.Mod, stabilityTolerance, version) is ModuleReplacement repl)
×
255
                        {
×
256
                            modules_to_remove.Add(repl.ToReplace);
×
257
                            extraInstalls.Add(repl.ReplaceWith);
×
258
                        }
×
259
                        break;
×
260
                    default:
261
                        throw new ArgumentOutOfRangeException(nameof(change),
×
262
                                                              change.ChangeType.ToString());
263
                }
264
            }
2✔
265

266
            var installed_modules = registry.InstalledModules.ToDictionary(
2✔
267
                imod => imod.Module.identifier,
×
268
                imod => imod.Module);
×
269

270
            foreach (var dependent in registry.FindReverseDependencies(
6✔
271
                modules_to_remove
272
                    .Select(mod => mod.identifier)
×
273
                    .Except(modules_to_install.Select(m => m.identifier))
2✔
274
                    .ToList(),
275
                modules_to_install))
276
            {
×
277
                if (!changeSet.Any(ch => ch.ChangeType == GUIModChangeType.Replace
×
278
                                      && ch.Mod.identifier == dependent)
279
                    && installed_modules.TryGetValue(dependent, out CkanModule? depMod)
280
                    && (registry.GetModuleByVersion(depMod.identifier, depMod.version)
281
                        ?? registry.InstalledModule(dependent)?.Module)
282
                        is CkanModule modByVer)
283
                {
×
284
                    changeSet.Add(new ModChange(modByVer, GUIModChangeType.Remove,
×
285
                                                new SelectionReason.DependencyRemoved(),
286
                                                coreConfig));
287
                    modules_to_remove.Add(modByVer);
×
288
                }
×
289
            }
×
290

291
            foreach (var im in registry.FindRemovableAutoInstalled(
6✔
292
                InstalledAfterChanges(registry, changeSet).ToArray(),
293
                Array.Empty<CkanModule>(), game, stabilityTolerance, version))
294
            {
×
295
                changeSet.Add(new ModChange(im.Module, GUIModChangeType.Remove, new SelectionReason.NoLongerUsed(),
×
296
                                            coreConfig));
297
                modules_to_remove.Add(im.Module);
×
298
            }
×
299

300
            // Get as many dependencies as we can, but leave decisions and prompts for installation time
301
            var resolver = new RelationshipResolver(
2✔
302
                modules_to_install, modules_to_remove,
303
                conflictOptions(stabilityTolerance), registry, game, version);
304

305
            // Replace Install entries in changeset with the ones from resolver to get all the reasons
306
            return new Tuple<ICollection<ModChange>, Dictionary<CkanModule, string>, List<string>>(
2✔
307
                changeSet.Where(ch => !(ch.ChangeType is GUIModChangeType.Install
2✔
308
                                        // Leave in replacements
309
                                        && !ch.Reasons.Any(r => r is SelectionReason.Replacement)))
2✔
310
                         .OrderBy(ch => ch.Mod.identifier)
×
311
                         .Union(resolver.ModList()
312
                                        // Changeset already contains changes for these
313
                                        .Except(extraInstalls)
314
                                        .Select(m => new ModChange(m, GUIModChangeType.Install, resolver.ReasonsFor(m),
2✔
315
                                                                   coreConfig)))
316
                         .ToArray(),
317
                resolver.ConflictList,
318
                resolver.ConflictDescriptions.ToList());
319
        }
2✔
320

321
        /// <summary>
322
        /// Check upgradeability of all rows and set GUIMod.HasUpdate appropriately
323
        /// </summary>
324
        /// <param name="inst">Current game instance</param>
325
        /// <param name="registry">Current instance's registry</param>
326
        /// <param name="ChangeSet">Currently pending changeset</param>
327
        /// <param name="rows">The grid rows in case we need to replace some</param>
328
        /// <returns>true if any mod can be updated, false otherwise</returns>
329
        public bool ResetHasUpdate(GameInstance              inst,
330
                                   IRegistryQuerier          registry,
331
                                   List<ModChange>?          ChangeSet,
332
                                   DataGridViewRowCollection rows)
333
        {
2✔
334
            var upgGroups = registry.CheckUpgradeable(inst,
2✔
335
                                                      allLabels.HeldIdentifiers(inst)
336
                                                               .ToHashSet(),
337
                                                      allLabels.IgnoreMissingIdentifiers(inst)
338
                                                               .ToHashSet());
339
            var dlls = registry.InstalledDlls.ToList();
2✔
340
            foreach ((var upgradeable, var mods) in upgGroups)
8✔
341
            {
2✔
342
                foreach (var ident in mods.Select(m => m.identifier))
2✔
343
                {
2✔
344
                    dlls.Remove(ident);
2✔
345
                    CheckRowUpgradeable(inst, ChangeSet, rows, ident, upgradeable);
2✔
346
                }
2✔
347
            }
2✔
348
            // AD mods don't have CkanModules in the return value of CheckUpgradeable
349
            foreach (var ident in dlls)
6✔
350
            {
×
351
                CheckRowUpgradeable(inst, ChangeSet, rows, ident, false);
×
352
            }
×
353
            return upgGroups[true].Count > 0;
2✔
354
        }
2✔
355

356
        /// <summary>
357
        /// Get all the GUI mods for the given instance.
358
        /// </summary>
359
        /// <param name="registry">Registry of the instance</param>
360
        /// <param name="repoData">Repo data of the instance</param>
361
        /// <param name="inst">Game instance</param>
362
        /// <param name="allLabels">All label definitions</param>
363
        /// <param name="cache">Cache for checking if mods are cached</param>
364
        /// <param name="config">GUI config to use</param>
365
        /// <returns>Sequence of GUIMods</returns>
366
        public static IEnumerable<GUIMod> GetGUIMods(IRegistryQuerier      registry,
367
                                                     RepositoryDataManager repoData,
368
                                                     GameInstance          inst,
369
                                                     ModuleLabelList       allLabels,
370
                                                     NetModuleCache        cache,
371
                                                     GUIConfiguration?     config)
372
            => GetGUIMods(registry, repoData, inst,
2✔
373
                          registry.InstalledModules.Select(im => im.identifier)
2✔
374
                                                   .ToHashSet(),
375
                          allLabels,
376
                          cache,
377
                          config?.HideEpochs ?? false, config?.HideV ?? false);
378

379
        private static IEnumerable<GUIMod> GetGUIMods(IRegistryQuerier      registry,
380
                                                      RepositoryDataManager repoData,
381
                                                      GameInstance          inst,
382
                                                      HashSet<string>       installedIdents,
383
                                                      ModuleLabelList       allLabels,
384
                                                      NetModuleCache        cache,
385
                                                      bool                  hideEpochs,
386
                                                      bool                  hideV)
387
            => registry.CheckUpgradeable(inst,
2✔
388
                                         allLabels.HeldIdentifiers(inst)
389
                                                  .ToHashSet(),
390
                                         allLabels.IgnoreMissingIdentifiers(inst)
391
                                                  .ToHashSet())
392
                       .SelectMany(kvp => kvp.Value
2✔
393
                                             .Select(mod => registry.IsAutodetected(mod.identifier)
2✔
394
                                                            ? new GUIMod(mod, repoData, registry,
395
                                                                         inst.StabilityToleranceConfig,
396
                                                                         inst, cache, null,
397
                                                                         hideEpochs, hideV)
398
                                                              {
399
                                                                  HasUpdate = kvp.Key,
400
                                                              }
401
                                                            : registry.InstalledModule(mod.identifier)
402
                                                              is InstalledModule found
403
                                                              ? new GUIMod(found,
404
                                                                           repoData, registry,
405
                                                                           inst.StabilityToleranceConfig,
406
                                                                           inst, cache, null,
407
                                                                           hideEpochs, hideV)
408
                                                              {
409
                                                                  HasUpdate = kvp.Key,
410
                                                              }
411
                                                              : null))
412
                       .OfType<GUIMod>()
413
                       .Concat(registry.CompatibleModules(inst.StabilityToleranceConfig, inst.VersionCriteria())
414
                                       .Where(m => !installedIdents.Contains(m.identifier))
2✔
415
                                       .AsParallel()
416
                                       .Where(m => !m.IsDLC)
2✔
417
                                       .Select(m => new GUIMod(m, repoData, registry,
2✔
418
                                                               inst.StabilityToleranceConfig,
419
                                                               inst, cache, null,
420
                                                               hideEpochs, hideV)))
421
                       .Concat(registry.IncompatibleModules(inst.StabilityToleranceConfig, inst.VersionCriteria())
422
                                       .Where(m => !installedIdents.Contains(m.identifier))
2✔
423
                                       .AsParallel()
424
                                       .Where(m => !m.IsDLC)
2✔
425
                                       .Select(m => new GUIMod(m, repoData, registry,
2✔
426
                                                               inst.StabilityToleranceConfig,
427
                                                               inst, cache, true,
428
                                                               hideEpochs, hideV)));
429

430
        private void CheckRowUpgradeable(GameInstance              inst,
431
                                         List<ModChange>?          ChangeSet,
432
                                         DataGridViewRowCollection rows,
433
                                         string                    ident,
434
                                         bool                      upgradeable)
435
        {
2✔
436
            if (full_list_of_mod_rows.TryGetValue(ident, out DataGridViewRow? row)
2✔
437
                && row.Tag is GUIMod gmod
438
                && gmod.HasUpdate != upgradeable)
439
            {
×
440
                gmod.HasUpdate = upgradeable;
×
441
                if (row.Visible)
×
442
                {
×
443
                    // Swap whether the row has an upgrade checkbox
444
                    var newRow = full_list_of_mod_rows[ident] = MakeRow(gmod, ChangeSet, inst);
×
445
                    var rowIndex = row.Index;
×
446
                    var selected = row.Selected;
×
447
                    rows.Remove(row);
×
448
                    rows.Insert(rowIndex, newRow);
×
449
                    if (selected)
×
450
                    {
×
451
                        rows[rowIndex].Selected = true;
×
452
                    }
×
453
                }
×
454
            }
×
455
        }
2✔
456

457
        private static Color SelectionBlend(Color c)
458
            => c == Color.Empty
2✔
459
                ? SystemColors.Highlight
460
                : SystemColors.Highlight.AlphaBlendWith(selectionAlpha, c);
461

462
        private const float selectionAlpha = 0.4f;
463

464
        private static IEnumerable<ModChange> rowChanges(IRegistryQuerier    registry,
465
                                                         DataGridViewRow     row,
466
                                                         DataGridViewColumn? upgradeCol,
467
                                                         DataGridViewColumn? replaceCol)
468
            => row.Tag is GUIMod gmod ? gmod.GetModChanges(
2✔
469
                   upgradeCol != null && upgradeCol.Visible
470
                   && row.Cells[upgradeCol.Index] is DataGridViewCheckBoxCell upgradeCell
471
                   && (bool)upgradeCell.Value,
472
                   replaceCol != null && replaceCol.Visible
473
                   && row.Cells[replaceCol.Index] is DataGridViewCheckBoxCell replaceCell
474
                   && (bool)replaceCell.Value,
475
                   registry.MetadataChanged(gmod.Identifier))
476
               : Enumerable.Empty<ModChange>();
477

478
        private DataGridViewRow MakeRow(GUIMod           mod,
479
                                        List<ModChange>? changes,
480
                                        GameInstance     instance)
481
        {
2✔
482
            DataGridViewRow item = new DataGridViewRow() { Tag = mod };
2✔
483

484
            item.DefaultCellStyle.BackColor = GetRowBackground(mod, false, instance);
2✔
485
            item.DefaultCellStyle.ForeColor = item.DefaultCellStyle.BackColor.ForeColorForBackColor()
2✔
486
                                              ?? SystemColors.WindowText;
487
            item.DefaultCellStyle.SelectionBackColor = SelectionBlend(item.DefaultCellStyle.BackColor);
2✔
488
            item.DefaultCellStyle.SelectionForeColor = item.DefaultCellStyle.SelectionBackColor.ForeColorForBackColor()
2✔
489
                                                       ?? SystemColors.HighlightText;
490

UNCOV
491
            var myChange = changes?.FindLast(ch => ch.Mod.Equals(mod));
×
492

493
            var selecting = mod.IsAutodetected
2✔
494
                ? new DataGridViewTextBoxCell()
495
                {
496
                    Value = Properties.Resources.MainModListAutoDetected
497
                }
498
                : mod.IsInstallable()
499
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
500
                {
501
                    Value = myChange == null
502
                        ? mod.IsInstalled
503
                        : myChange.ChangeType == GUIModChangeType.Install
504
                          || (myChange.ChangeType != GUIModChangeType.Remove && mod.IsInstalled)
505
                }
506
                : new DataGridViewTextBoxCell()
507
                {
508
                    Value = "-"
509
                };
510

511
            var autoInstalled = mod.IsInstalled && !mod.IsAutodetected
2✔
512
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
513
                {
514
                    Value = mod.IsAutoInstalled,
515
                    ToolTipText = Properties.Resources.MainModListAutoInstalledToolTip,
516
                }
517
                : new DataGridViewTextBoxCell()
518
                {
519
                    Value = "-"
520
                };
521

522
            var updating = mod.IsInstallable() && mod.HasUpdate
2✔
523
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
524
                {
525
                    Value = myChange?.ChangeType == GUIModChangeType.Update
526
                }
527
                : new DataGridViewTextBoxCell()
528
                {
529
                    Value = "-"
530
                };
531

532
            var replacing = (mod.IsInstalled && mod.HasReplacement)
2✔
533
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
534
                {
535
                    Value = myChange?.ChangeType == GUIModChangeType.Replace
536
                }
537
                : new DataGridViewTextBoxCell()
538
                {
539
                    Value = "-"
540
                };
541

542
            var name   = new DataGridViewTextBoxCell { Value = ToGridText(mod.Name)                       };
2✔
543
            var author = new DataGridViewTextBoxCell { Value = ToGridText(string.Join(", ", mod.Authors)) };
2✔
544

545
            var installVersion = new DataGridViewTextBoxCell()
2✔
546
            {
547
                Value = mod.InstalledVersion
548
            };
549

550
            var latestVersion = new DataGridViewTextBoxCell()
2✔
551
            {
552
                Value = mod.LatestVersion
553
            };
554

555
            var downloadCount = new DataGridViewTextBoxCell { Value = $"{mod.DownloadCount:N0}"   };
2✔
556
            var compat        = new DataGridViewTextBoxCell { Value = mod.GameCompatibility       };
2✔
557
            var downloadSize  = new DataGridViewTextBoxCell { Value = mod.DownloadSize            };
2✔
558
            var installSize   = new DataGridViewTextBoxCell { Value = mod.InstallSize             };
2✔
559
            var releaseDate   = new DataGridViewTextBoxCell { Value = mod.Module.release_date     };
2✔
560
            var installDate   = new DataGridViewTextBoxCell { Value = mod.InstallDate             };
2✔
561
            var desc          = new DataGridViewTextBoxCell { Value = ToGridText(mod.Abstract)    };
2✔
562

563
            item.Cells.AddRange(selecting, autoInstalled, updating, replacing, name, author, installVersion, latestVersion, compat, downloadSize, installSize, releaseDate, installDate, downloadCount, desc);
2✔
564

565
            selecting.ReadOnly     = selecting     is DataGridViewTextBoxCell;
2✔
566
            autoInstalled.ReadOnly = autoInstalled is DataGridViewTextBoxCell;
2✔
567
            updating.ReadOnly      = updating      is DataGridViewTextBoxCell;
2✔
568

569
            return item;
2✔
570
        }
2✔
571

572
        private static string ToGridText(string text)
573
            => Platform.IsMono ? text.Replace("&", "&&") : text;
2✔
574

575
        private bool TagInSearches(ModuleTag tag)
UNCOV
576
            => activeSearches.Any(s => s.TagNames.Contains(tag.Name));
×
577

578
        private bool LabelInSearches(ModuleLabel label)
UNCOV
579
            => activeSearches.Any(s => s.LabelNames.Contains(label.Name));
×
580

581
        private bool HiddenByTagsOrLabels(GUIMod m, GameInstance instance, Registry registry)
582
            // "Hide" labels apply to all non-custom filters
583
            => (allLabels?.LabelsFor(instance.Name)
2✔
584
                                             .Where(l => !LabelInSearches(l) && l.Hide)
2✔
585
                                             .Any(l => l.ContainsModule(instance.Game, m.Identifier))
2✔
586
                                            ?? false)
587
               || (registry.Tags?.Values
588
                                 .Where(t => !TagInSearches(t) && allTags.HiddenTags.Contains(t.Name))
2✔
589
                                 .Any(t => t.ModuleIdentifiers.Contains(m.Identifier))
2✔
590
                                ?? false);
591

592
        private static RelationshipResolverOptions conflictOptions(StabilityToleranceConfig stabilityTolerance)
593
            => new RelationshipResolverOptions(stabilityTolerance)
2✔
594
            {
595
                without_toomanyprovides_kraken = true,
596
                proceed_with_inconsistencies   = true,
597
                without_enforce_consistency    = true,
598
                with_recommends                = false
599
            };
600

601
        /// <summary>
602
        /// Get the InstalledModules that we'll have after the changeset,
603
        /// not including dependencies
604
        /// </summary>
605
        /// <param name="registry">Registry with currently installed modules</param>
606
        /// <param name="changeSet">Changes to be made to the installed modules</param>
607
        private static IEnumerable<InstalledModule> InstalledAfterChanges(
608
            IRegistryQuerier               registry,
609
            IReadOnlyCollection<ModChange> changeSet)
610
        {
2✔
611
            var removingIdents = changeSet
2✔
612
                .Where(ch => ch.ChangeType != GUIModChangeType.Install)
2✔
613
                .Select(ch => ch.Mod.identifier)
2✔
614
                .ToHashSet();
615
            return registry.InstalledModules
2✔
616
                .Where(im => !removingIdents.Contains(im.identifier))
2✔
617
                .Concat(changeSet
618
                    .Where(ch => ch.ChangeType is not GUIModChangeType.Remove
2✔
619
                                              and not GUIModChangeType.Replace)
620
                    .Select(ch => new InstalledModule(
2✔
621
                        null,
622
                        (ch as ModUpgrade)?.targetMod ?? ch.Mod,
623
                        Enumerable.Empty<string>(),
624
                        false)));
625
        }
2✔
626

627
        private static bool SearchesEqual(IReadOnlyCollection<ModSearch> a,
628
                                          IReadOnlyCollection<ModSearch> b)
629
            => a.SequenceEqual(b);
2✔
630

631
        private static string FilterName(GUIModFilter filter,
632
                                         ModuleTag?   tag   = null,
633
                                         ModuleLabel? label = null)
634
            => filter switch
2✔
635
               {
636
                   GUIModFilter.Compatible               => Properties.Resources.MainFilterCompatible,
2✔
637
                   GUIModFilter.Incompatible             => Properties.Resources.MainFilterIncompatible,
2✔
638
                   GUIModFilter.Installed                => Properties.Resources.MainFilterInstalled,
2✔
639
                   GUIModFilter.NotInstalled             => Properties.Resources.MainFilterNotInstalled,
2✔
640
                   GUIModFilter.InstalledUpdateAvailable => Properties.Resources.MainFilterUpgradeable,
2✔
641
                   GUIModFilter.Replaceable              => Properties.Resources.MainFilterReplaceable,
2✔
642
                   GUIModFilter.Cached                   => Properties.Resources.MainFilterCached,
2✔
643
                   GUIModFilter.Uncached                 => Properties.Resources.MainFilterUncached,
2✔
644
                   GUIModFilter.NewInRepository          => Properties.Resources.MainFilterNew,
2✔
645
                   GUIModFilter.All                      => Properties.Resources.MainFilterAll,
2✔
646
                   GUIModFilter.CustomLabel              => string.Format(Properties.Resources.MainFilterLabel,
2✔
647
                                                                          label?.Name ?? "CUSTOM"),
648
                   GUIModFilter.Tag                      => tag == null
2✔
649
                                                                ? Properties.Resources.MainFilterUntagged
650
                                                                : string.Format(Properties.Resources.MainFilterTag,
651
                                                                                tag.Name),
652
                   _                                     => "",
×
653
               };
654

655
        private readonly ModuleLabelList                allLabels;
656
        private readonly ModuleTagList                  allTags;
657
        private readonly IConfiguration                 coreConfig;
658
        private readonly GUIConfiguration               guiConfig;
659
        private          IReadOnlyCollection<ModSearch> activeSearches;
660

661
        private static readonly Color conflictColor = Color.FromArgb(255, 64, 64);
2✔
662

663
        private static readonly ILog log = LogManager.GetLogger(typeof(ModList));
2✔
664
    }
665
}
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