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

KSP-CKAN / CKAN / 17278596547

27 Aug 2025 08:59PM UTC coverage: 59.295% (+0.8%) from 58.484%
17278596547

push

github

HebaruSan
Merge #4422 Tests for GameInstanceManager and GUI.ModList

4825 of 8419 branches covered (57.31%)

Branch coverage included in aggregate %.

89 of 142 new or added lines in 4 files covered. (62.68%)

1 existing line in 1 file now uncovered.

10169 of 16868 relevant lines covered (60.29%)

1.23 hits per line

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

50.27
/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="coreConfig">Core configuration</param>
36
        /// <param name="guiConfig">GUI configuration</param>
37
        /// <param name="mc">Changes the user has made</param>
38
        /// <returns>The mod list</returns>
39
        public ModList(IReadOnlyCollection<GUIMod> modules,
2✔
40
                       GameInstance                instance,
41
                       ModuleLabelList             allLabels,
42
                       IConfiguration              coreConfig,
43
                       GUIConfiguration            guiConfig,
44
                       List<ModChange>?            mc = null)
45
        {
2✔
46
            this.allLabels        = allLabels;
2✔
47
            this.coreConfig       = coreConfig;
2✔
48
            this.guiConfig        = guiConfig;
2✔
49
            activeSearches        = guiConfig.DefaultSearches
2✔
NEW
50
                                             ?.Select(s => ModSearch.Parse(allLabels, instance, s))
×
51
                                              .OfType<ModSearch>()
52
                                              .ToArray()
53
                                             ?? Array.Empty<ModSearch>();
54
            Modules               = modules;
2✔
55
            HasAnyInstalled       = Modules.Any(m => m.IsInstalled);
2✔
56
            full_list_of_mod_rows = Modules.AsParallel()
2✔
57
                                           .ToDictionary(gm => gm.Identifier,
2✔
58
                                                         gm => MakeRow(gm, mc, instance));
2✔
59
        }
2✔
60

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

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

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

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

100
        public bool IsVisible(GUIMod mod, GameInstance instance, Registry registry)
101
            => activeSearches.IsEmptyOrAny(s => s.Matches(mod))
1!
102
               && !HiddenByTagsOrLabels(mod, instance, registry);
103

104
        public int CountModsBySearches(List<ModSearch> searches)
105
            => Modules.Count(mod => searches?.Any(s => s?.Matches(mod) ?? true) ?? true);
1!
106

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

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

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

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

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

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

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

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

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

263
            var installed_modules = registry.InstalledModules.ToDictionary(
×
264
                imod => imod.Module.identifier,
×
265
                imod => imod.Module);
×
266

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

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

297
            // Get as many dependencies as we can, but leave decisions and prompts for installation time
298
            var resolver = new RelationshipResolver(
×
299
                modules_to_install, modules_to_remove,
300
                conflictOptions(stabilityTolerance), registry, game, version);
301

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

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

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

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

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

451
        private static Color SelectionBlend(Color c)
452
            => c == Color.Empty
2✔
453
                ? SystemColors.Highlight
454
                : SystemColors.Highlight.AlphaBlendWith(selectionAlpha, c);
455

456
        private const float selectionAlpha = 0.4f;
457

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

472
        private DataGridViewRow MakeRow(GUIMod           mod,
473
                                        List<ModChange>? changes,
474
                                        GameInstance     instance)
