diff --git a/Cmdline/Action/List.cs b/Cmdline/Action/List.cs index e5cadd5bc..67040b184 100644 --- a/Cmdline/Action/List.cs +++ b/Cmdline/Action/List.cs @@ -43,7 +43,7 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) user.RaiseMessage(""); user.RaiseMessage(Properties.Resources.ListGameFound, instance.game.ShortName, - instance.GameDir().Replace('/', Path.DirectorySeparatorChar)); + Platform.FormatPath(instance.GameDir())); user.RaiseMessage(""); user.RaiseMessage(Properties.Resources.ListGameVersion, instance.game.ShortName, instance.Version()); user.RaiseMessage(""); diff --git a/ConsoleUI/AuthTokenListScreen.cs b/ConsoleUI/AuthTokenListScreen.cs index 7ae68d416..c08221590 100644 --- a/ConsoleUI/AuthTokenListScreen.cs +++ b/ConsoleUI/AuthTokenListScreen.cs @@ -39,7 +39,7 @@ public AuthTokenScreen() : base() }, new ConsoleListBoxColumn() { Header = Properties.Resources.AuthTokenListTokenHeader, - Width = 50, + Width = null, Renderer = (string s) => { return ServiceLocator.Container.Resolve().TryGetAuthToken(s, out string token) ? token diff --git a/ConsoleUI/CompatibleVersionDialog.cs b/ConsoleUI/CompatibleVersionDialog.cs index 50afd0380..c86cc5ae8 100644 --- a/ConsoleUI/CompatibleVersionDialog.cs +++ b/ConsoleUI/CompatibleVersionDialog.cs @@ -30,7 +30,7 @@ public CompatibleVersionDialog(IGame game) : base() new List>() { new ConsoleListBoxColumn() { Header = Properties.Resources.CompatibleVersionsListHeader, - Width = r - l - 5, + Width = null, Renderer = v => v.ToString(), Comparer = (v1, v2) => v1.CompareTo(v2) } diff --git a/ConsoleUI/DeleteDirectoriesScreen.cs b/ConsoleUI/DeleteDirectoriesScreen.cs new file mode 100644 index 000000000..75e163bd0 --- /dev/null +++ b/ConsoleUI/DeleteDirectoriesScreen.cs @@ -0,0 +1,122 @@ +using System; +using System.Linq; +using System.IO; +using System.Collections.Generic; + +using CKAN.ConsoleUI.Toolkit; + +namespace CKAN.ConsoleUI { + /// + /// Screen prompting user to delete game-created folders + /// + public class DeleteDirectoriesScreen : ConsoleScreen { + + /// + /// Initialize the screen + /// + /// Game instance + /// Deletable stuff the user should see + public DeleteDirectoriesScreen(GameInstance inst, + HashSet possibleConfigOnlyDirs) + { + instance = inst; + toDelete = possibleConfigOnlyDirs.ToHashSet(); + + var tb = new ConsoleTextBox(1, 2, -1, 4, false); + tb.AddLine(Properties.Resources.DeleteDirectoriesHelpLabel); + AddObject(tb); + + var listWidth = (Console.WindowWidth - 4) / 2; + + directories = new ConsoleListBox( + 1, 6, listWidth - 1, -2, + possibleConfigOnlyDirs.OrderBy(d => d).ToList(), + new List>() { + new ConsoleListBoxColumn() { + Header = "", + Width = 1, + Renderer = s => toDelete.Contains(s) ? Symbols.checkmark : "", + }, + new ConsoleListBoxColumn() { + Header = Properties.Resources.DeleteDirectoriesFoldersHeader, + Width = null, + // The data model holds absolute paths, but the UI shows relative + Renderer = p => Platform.FormatPath(instance.ToRelativeGameDir(p)), + }, + }, + 0, -1); + directories.AddTip("D", Properties.Resources.DeleteDirectoriesDeleteDirTip, + () => !toDelete.Contains(directories.Selection)); + directories.AddBinding(Keys.D, (object sender, ConsoleTheme theme) => { + toDelete.Add(directories.Selection); + return true; + }); + directories.AddTip("K", Properties.Resources.DeleteDirectoriesKeepDirTip, + () => toDelete.Contains(directories.Selection)); + directories.AddBinding(Keys.K, (object sender, ConsoleTheme theme) => { + toDelete.Remove(directories.Selection); + return true; + }); + directories.SelectionChanged += PopulateFiles; + AddObject(directories); + + files = new ConsoleListBox( + listWidth + 1, 6, -1, -2, + new List() { "" }, + new List>() { + new ConsoleListBoxColumn() { + Header = Properties.Resources.DeleteDirectoriesFilesHeader, + Width = null, + Renderer = p => Platform.FormatPath(CKANPathUtils.ToRelative(p, directories.Selection)), + }, + }, + 0, -1); + AddObject(files); + + AddTip(Properties.Resources.Esc, + Properties.Resources.DeleteDirectoriesCancelTip); + AddBinding(Keys.Escape, + // Discard changes + (object sender, ConsoleTheme theme) => false); + + AddTip("F9", Properties.Resources.DeleteDirectoriesApplyTip); + AddBinding(Keys.F9, (object sender, ConsoleTheme theme) => { + foreach (var d in toDelete) { + try { + Directory.Delete(d, true); + } catch { + } + } + return false; + }); + + PopulateFiles(); + } + + private void PopulateFiles() + { + files.SetData( + Directory.EnumerateFileSystemEntries(directories.Selection, + "*", + SearchOption.AllDirectories) + .OrderBy(f => f) + .ToList(), + true); + } + + /// + /// Put CKAN 1.25.5 in top left corner + /// + protected override string LeftHeader() => $"CKAN {Meta.GetVersion()}"; + + /// + /// Show the Delete Directories header + /// + protected override string CenterHeader() => Properties.Resources.DeleteDirectoriesHeader; + + private readonly GameInstance instance; + private readonly HashSet toDelete; + private readonly ConsoleListBox directories; + private readonly ConsoleListBox files; + } +} diff --git a/ConsoleUI/DependencyScreen.cs b/ConsoleUI/DependencyScreen.cs index 189d7ca72..1e61beb7e 100644 --- a/ConsoleUI/DependencyScreen.cs +++ b/ConsoleUI/DependencyScreen.cs @@ -53,7 +53,7 @@ public DependencyScreen(GameInstanceManager mgr, Registry registry, ChangePlan c }, new ConsoleListBoxColumn() { Header = Properties.Resources.RecommendationsNameHeader, - Width = 36, + Width = null, Renderer = (Dependency d) => d.module.ToString() }, new ConsoleListBoxColumn() { diff --git a/ConsoleUI/GameInstanceEditScreen.cs b/ConsoleUI/GameInstanceEditScreen.cs index ecf923f9e..ef268d020 100644 --- a/ConsoleUI/GameInstanceEditScreen.cs +++ b/ConsoleUI/GameInstanceEditScreen.cs @@ -66,7 +66,7 @@ public GameInstanceEditScreen(GameInstanceManager mgr, RepositoryDataManager rep }, new ConsoleListBoxColumn() { Header = Properties.Resources.InstanceEditRepoURLHeader, Renderer = r => r.uri.ToString(), - Width = 50 + Width = null } }, 1, 0, ListSortDirection.Ascending diff --git a/ConsoleUI/GameInstanceListScreen.cs b/ConsoleUI/GameInstanceListScreen.cs index d8ff76947..9a5973f73 100644 --- a/ConsoleUI/GameInstanceListScreen.cs +++ b/ConsoleUI/GameInstanceListScreen.cs @@ -52,7 +52,7 @@ public GameInstanceListScreen(GameInstanceManager mgr, RepositoryDataManager rep Comparer = (a, b) => a.Version()?.CompareTo(b.Version() ?? GameVersion.Any) ?? 1 }, new ConsoleListBoxColumn() { Header = Properties.Resources.InstanceListPathHeader, - Width = 70, + Width = null, Renderer = k => k.GameDir() } }, @@ -229,7 +229,7 @@ public static bool TryGetInstance(ConsoleTheme theme, ConsoleMessageDialog errd = new ConsoleMessageDialog( string.Format(Properties.Resources.InstanceListLoadingError, - Path.Combine(ksp.CkanDir(), "registry.json").Replace('/', Path.DirectorySeparatorChar), + Platform.FormatPath(Path.Combine(ksp.CkanDir(), "registry.json")), e.ToString()), new List() { Properties.Resources.OK } ); diff --git a/ConsoleUI/InstallFiltersScreen.cs b/ConsoleUI/InstallFiltersScreen.cs index ee344bc1c..d212102c2 100644 --- a/ConsoleUI/InstallFiltersScreen.cs +++ b/ConsoleUI/InstallFiltersScreen.cs @@ -50,7 +50,7 @@ public InstallFiltersScreen(IConfiguration globalConfig, GameInstance instance) new List>() { new ConsoleListBoxColumn() { Header = Properties.Resources.FiltersGlobalHeader, - Width = 40, + Width = null, Renderer = f => f, } }, @@ -73,7 +73,7 @@ public InstallFiltersScreen(IConfiguration globalConfig, GameInstance instance) new List>() { new ConsoleListBoxColumn() { Header = Properties.Resources.FiltersInstanceHeader, - Width = 40, + Width = null, Renderer = f => f, } }, diff --git a/ConsoleUI/InstallScreen.cs b/ConsoleUI/InstallScreen.cs index d7eb965ae..6258c4548 100644 --- a/ConsoleUI/InstallScreen.cs +++ b/ConsoleUI/InstallScreen.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Transactions; using System.Collections.Generic; using System.Linq; @@ -105,6 +106,8 @@ public override void Run(ConsoleTheme theme, Action process = null // Don't let the installer re-use old screen references inst.User = null; + HandlePossibleConfigOnlyDirs(theme, registry, possibleConfigOnlyDirs); + } catch (CancelledActionKraken) { // Don't need to tell the user they just cancelled out. } catch (FileNotFoundKraken ex) { @@ -165,6 +168,26 @@ public override void Run(ConsoleTheme theme, Action process = null } } + private void HandlePossibleConfigOnlyDirs(ConsoleTheme theme, + Registry registry, + HashSet possibleConfigOnlyDirs) + { + if (possibleConfigOnlyDirs != null) + { + // Check again for registered files, since we may + // just have installed or upgraded some + possibleConfigOnlyDirs.RemoveWhere( + d => !Directory.Exists(d) + || Directory.EnumerateFileSystemEntries(d, "*", SearchOption.AllDirectories) + .Select(absF => manager.CurrentInstance.ToRelativeGameDir(absF)) + .Any(relF => registry.FileOwner(relF) != null)); + if (possibleConfigOnlyDirs.Count > 0) + { + LaunchSubScreen(theme, new DeleteDirectoriesScreen(manager.CurrentInstance, possibleConfigOnlyDirs)); + } + } + } + private void OnModInstalled(CkanModule mod) { RaiseMessage(Properties.Resources.InstallModInstalled, Symbols.checkmark, mod.name, ModuleInstaller.StripEpoch(mod.version)); diff --git a/ConsoleUI/ModListScreen.cs b/ConsoleUI/ModListScreen.cs index cb85d56f1..7d92de5bb 100644 --- a/ConsoleUI/ModListScreen.cs +++ b/ConsoleUI/ModListScreen.cs @@ -45,7 +45,7 @@ public ModListScreen(GameInstanceManager mgr, RepositoryDataManager repoData, Re Renderer = StatusSymbol }, new ConsoleListBoxColumn() { Header = Properties.Resources.ModListNameHeader, - Width = 44, + Width = null, Renderer = m => m.name ?? "" }, new ConsoleListBoxColumn() { Header = Properties.Resources.ModListVersionHeader, @@ -639,8 +639,11 @@ private bool Help(ConsoleTheme theme) private bool ApplyChanges(ConsoleTheme theme) { - LaunchSubScreen(theme, new InstallScreen(manager, repoData, plan, debug)); - RefreshList(theme); + if (plan.NonEmpty()) + { + LaunchSubScreen(theme, new InstallScreen(manager, repoData, plan, debug)); + RefreshList(theme); + } return true; } diff --git a/ConsoleUI/Properties/Resources.fr-FR.resx b/ConsoleUI/Properties/Resources.fr-FR.resx index 8a92c5114..edc5796f7 100644 --- a/ConsoleUI/Properties/Resources.fr-FR.resx +++ b/ConsoleUI/Properties/Resources.fr-FR.resx @@ -466,6 +466,12 @@ Veuillez désinstaller manuellement le mod auquel ce fichier appartient pour pou {0} installé avec succès {1} {2} + Supprimer les Dossiers + Attention, il reste certains dossiers, parce que CKAN ne sait pas s'il peut supprimer les fichiers restants. Garder ces dossiers peut casser d'autres mods ! Si vous n'avez pas besoin de ces fichiers, il est recommandé de les supprimer. + Dossiers + Contenu du Dossier + Tout Garder + Supprimer Coché Détails du Mod diff --git a/ConsoleUI/Properties/Resources.it-IT.resx b/ConsoleUI/Properties/Resources.it-IT.resx index a29771996..7d006992d 100644 --- a/ConsoleUI/Properties/Resources.it-IT.resx +++ b/ConsoleUI/Properties/Resources.it-IT.resx @@ -466,6 +466,13 @@ Modificaew i token di autenticazione adesso? {0} installato con successo {1} {2} + Elimina Cartelle + Le directory sottostanti sono dei residui rimasti a seguito della rimozione di alcune mod. Contengono file che non sono stati installati da CKAN (probabilmente generati da una mod o installati manualmente). CKAN non elimina automaticamente i file che non ha installato, ma puoi scegliere di rimuoverli se ti sembra sicuro farlo (scelta consigliata). +Nota che se decidi di non rimuovere una cartella, ModuleManager potrebbe pensare erroneamente che il mod sia ancora installato. + Cartelle + Contenuto Cartella + Mantieni Tutte + Elimina Selezionata Dettagli Mod diff --git a/ConsoleUI/Properties/Resources.pl-PL.resx b/ConsoleUI/Properties/Resources.pl-PL.resx index d0e6641b3..95052b286 100644 --- a/ConsoleUI/Properties/Resources.pl-PL.resx +++ b/ConsoleUI/Properties/Resources.pl-PL.resx @@ -466,6 +466,13 @@ Edytować tokeny uwierzytelniania? {0} pomyślnie zainstalowano {1} {2} + Usuń foldery + Poniższe foldery są pozostałościami po usuniętych modach. Zawierają pliki które nie były pobierane przez CKAN (prawdopodobnie wygenerowane przez moda bądź zainstalowane ręcznie). CKAN nie usuwa plików których nie instalował ale możesz sam je usunąć jeśli uważasz że to bezpieczne (zalecane) +Uważaj bo jeśli nie usuniesz folderu, ModuleManager może myśleć że mod jest nadal zainstalowany. + Foldery + Zawartość folderu + Zachowaj wszystko + Usuń zaznaczone Szczegóły modyfikacji diff --git a/ConsoleUI/Properties/Resources.resx b/ConsoleUI/Properties/Resources.resx index 6e6245555..8818cf4cf 100644 --- a/ConsoleUI/Properties/Resources.resx +++ b/ConsoleUI/Properties/Resources.resx @@ -238,6 +238,14 @@ Edit authentication tokens now? {1} {0} is not installed, can't remove {0} Successfully installed {1} {2} + Delete Directories + Warning, some folders have been left behind because CKAN does not know whether it is safe to delete their remaining files. Keeping these folders may break other mods! If you do not need these files, deleting them is recommended. + Directories + Directory Contents + Delete folder + Keep folder + Keep all + Delete checked Mod Details Links By {0} diff --git a/ConsoleUI/Properties/Resources.ru-RU.resx b/ConsoleUI/Properties/Resources.ru-RU.resx index 3a7c0da4c..24c13d9a0 100644 --- a/ConsoleUI/Properties/Resources.ru-RU.resx +++ b/ConsoleUI/Properties/Resources.ru-RU.resx @@ -466,6 +466,13 @@ {0} Успешно установлен {1} {2} + Удаление папок + Следующие папки остались после удаления модификаций. Они содержат файлы, не установленные CKAN (сгенерированные модификациями или установленные вручную). CKAN не удаляет сторонние файлы автоматически, однако вы можете удалить их вручную, если это безопасно. +Обратите внимание, что если папки не будут удалены, ModuleManager может неверно посчитать, что модификации установлены. + Папки + Содержимое папок + Оставить всё + Удалить отмеченное О модификации diff --git a/ConsoleUI/Toolkit/ConsoleChoiceDialog.cs b/ConsoleUI/Toolkit/ConsoleChoiceDialog.cs index 50d4d19ab..28c5170bd 100644 --- a/ConsoleUI/Toolkit/ConsoleChoiceDialog.cs +++ b/ConsoleUI/Toolkit/ConsoleChoiceDialog.cs @@ -51,7 +51,7 @@ public ConsoleChoiceDialog(string m, string hdr, List c, Func>() { new ConsoleListBoxColumn() { Header = hdr, - Width = w - 6, + Width = null, Renderer = renderer, Comparer = comparer } diff --git a/ConsoleUI/Toolkit/ConsoleFileMultiSelectDialog.cs b/ConsoleUI/Toolkit/ConsoleFileMultiSelectDialog.cs index 68d9a1ae9..869a8971e 100644 --- a/ConsoleUI/Toolkit/ConsoleFileMultiSelectDialog.cs +++ b/ConsoleUI/Toolkit/ConsoleFileMultiSelectDialog.cs @@ -64,7 +64,7 @@ public ConsoleFileMultiSelectDialog(string title, string startPath, string filPa Renderer = getRowSymbol }, new ConsoleListBoxColumn() { Header = Properties.Resources.FileSelectNameHeader, - Width = 36, + Width = null, Renderer = getRowName, Comparer = compareNames }, new ConsoleListBoxColumn() { diff --git a/ConsoleUI/Toolkit/ConsoleListBox.cs b/ConsoleUI/Toolkit/ConsoleListBox.cs index a4bb218e1..8539ec685 100644 --- a/ConsoleUI/Toolkit/ConsoleListBox.cs +++ b/ConsoleUI/Toolkit/ConsoleListBox.cs @@ -2,6 +2,7 @@ using System.Text.RegularExpressions; using System.ComponentModel; using System.Collections.Generic; +using System.Linq; namespace CKAN.ConsoleUI.Toolkit { @@ -43,6 +44,11 @@ public ConsoleListBox(int l, int t, int r, int b, filterAndSort(); } + /// + /// Fired when the user changes the selection with the arrow keys + /// + public event Action SelectionChanged; + /// /// Set which column to sort by /// @@ -90,7 +96,7 @@ public RowT Selection /// /// Return the number of rows shown in the box /// - public int VisibleRowCount() { return sortedFilteredData?.Count ?? 0; } + public int VisibleRowCount() => sortedFilteredData?.Count ?? 0; /// /// Draw the list box @@ -133,6 +139,15 @@ public override void Draw(ConsoleTheme theme, bool focused) topRow = selectedRow - h + 2; } + var remainingWidth = contentR - l + 1 + - columns.Select(col => col.Width ?? 0) + .Sum() + - (padding.Length * (columns.Count - 1)); + var autoWidthCount = columns.Count(col => !col.Width.HasValue); + var autoWidth = autoWidthCount > 0 && remainingWidth > 0 + ? remainingWidth / autoWidthCount + : 1; + for (int y = 0, index = topRow - 1; y < h; ++y, ++index) { Console.SetCursorPosition(l, t + y); if (y == 0) { @@ -142,14 +157,15 @@ public override void Draw(ConsoleTheme theme, bool focused) for (int i = 0; i < columns.Count; ++i) { ConsoleListBoxColumn col = columns[i]; if (i > 0) { - Console.Write(" "); + Console.Write(padding); } // Truncate to designated size of the ListBox int maxW = r - Console.CursorLeft + 1; if (maxW > 0) { + var w = col.Width ?? autoWidth; Console.Write(FmtHdr( i, - col.Width < maxW ? col.Width : maxW + w < maxW ? w : maxW )); } } @@ -165,14 +181,15 @@ public override void Draw(ConsoleTheme theme, bool focused) for (int i = 0; i < columns.Count; ++i) { ConsoleListBoxColumn col = columns[i]; if (i > 0) { - Console.Write(" "); + Console.Write(padding); } // Truncate to designated size of the ListBox int maxW = contentR - Console.CursorLeft + 1; if (maxW > 0) { + var w = col.Width ?? autoWidth; Console.Write(FormatExactWidth( col.Renderer(sortedFilteredData[index]).Trim(), - col.Width < maxW ? col.Width : maxW + w < maxW ? w : maxW )); } } @@ -214,11 +231,13 @@ public override void OnKeyPress(ConsoleKeyInfo k) case ConsoleKey.UpArrow: if (selectedRow > 0) { --selectedRow; + SelectionChanged?.Invoke(); } break; case ConsoleKey.DownArrow: if (selectedRow < sortedFilteredData.Count - 1) { ++selectedRow; + SelectionChanged?.Invoke(); } break; case ConsoleKey.PageUp: @@ -227,6 +246,7 @@ public override void OnKeyPress(ConsoleKeyInfo k) } else { selectedRow = 0; } + SelectionChanged?.Invoke(); break; case ConsoleKey.PageDown: if (selectedRow < sortedFilteredData.Count - 1 - h) { @@ -234,12 +254,15 @@ public override void OnKeyPress(ConsoleKeyInfo k) } else { selectedRow = sortedFilteredData.Count - 1; } + SelectionChanged?.Invoke(); break; case ConsoleKey.Home: selectedRow = 0; + SelectionChanged?.Invoke(); break; case ConsoleKey.End: selectedRow = sortedFilteredData.Count - 1; + SelectionChanged?.Invoke(); break; case ConsoleKey.Tab: Blur(!k.Modifiers.HasFlag(ConsoleModifiers.Shift)); @@ -265,6 +288,7 @@ public override void OnKeyPress(ConsoleKeyInfo k) ).IndexOf($"{k.KeyChar}", StringComparison.CurrentCultureIgnoreCase) == 0) { selectedRow = candidateRow; + SelectionChanged?.Invoke(); break; } } @@ -338,10 +362,14 @@ public ConsolePopupMenu SortMenu() /// Set the data shown in the list /// /// List of objects to show - public void SetData(IList newData) + /// If true, select the top row after refreshing + public void SetData(IList newData, bool resetSelection = false) { data = newData; filterAndSort(); + if (resetSelection) { + selectedRow = 0; + } } private string FmtHdr(int colIndex, int w) @@ -414,8 +442,9 @@ private int IntOr(Func first, Func second) private static readonly Regex nonAlphaNumPrefix = new Regex("^[^a-zA-Z0-9]*", RegexOptions.Compiled); - private static readonly string sortUp = "^"; - private static readonly string sortDown = "v"; + private const string sortUp = "^"; + private const string sortDown = "v"; + private const string padding = " "; } /// @@ -442,9 +471,10 @@ public ConsoleListBoxColumn() { } /// public Comparison Comparer; /// - /// Number of screen columns to use for this column + /// Number of screen columns to use for this column. + /// If null, take up remaining space left behind by other columns. /// - public int Width; + public int? Width; } } diff --git a/ConsoleUI/Toolkit/ConsoleTextBox.cs b/ConsoleUI/Toolkit/ConsoleTextBox.cs index 5a4c8466e..edcba9179 100644 --- a/ConsoleUI/Toolkit/ConsoleTextBox.cs +++ b/ConsoleUI/Toolkit/ConsoleTextBox.cs @@ -4,7 +4,7 @@ namespace CKAN.ConsoleUI.Toolkit { /// - /// Object displaying a long screen in a big box + /// Object displaying a long string in a big box /// public class ConsoleTextBox : ScreenObject { diff --git a/ConsoleUI/Toolkit/Keys.cs b/ConsoleUI/Toolkit/Keys.cs index bd5378fe4..b136c66e0 100644 --- a/ConsoleUI/Toolkit/Keys.cs +++ b/ConsoleUI/Toolkit/Keys.cs @@ -240,6 +240,13 @@ public static class Keys { 'e', ConsoleKey.E, false, false, false ); + /// + /// Representation of letter 'k' for key bindings + /// + public static readonly ConsoleKeyInfo K = new ConsoleKeyInfo( + 'k', ConsoleKey.K, false, false, false + ); + /// /// Representation of letter 'n' for key bindings /// diff --git a/Core/GameInstanceManager.cs b/Core/GameInstanceManager.cs index 1a6a114a4..7fc0ec9dd 100644 --- a/Core/GameInstanceManager.cs +++ b/Core/GameInstanceManager.cs @@ -655,7 +655,8 @@ public IGame DetermineGame(DirectoryInfo path, IUser user) default: // Prompt user to choose int selection = user.RaiseSelectionDialog( - $"Please select the game that is installed at {path.FullName.Replace('/', Path.DirectorySeparatorChar)}", + string.Format(Properties.Resources.GameInstanceManagerSelectGamePrompt, + Platform.FormatPath(path.FullName)), matchingGames.Select(g => g.ShortName).ToArray()); return selection >= 0 ? matchingGames[selection] : null; } diff --git a/Core/Platform.cs b/Core/Platform.cs index be780ef65..8fe91c304 100644 --- a/Core/Platform.cs +++ b/Core/Platform.cs @@ -69,6 +69,9 @@ public static class Platform IsWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + public static string FormatPath(string p) + => p.Replace('/', Path.DirectorySeparatorChar); + public static bool IsAdministrator() { if (File.Exists("/.dockerenv")) diff --git a/Core/Properties/Resources.resx b/Core/Properties/Resources.resx index 46a42c6bb..819499419 100644 --- a/Core/Properties/Resources.resx +++ b/Core/Properties/Resources.resx @@ -203,6 +203,7 @@ Install the `mono-complete` package or equivalent for your operating system.{0} Install: {1} portable Auto {0} + Please select the game that is installed at {0} The specified instance is not a valid {0} instance The specified {0} version is not a known version: {1} The specified folder already exists and is not empty diff --git a/Core/Types/Kraken.cs b/Core/Types/Kraken.cs index 11930317f..58d6e1bfe 100644 --- a/Core/Types/Kraken.cs +++ b/Core/Types/Kraken.cs @@ -233,7 +233,7 @@ public FailedToDeleteFilesKraken(string identifier, List undeletableFile : base(string.Format(Properties.Resources.KrakenFailedToDeleteFiles, identifier, string.Join(Environment.NewLine, - undeletableFiles.Select(f => f.Replace('/', Path.DirectorySeparatorChar))))) + undeletableFiles.Select(Platform.FormatPath)))) { this.undeletableFiles = undeletableFiles; } @@ -482,7 +482,7 @@ public RegistryInUseKraken(string path, string reason = null, Exception inner_ex public override string ToString() => string.Format(Properties.Resources.KrakenAlreadyRunning, - lockfilePath.Replace('/', Path.DirectorySeparatorChar)); + Platform.FormatPath(lockfilePath)); } /// diff --git a/GUI/Controls/DeleteDirectories.cs b/GUI/Controls/DeleteDirectories.cs index cc13f2550..da447d008 100644 --- a/GUI/Controls/DeleteDirectories.cs +++ b/GUI/Controls/DeleteDirectories.cs @@ -34,7 +34,7 @@ public void LoadDirs(GameInstance ksp, HashSet possibleConfigOnlyDirs) instance = ksp; var items = possibleConfigOnlyDirs .OrderBy(d => d) - .Select(d => new ListViewItem(instance.ToRelativeGameDir(d).Replace('/', Path.DirectorySeparatorChar)) + .Select(d => new ListViewItem(Platform.FormatPath(instance.ToRelativeGameDir(d))) { Tag = d, Checked = true @@ -112,7 +112,7 @@ private void DirectoriesListView_ItemSelectionChanged(object sender, ListViewIte lvi.Tag as string, "*", SearchOption.AllDirectories) - .Select(f => new ListViewItem(instance.ToRelativeGameDir(f).Replace('/', Path.DirectorySeparatorChar)))) + .Select(f => new ListViewItem(Platform.FormatPath(CKANPathUtils.ToRelative(f, lvi.Tag as string))))) .ToArray()); if (DirectoriesListView.SelectedItems.Count == 0) { diff --git a/GUI/Controls/UnmanagedFiles.cs b/GUI/Controls/UnmanagedFiles.cs index 992a9535a..cc1952827 100644 --- a/GUI/Controls/UnmanagedFiles.cs +++ b/GUI/Controls/UnmanagedFiles.cs @@ -56,7 +56,7 @@ private void _UpdateGameFolderTree() GameFolderTree.Nodes.Clear(); var rootNode = GameFolderTree.Nodes.Add( "", - inst.GameDir().Replace('/', Path.DirectorySeparatorChar), + Platform.FormatPath(inst.GameDir()), "folder", "folder"); UseWaitCursor = true; @@ -167,7 +167,7 @@ private void DeleteButton_Click(object sender, EventArgs e) } else if (!string.IsNullOrEmpty(relPath) && Main.Instance.YesNoDialog( string.Format(Properties.Resources.DeleteUnmanagedFileConfirmation, - relPath.Replace('/', Path.DirectorySeparatorChar)), + Platform.FormatPath(relPath)), Properties.Resources.DeleteUnmanagedFileDelete, Properties.Resources.DeleteUnmanagedFileCancel)) { diff --git a/GUI/Dialogs/CloneGameInstanceDialog.cs b/GUI/Dialogs/CloneGameInstanceDialog.cs index 0acc8b814..fca447684 100644 --- a/GUI/Dialogs/CloneGameInstanceDialog.cs +++ b/GUI/Dialogs/CloneGameInstanceDialog.cs @@ -56,7 +56,7 @@ private void comboBoxKnownInstance_SelectedIndexChanged(object sender, EventArgs string sel = comboBoxKnownInstance.SelectedItem as string; textBoxClonePath.Text = string.IsNullOrEmpty(sel) ? "" - : manager.Instances[sel].GameDir().Replace('/', Path.DirectorySeparatorChar); + : Platform.FormatPath(manager.Instances[sel].GameDir()); } /// @@ -122,7 +122,7 @@ private async void buttonOK_Click(object sender, EventArgs e) try { if (!manager.Instances.TryGetValue(comboBoxKnownInstance.SelectedItem as string, out GameInstance instanceToClone) - || existingPath != instanceToClone.GameDir().Replace('/', Path.DirectorySeparatorChar)) + || existingPath != Platform.FormatPath(instanceToClone.GameDir())) { IGame sourceGame = manager.DetermineGame(new DirectoryInfo(existingPath), user); if (sourceGame == null) @@ -154,13 +154,15 @@ await Task.Run(() => } catch (NotKSPDirKraken kraken) { - user.RaiseError(string.Format(Properties.Resources.CloneFakeKspDialogInstanceNotValid, kraken.path.Replace('/', Path.DirectorySeparatorChar))); + user.RaiseError(string.Format(Properties.Resources.CloneFakeKspDialogInstanceNotValid, + Platform.FormatPath(kraken.path))); reactivateDialog(); return; } catch (PathErrorKraken kraken) { - user.RaiseError(string.Format(Properties.Resources.CloneFakeKspDialogDestinationNotEmpty, kraken.path.Replace('/', Path.DirectorySeparatorChar))); + user.RaiseError(string.Format(Properties.Resources.CloneFakeKspDialogDestinationNotEmpty, + Platform.FormatPath(kraken.path))); reactivateDialog(); return; } diff --git a/GUI/Dialogs/CompatibleGameVersionsDialog.cs b/GUI/Dialogs/CompatibleGameVersionsDialog.cs index 64a9d7205..82a89a140 100644 --- a/GUI/Dialogs/CompatibleGameVersionsDialog.cs +++ b/GUI/Dialogs/CompatibleGameVersionsDialog.cs @@ -38,7 +38,7 @@ public CompatibleGameVersionsDialog(GameInstance inst, bool centerScreen) var compatibleVersions = inst.GetCompatibleVersions(); GameVersionLabel.Text = inst.Version()?.ToString() ?? Properties.Resources.CompatibleGameVersionsDialogNone; - GameLocationLabel.Text = inst.GameDir().Replace('/', Path.DirectorySeparatorChar); + GameLocationLabel.Text = Platform.FormatPath(inst.GameDir()); var knownVersions = inst.game.KnownVersions; var majorVersionsList = CreateMajorVersionsList(knownVersions); var compatibleVersionsLeftOthers = compatibleVersions.Except(knownVersions) diff --git a/GUI/Dialogs/ManageGameInstancesDialog.cs b/GUI/Dialogs/ManageGameInstancesDialog.cs index f35653482..25a6b8465 100644 --- a/GUI/Dialogs/ManageGameInstancesDialog.cs +++ b/GUI/Dialogs/ManageGameInstancesDialog.cs @@ -136,7 +136,7 @@ private string[] rowItems(GameInstance instance, bool includeGame, bool includeP list.Add(instance.playTime?.ToString() ?? ""); } - list.Add(instance.GameDir().Replace('/', Path.DirectorySeparatorChar)); + list.Add(Platform.FormatPath(instance.GameDir())); return list.ToArray(); } diff --git a/GUI/Main/Main.cs b/GUI/Main/Main.cs index 71edf32de..3c0fc0de7 100644 --- a/GUI/Main/Main.cs +++ b/GUI/Main/Main.cs @@ -393,7 +393,7 @@ private void CurrentInstanceUpdated() Util.Invoke(this, () => { - Text = $"CKAN {Meta.GetVersion()} - {CurrentInstance.game.ShortName} {CurrentInstance.Version()} -- {CurrentInstance.GameDir().Replace('/', Path.DirectorySeparatorChar)}"; + Text = $"CKAN {Meta.GetVersion()} - {CurrentInstance.game.ShortName} {CurrentInstance.Version()} -- {Platform.FormatPath(CurrentInstance.GameDir())}"; UpdateStatusBar(); }); diff --git a/GUI/Main/MainInstall.cs b/GUI/Main/MainInstall.cs index f98bdb489..7ff783f7c 100644 --- a/GUI/Main/MainInstall.cs +++ b/GUI/Main/MainInstall.cs @@ -334,7 +334,7 @@ private void HandlePossibleConfigOnlyDirs(Registry registry, HashSet pos .Any(relF => registry.FileOwner(relF) != null)); if (possibleConfigOnlyDirs.Count > 0) { - currentUser.RaiseMessage(""); + Util.Invoke(this, () => StatusLabel.ToolTipText = StatusLabel.Text = ""); tabController.ShowTab("DeleteDirectoriesTabPage", 4); tabController.SetTabLock(true);