475
        {
2✔
476
            DataGridViewRow item = new DataGridViewRow() { Tag = mod };
2✔
477

478
            item.DefaultCellStyle.BackColor = GetRowBackground(mod, false, instance);
2✔
479
            item.DefaultCellStyle.ForeColor = item.DefaultCellStyle.BackColor.ForeColorForBackColor()
2✔
480
                                              ?? SystemColors.WindowText;
481
            item.DefaultCellStyle.SelectionBackColor = SelectionBlend(item.DefaultCellStyle.BackColor);
2✔
482
            item.DefaultCellStyle.SelectionForeColor = item.DefaultCellStyle.SelectionBackColor.ForeColorForBackColor()
2✔
483
                                                       ?? SystemColors.HighlightText;
484

485
            var myChange = changes?.FindLast(ch => ch.Mod.Equals(mod));
1✔
486

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

505
            var autoInstalled = mod.IsInstalled && !mod.IsAutodetected
2✔
506
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
507
                {
508
                    Value = mod.IsAutoInstalled,
509
                    ToolTipText = Properties.Resources.MainModListAutoInstalledToolTip,
510
                }
511
                : new DataGridViewTextBoxCell()
512
                {
513
                    Value = "-"
514
                };
515

516
            var updating = mod.IsInstallable() && mod.HasUpdate
2✔
517
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
518
                {
519
                    Value = myChange?.ChangeType == GUIModChangeType.Update
520
                }
521
                : new DataGridViewTextBoxCell()
522
                {
523
                    Value = "-"
524
                };
525

526
            var replacing = (mod.IsInstalled && mod.HasReplacement)
2✔
527
                ? (DataGridViewCell) new DataGridViewCheckBoxCell()
528
                {
529
                    Value = myChange?.ChangeType == GUIModChangeType.Replace
530
                }
531
                : new DataGridViewTextBoxCell()
532
                {
533
                    Value = "-"
534
                };
535

536
            var name   = new DataGridViewTextBoxCell { Value = ToGridText(mod.Name)                       };
2✔
537
            var author = new DataGridViewTextBoxCell { Value = ToGridText(string.Join(", ", mod.Authors)) };
2✔
538

539
            var installVersion = new DataGridViewTextBoxCell()
2✔
540
            {
541
                Value = mod.InstalledVersion
542
            };
543

544
            var latestVersion = new DataGridViewTextBoxCell()
2✔
545
            {
546
                Value = mod.LatestVersion
547
            };
548

549
            var downloadCount = new DataGridViewTextBoxCell { Value = $"{mod.DownloadCount:N0}"   };
2✔
550
            var compat        = new DataGridViewTextBoxCell { Value = mod.GameCompatibility       };
2✔
551
            var downloadSize  = new DataGridViewTextBoxCell { Value = mod.DownloadSize            };
2✔
552
            var installSize   = new DataGridViewTextBoxCell { Value = mod.InstallSize             };
2✔
553
            var releaseDate   = new DataGridViewTextBoxCell { Value = mod.ToModule().release_date };
2✔
554
            var installDate   = new DataGridViewTextBoxCell { Value = mod.InstallDate             };
2✔
555
            var desc          = new DataGridViewTextBoxCell { Value = ToGridText(mod.Abstract)    };
2✔
556

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

559
            selecting.ReadOnly     = selecting     is DataGridViewTextBoxCell;
2✔
560
            autoInstalled.ReadOnly = autoInstalled is DataGridViewTextBoxCell;
2✔
561
            updating.ReadOnly      = updating      is DataGridViewTextBoxCell;
2✔
562

563
            return item;
2✔
564
        }
2✔
565

566
        private static string ToGridText(string text)
567
            => Platform.IsMono ? text.Replace("&", "&&") : text;
2!
568

569
        private bool TagInSearches(ModuleTag tag)
NEW
570
            => activeSearches.Any(s => s.TagNames.Contains(tag.Name));
×
571

572
        private bool LabelInSearches(ModuleLabel label)
573
            => activeSearches.Any(s => s.LabelNames.Contains(label.Name));
1✔
574

575
        private bool HiddenByTagsOrLabels(GUIMod m, GameInstance instance, Registry registry)
576
            // "Hide" labels apply to all non-custom filters
577
            => (allLabels?.LabelsFor(instance.Name)
2!
578
                                             .Where(l => !LabelInSearches(l) && l.Hide)
2!
579
                                             .Any(l => l.ContainsModule(instance.game, m.Identifier))
2✔
580
                                            ?? false)
581
               || (registry.Tags?.Values
NEW
582
                                 .Where(t => !TagInSearches(t) && ModuleTagList.ModuleTags.HiddenTags.Contains(t.Name))
×
NEW
583
                                 .Any(t => t.ModuleIdentifiers.Contains(m.Identifier))
×
584
                                ?? false);
585

586
        private static RelationshipResolverOptions conflictOptions(StabilityToleranceConfig stabilityTolerance)
NEW
587
            => new RelationshipResolverOptions(stabilityTolerance)
×
588
            {
589
                without_toomanyprovides_kraken = true,
590
                proceed_with_inconsistencies   = true,
591
                without_enforce_consistency    = true,
592
                with_recommends                = false
593
            };
594

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

621
        private static bool SearchesEqual(IReadOnlyCollection<ModSearch> a,
622
                                          IReadOnlyCollection<ModSearch> b)
623
            => a.SequenceEqual(b);
2✔
624

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

649
        private readonly ModuleLabelList                allLabels;
650
        private readonly IConfiguration                 coreConfig;
651
        private readonly GUIConfiguration               guiConfig;
652
        private          IReadOnlyCollection<ModSearch> activeSearches;
653

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

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