From fbba4e4999a76916943923036feaa181434ac4d3 Mon Sep 17 00:00:00 2001 From: Flaky Date: Fri, 2 Jan 2026 10:58:22 +0000 Subject: [PATCH] remove terminal.gui project --- TagHierarchyManager.sln | 9 - UI/Terminal/Program.cs | 55 ---- .../Properties/PublishProfiles/Linux.pubxml | 12 - .../Properties/PublishProfiles/Windows.pubxml | 12 - UI/Terminal/Services/TagDatabaseService.cs | 266 ----------------- .../TagHierarchyManager.UI.TerminalUI.csproj | 25 -- ...hyManager.UI.TerminalUI.csproj.DotSettings | 7 - UI/Terminal/Views/Dialogs/DbSettingsDialog.cs | 63 ---- UI/Terminal/Views/Dialogs/ImportDialog.cs | 234 --------------- UI/Terminal/Views/Dialogs/UserInputDialog.cs | 39 --- .../Views/EditorWindow/EditorWindow.cs | 136 --------- .../Views/EditorWindow/MultiModeStatusBar.cs | 190 ------------ .../Views/EditorWindow/TagBrowserPane.cs | 240 --------------- .../Views/EditorWindow/TagEditorPane.cs | 280 ------------------ .../EditorWindow/TagHierarchyTreeNode.cs | 27 -- UI/Terminal/Views/StandardDialogs.cs | 159 ---------- UI/Terminal/Views/TopMenu.cs | 144 --------- UI/Terminal/Views/TopView.cs | 47 --- 18 files changed, 1945 deletions(-) delete mode 100644 UI/Terminal/Program.cs delete mode 100644 UI/Terminal/Properties/PublishProfiles/Linux.pubxml delete mode 100644 UI/Terminal/Properties/PublishProfiles/Windows.pubxml delete mode 100644 UI/Terminal/Services/TagDatabaseService.cs delete mode 100644 UI/Terminal/TagHierarchyManager.UI.TerminalUI.csproj delete mode 100644 UI/Terminal/TagHierarchyManager.UI.TerminalUI.csproj.DotSettings delete mode 100644 UI/Terminal/Views/Dialogs/DbSettingsDialog.cs delete mode 100644 UI/Terminal/Views/Dialogs/ImportDialog.cs delete mode 100644 UI/Terminal/Views/Dialogs/UserInputDialog.cs delete mode 100644 UI/Terminal/Views/EditorWindow/EditorWindow.cs delete mode 100644 UI/Terminal/Views/EditorWindow/MultiModeStatusBar.cs delete mode 100644 UI/Terminal/Views/EditorWindow/TagBrowserPane.cs delete mode 100644 UI/Terminal/Views/EditorWindow/TagEditorPane.cs delete mode 100644 UI/Terminal/Views/EditorWindow/TagHierarchyTreeNode.cs delete mode 100644 UI/Terminal/Views/StandardDialogs.cs delete mode 100644 UI/Terminal/Views/TopMenu.cs delete mode 100644 UI/Terminal/Views/TopView.cs diff --git a/TagHierarchyManager.sln b/TagHierarchyManager.sln index 9ea0ac1..6d3e6ea 100644 --- a/TagHierarchyManager.sln +++ b/TagHierarchyManager.sln @@ -9,10 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{397693A2 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagHierarchyManager.Tests", "Tests\TagHierarchyManager.Tests.csproj", "{F26B4A2D-0389-4FE8-8B0D-E491B354AC83}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{9666D8FC-785C-4F34-B224-21636441C508}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagHierarchyManager.UI.TerminalUI", "UI\Terminal\TagHierarchyManager.UI.TerminalUI.csproj", "{FC98B5BA-1E3D-42F2-8A32-CDDAD1FEDF72}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,13 +26,8 @@ Global {F26B4A2D-0389-4FE8-8B0D-E491B354AC83}.Debug|Any CPU.Build.0 = Debug|Any CPU {F26B4A2D-0389-4FE8-8B0D-E491B354AC83}.Release|Any CPU.ActiveCfg = Release|Any CPU {F26B4A2D-0389-4FE8-8B0D-E491B354AC83}.Release|Any CPU.Build.0 = Release|Any CPU - {FC98B5BA-1E3D-42F2-8A32-CDDAD1FEDF72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC98B5BA-1E3D-42F2-8A32-CDDAD1FEDF72}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC98B5BA-1E3D-42F2-8A32-CDDAD1FEDF72}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC98B5BA-1E3D-42F2-8A32-CDDAD1FEDF72}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {F26B4A2D-0389-4FE8-8B0D-E491B354AC83} = {397693A2-4BD9-4EA2-B70E-84B0B4DD2BF9} - {FC98B5BA-1E3D-42F2-8A32-CDDAD1FEDF72} = {9666D8FC-785C-4F34-B224-21636441C508} EndGlobalSection EndGlobal diff --git a/UI/Terminal/Program.cs b/UI/Terminal/Program.cs deleted file mode 100644 index 653e9b3..0000000 --- a/UI/Terminal/Program.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Serilog; -using TagHierarchyManager.Models; -using TagHierarchyManager.UI.TerminalUI.Views; -using Terminal.Gui.App; - -namespace TagHierarchyManager.UI.TerminalUI; - -/// -/// The class that the application will execute at first. -/// -internal static class Program -{ - /// - /// A logger for debugging purposes that logs to any compatible .NET debugger, using Serilog. - /// - public static readonly ILogger Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .WriteTo.Debug() - .CreateLogger(); - - /// - /// Gets the top-level view, which also encapsulates the main window of the application. - /// - internal static readonly TopView TopView = new(); - - /// - /// Gets the current database associated with the running process of the application. - /// - internal static TagDatabase? CurrentDatabase = null!; - - /// - /// Gets the main editor window of the application, essentially acting as a shortcut. - /// - internal static EditorWindow MainView => TopView.Window; - - /// - /// The method the .NET runtime will use to execute the application. - /// - public static void Main() - { - try - { - Application.Init(); - Application.Run(TopView); - } - catch (Exception ex) - { - Logger.Fatal(ex, "FATAL ERROR WAS CAUGHT."); - } - finally - { - Application.Shutdown(); - } - } -} \ No newline at end of file diff --git a/UI/Terminal/Properties/PublishProfiles/Linux.pubxml b/UI/Terminal/Properties/PublishProfiles/Linux.pubxml deleted file mode 100644 index 6bb4f1b..0000000 --- a/UI/Terminal/Properties/PublishProfiles/Linux.pubxml +++ /dev/null @@ -1,12 +0,0 @@ - - - - linux-x64 - true - true - full - true - none - false - - diff --git a/UI/Terminal/Properties/PublishProfiles/Windows.pubxml b/UI/Terminal/Properties/PublishProfiles/Windows.pubxml deleted file mode 100644 index 4e8b01c..0000000 --- a/UI/Terminal/Properties/PublishProfiles/Windows.pubxml +++ /dev/null @@ -1,12 +0,0 @@ - - - - win-x64 - true - true - full - true - none - false - - diff --git a/UI/Terminal/Services/TagDatabaseService.cs b/UI/Terminal/Services/TagDatabaseService.cs deleted file mode 100644 index 4796970..0000000 --- a/UI/Terminal/Services/TagDatabaseService.cs +++ /dev/null @@ -1,266 +0,0 @@ -using TagHierarchyManager.Common; -using TagHierarchyManager.Exporters; -using TagHierarchyManager.Importers; -using TagHierarchyManager.Models; -using TagHierarchyManager.UI.TerminalUI.Views; -using Terminal.Gui.App; - -namespace TagHierarchyManager.UI.TerminalUI.Services; - -/// -/// Handles interaction between the and the UI. -/// -public static class TagDatabaseService -{ - /// - /// An event thrown when the database initialisation has started. - /// - public static event EventHandler DatabaseInitialising = delegate { }; - - /// - /// An event thrown when the database export process has completed, storing a string with it pointing to the - /// export's file path. - /// - public static event EventHandler ExportCompleted = delegate { }; - - /// - /// Thrown when an exception has occurred while exporting, storing a string with it - /// specifying the error. - /// - public static event EventHandler ExportError = delegate { }; - - /// - /// Thrown when the database export process has started. - /// - public static event EventHandler ExportStarted = delegate { }; - - /// - /// Thrown when an exception has occurred while processing data to import, storing a string with it - /// specifying the error. - /// - public static event EventHandler ImportError = delegate { }; - - /// - /// Thrown when the initialisation has completed. - /// - public static event EventHandler InitialisationComplete = delegate { }; - - /// - /// Thrown when an exception has occurred while attempting to initialise the database, storing a string with it - /// specifying the error. - /// - public static event EventHandler InitialisationError = delegate { }; - - /// - /// Thrown when the search process has finished, storing with it the number of Tags found. - /// - public static event EventHandler> SearchFinished = delegate { }; - - /// - /// Thrown when a new Tag has been added, storing the newly created Tag object with it. - /// - public static event EventHandler TagAdded = delegate { }; - - /// - /// Thrown when a Tag has been deleted, storing the ID and name of the Tag with it. - /// - public static event EventHandler<(int, string)> TagDeleted = delegate { }; - - /// - /// Thrown when a Tag has been saved, regardless of whether it was new or not, storing the saved Tag object - /// with it. - /// - public static event EventHandler TagSaved = delegate { }; - - // should this be generic and inside the Core? - // not yet, we'll see when i get to adding the avalonia ui. - - /// - /// Throws a save dialog to the user, and starts the initialisation process. - /// - public static void CreateDatabase((string filePath, bool userAllowedOverwrite) newDatabase) - { - StartDatabaseInit(newDatabase.filePath, newDatabase.userAllowedOverwrite); - } - - public static async Task DeleteTag(Tag tag) - { - string tagName = tag.Name; - int tagId = tag.Id; - if (Program.CurrentDatabase != null) await Program.CurrentDatabase.DeleteTag(tag.Id); - TagDeleted.Invoke(null, (tagId, tagName)); - - } - - /// - /// Throws a load dialog to the user, and starts the initialisation process. - /// - public static void PromptDatabaseLoad() - { - string? newDatabasePath = StandardDialogs.DatabaseOpenDialog(); - if (newDatabasePath is null) return; - - StartDatabaseInit(newDatabasePath); - } - - public static void SearchTags(string query, TagDatabaseSearchMode mode, bool searchAliases) - { - if (Program.CurrentDatabase is null) throw new InvalidOperationException(ErrorMessages.DbNotInitialised); - - List tags = searchAliases - ? Program.CurrentDatabase.SearchWithAliases(query, mode) - : Program.CurrentDatabase.Search(query, mode); - SearchFinished.Invoke(Program.CurrentDatabase, tags.OrderBy(t => t.Name).ToList()); - } - - /// - /// Starts the database initialisation. - /// - /// The file path of the database to create or load. - /// - /// Whether to overwrite the database file on filePath.
- /// Null implies database load - true/false implies database creation. - /// - /// Optional path to a file to import into the new database (used on creation). - public static void StartDatabaseInit( - string filePath, - bool? overwriteOnCreation = null, - string? importedFilePath = null) - { - ClearMainWindowData(); - - TagDatabase db = new(); - SubscribeToEvents(db); - - _ = Task.Run(async () => - { - try - { - Dictionary? tagsToImport = null; - if (overwriteOnCreation.HasValue) - { - if (!string.IsNullOrEmpty(importedFilePath)) - try - { - tagsToImport = await PickImporterFromFileExt(importedFilePath) - .ImportFromFileAsync(importedFilePath); - } - catch (Exception ex) - { - Application.Invoke(() => ImportError.Invoke(null, ex.Message)); - return; - } - - DatabaseInitialising.Invoke(null, EventArgs.Empty); - await db.CreateAsync(filePath, overwriteOnCreation.Value, - tagsToImport); - } - else - { - DatabaseInitialising.Invoke(null, EventArgs.Empty); - await db.LoadAsync(filePath); - } - } - catch (Exception ex) - { - InitialisationError.Invoke(db, ex.Message); - UnsubscribeFromEvents(db); - } - }); - } - - public static void StartExportProcess() - { - if (Program.CurrentDatabase is null) return; - (string filePath, bool userAllowedOverwrite)? saveLocation = StandardDialogs.ExportSaveDialog(); - if (saveLocation is null) return; - _ = Task.Run(async () => - { - ExportStarted.Invoke(null, EventArgs.Empty); - await ExportToFile(saveLocation.Value); - }); - } - - public static async Task WriteTagToDatabase(Tag tag) - { - bool newTag = tag.Id == 0; - if (Program.CurrentDatabase != null) await Program.CurrentDatabase.WriteTagToDatabase(tag); - - if (newTag) TagAdded.Invoke(Program.CurrentDatabase, tag); - TagSaved.Invoke(Program.CurrentDatabase, tag); - } - - /// - /// Clears the data associated with the main window, prompting the user if unsaved changes were detected. - /// - /// True if the data has been cleared. - private static void ClearMainWindowData() - { - if (Program.CurrentDatabase is null) return; - UnsubscribeFromEvents(Program.CurrentDatabase); - Program.CurrentDatabase.Close(); - } - - private static async Task ExportToFile((string filePath, bool userAllowedOverwrite) exportLocation) - { - if (Program.CurrentDatabase is null) return; - IExporter exporter = PickExporterFromFileExt(exportLocation.filePath); - try - { - string export = exporter.ExportDatabase(Program.CurrentDatabase); - if (exportLocation.userAllowedOverwrite && File.Exists(exportLocation.filePath)) - File.Delete(exportLocation.filePath); - await File.WriteAllTextAsync(exportLocation.filePath, export); - ExportCompleted.Invoke(null, exportLocation.filePath); - } - catch (Exception ex) - { - ExportError.Invoke(exportLocation.filePath, ex.Message); - } - } - - private static void OnInitialisationComplete(object? sender, EventArgs e) - { - Application.Invoke(() => - { - Program.Logger.Debug("[LoadingDialog.OnInitialised] Event has been caught"); - Program.Logger.Information("[LoadingDialog.OnInitialised] Sender: {@Sender}", sender); - if (sender is not TagDatabase db) return; - - Program.CurrentDatabase = db; - - InitialisationComplete.Invoke(db, EventArgs.Empty); - }); - } - - private static IExporter PickExporterFromFileExt(string path) - { - string fileExt = Path.GetExtension(path); - - // ReSharper disable once ConvertIfStatementToReturnStatement - if (fileExt == FileTypes.MusicBeeTagHierarchyTemplate.FileExtension) return new MusicBeeTagHierarchyExporter(); - - throw new NotSupportedException($"File extension '{fileExt}' is not supported."); - } - - private static Importer PickImporterFromFileExt(string path) - { - string fileExt = Path.GetExtension(path); - - // if more importers are added, convert this to a switch statement based on file extension. - // ReSharper disable once ConvertIfStatementToReturnStatement - if (fileExt == FileTypes.MusicBeeTagHierarchyTemplate.FileExtension) return new MusicBeeTagHierarchyImporter(); - - throw new NotSupportedException($"File extension '{fileExt}' is not supported."); - } - - private static void SubscribeToEvents(TagDatabase db) - { - db.InitialisationComplete += OnInitialisationComplete; - } - - private static void UnsubscribeFromEvents(TagDatabase db) - { - db.InitialisationComplete -= OnInitialisationComplete; - } -} \ No newline at end of file diff --git a/UI/Terminal/TagHierarchyManager.UI.TerminalUI.csproj b/UI/Terminal/TagHierarchyManager.UI.TerminalUI.csproj deleted file mode 100644 index a51b83c..0000000 --- a/UI/Terminal/TagHierarchyManager.UI.TerminalUI.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - Exe - net9.0 - enable - enable - portable - TagHierarchyManager.UI.TerminalUI - - - - - - - - - - - - - - - - diff --git a/UI/Terminal/TagHierarchyManager.UI.TerminalUI.csproj.DotSettings b/UI/Terminal/TagHierarchyManager.UI.TerminalUI.csproj.DotSettings deleted file mode 100644 index dcf9c9b..0000000 --- a/UI/Terminal/TagHierarchyManager.UI.TerminalUI.csproj.DotSettings +++ /dev/null @@ -1,7 +0,0 @@ - - True - True \ No newline at end of file diff --git a/UI/Terminal/Views/Dialogs/DbSettingsDialog.cs b/UI/Terminal/Views/Dialogs/DbSettingsDialog.cs deleted file mode 100644 index 7876294..0000000 --- a/UI/Terminal/Views/Dialogs/DbSettingsDialog.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Terminal.Gui.App; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -/// -/// A for handling TagDatabase settings. -/// -internal class DbSettingsDialog : UserInputDialog -{ - private readonly TextField defaultTagBindingField = new() - { - X = 0, - Width = Dim.Fill(), - }; - - /// - /// Initializes a new instance of the class. - /// - public DbSettingsDialog() - { - this.InitialiseUI(); - } - - private void InitialiseUI() - { - this.Title = $"Settings - {Program.CurrentDatabase!.Name}"; - this.Width = 70; - this.Height = 12; - - Label schemaVersionLabel = new() - { - X = 0, - Y = 0, - Title = $"Database version: {Program.CurrentDatabase.Version}", - }; - - Label defaultTagBindingLabel = new() - { - X = 0, - Y = Pos.Bottom(schemaVersionLabel) + 1, - Title = "Default tag binding (separated by semi-colon):", - }; - this.defaultTagBindingField.Y = Pos.Bottom(defaultTagBindingLabel); - this.defaultTagBindingField.Text = string.Join(';', Program.CurrentDatabase.DefaultTagBindings); - this.OkButton.Accepting += this.OkButton_OnAccepting; - - this.Add(schemaVersionLabel, defaultTagBindingLabel, this.defaultTagBindingField); - } - - private void OkButton_OnAccepting(object? sender, EventArgs e) - { - string newDefaultTagBindings = this.defaultTagBindingField.Text.Replace("::", string.Empty); - Program.CurrentDatabase!.DefaultTagBindings = - newDefaultTagBindings.Split( - ';', - StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) - .ToList(); - Program.Logger.Debug("{@TagBindings}", Program.CurrentDatabase.DefaultTagBindings); - Application.RequestStop(); - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/Dialogs/ImportDialog.cs b/UI/Terminal/Views/Dialogs/ImportDialog.cs deleted file mode 100644 index a840ebf..0000000 --- a/UI/Terminal/Views/Dialogs/ImportDialog.cs +++ /dev/null @@ -1,234 +0,0 @@ -using TagHierarchyManager.Models; -using TagHierarchyManager.UI.TerminalUI.Services; -using Terminal.Gui.App; -using Terminal.Gui.Drivers; -using Terminal.Gui.Input; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -/// -/// A for importing MusicBee tag hierarchy files. -/// -internal class ImportDialog : UserInputDialog -{ - private (string filePath, bool userAllowedOverwrite)? chosenDatabaseLocation; - private Label errorLabel = null!; - private View importingView = null!; - - /// - /// Initializes a new instance of the class. - /// - public ImportDialog() - { - this.InitialiseUI(); - } - - /// - /// Gets the object representing the current database to save to. - /// - internal (string filePath, bool userAllowedOverwrite)? CurrentDatabase => this.chosenDatabaseLocation; - - /// - /// Gets the containing the file path to where the new should be - /// saved. - /// - private TextField DatabaseField { get; set; } = null!; - - /// - /// Gets the containing the file path to the target file for import. - /// - private TextField ImportedFileField { get; set; } = null!; - - private void databaseBrowseButton_OnAccepting(object? sender, CommandEventArgs e) - { - (string filePath, bool userAllowedOverwrite)? dbFile = StandardDialogs.NewDatabaseSaveDialog(); - if (dbFile.HasValue) - { - this.chosenDatabaseLocation = dbFile; - this.DatabaseField.Text = dbFile.Value.filePath; - this.DatabaseField.CursorPosition = dbFile.Value.filePath.Length; - - if (!string.IsNullOrEmpty(this.ImportedFileField.Text)) this.OkButton.Enabled = true; - } - - e.Handled = true; - } - - private void InitialiseUI() - { - this.Title = "Import file"; - this.Arrangement = ViewArrangement.Movable; - this.Width = 70; - this.Height = 12; - - Label tagHierarchyLabel = new() - { - X = 0, - Y = 0, - Title = "File to import:", - }; - - const string browseTitle = "Browse"; - - this.ImportedFileField = new TextField - { - Y = Pos.Bottom(tagHierarchyLabel), - Width = Dim.Fill() ! - (browseTitle.Length + 5), - CanFocus = false, - CursorVisibility = CursorVisibility.Invisible, - }; - - Button importFileBrowseButton = new() - { - X = Pos.Right(this.ImportedFileField), - Y = Pos.Bottom(tagHierarchyLabel), - Width = Dim.Fill(), - Title = browseTitle, - }; - importFileBrowseButton.Accepting += this.OnImportFileBrowseClick; - - Label databaseLabel = new() - { - X = 0, - Y = Pos.Bottom(this.ImportedFileField) + 1, - Title = "Database to save to:", - }; - - this.DatabaseField = new TextField - { - Y = Pos.Bottom(databaseLabel), - Width = Dim.Fill() ! - (browseTitle.Length + 5), - CanFocus = false, - CursorVisibility = CursorVisibility.Invisible, - }; - - Button databaseBrowseButton = new() - { - X = Pos.Right(this.DatabaseField), - Y = Pos.Bottom(databaseLabel), - Width = Dim.Fill(), - Title = browseTitle, - }; - databaseBrowseButton.Accepting += this.databaseBrowseButton_OnAccepting; - - this.importingView = new View - { - X = 0, - Y = Pos.Bottom(this.DatabaseField) + 1, - Width = Dim.Fill(), - Height = 1, - }; - - this.errorLabel = new Label - { - X = 0, - Y = Pos.Bottom(this.DatabaseField) + 1, - Width = Dim.Fill(), - Visible = false, - Text = "Import failed.", - }; - - SpinnerView spinner = new() - { - AutoSpin = true, - Style = new SpinnerStyle.Dots(), - X = 0, - }; - - Label importingLabel = new() - { - Title = "Importing...", - Width = Dim.Auto(), - X = Pos.Right(spinner) + 1, - }; - - this.OkButton.Accepting += this.OkButton_OnAccepting; - this.OkButton.Enabled = false; - - this.Add(tagHierarchyLabel, this.ImportedFileField, importFileBrowseButton, databaseLabel, - this.DatabaseField, databaseBrowseButton, this.importingView, this.errorLabel); - this.importingView.Add(spinner, importingLabel); - this.importingView.Visible = false; - TagDatabaseService.ImportError += this.TagDatabaseService_OnImportError; - TagDatabaseService.InitialisationComplete += this.TagDatabaseService_OnInitialisationComplete; - this.Closing += this.OnClosing; - } - - private void OkButton_OnAccepting(object? sender, CommandEventArgs e) - { - if (this.chosenDatabaseLocation is null || - string.IsNullOrEmpty(this.ImportedFileField.Text)) - { - MessageBox.ErrorQuery( - "Error", - "One or more files were missing for the import process.\nPlease choose a valid file to import and/or a location for the tag database, and try again.", - "OK"); - } - else - { - foreach (Button button in this.Buttons) button.Enabled = false; - this.errorLabel.Visible = false; - this.importingView.Visible = true; - TagDatabaseService.StartDatabaseInit( - this.chosenDatabaseLocation.Value.filePath, - this.chosenDatabaseLocation.Value.userAllowedOverwrite, - this.ImportedFileField.Text); - } - - e.Handled = true; - } - - private void OnClosing(object? sender, ToplevelClosingEventArgs e) - { - if (this.importingView.Visible) - { - e.Cancel = true; - return; - } - - this.importingView.Visible = false; - this.errorLabel.Visible = false; - TagDatabaseService.ImportError -= this.TagDatabaseService_OnImportError; - TagDatabaseService.InitialisationComplete -= this.TagDatabaseService_OnInitialisationComplete; - - this.Closing -= this.OnClosing; - } - - private void OnImportFileBrowseClick(object? sender, CommandEventArgs e) - { - string? tagHierarchyFilePath = StandardDialogs.ImportOpenDialog(); - if (tagHierarchyFilePath is not null) - { - this.ImportedFileField.Text = tagHierarchyFilePath; - this.ImportedFileField.CursorPosition = tagHierarchyFilePath.Length; - - if (this.chosenDatabaseLocation is not null) this.OkButton.Enabled = true; - } - - e.Handled = true; - } - - private void TagDatabaseService_OnImportError(object? sender, string e) - { - Application.Invoke(() => - { - this.importingView.Visible = false; - this.errorLabel.Visible = true; - foreach (Button button in this.Buttons) button.Enabled = true; - MessageBox.ErrorQuery("Error", $"An error occurred trying to import the file:\n{e}", "OK"); - }); - } - - private void TagDatabaseService_OnInitialisationComplete(object? sender, EventArgs e) - { - Application.Invoke(() => - { - this.importingView.Visible = false; - this.errorLabel.Visible = false; - foreach (Button button in this.Buttons) button.Enabled = true; - this.RequestStop(); - }); - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/Dialogs/UserInputDialog.cs b/UI/Terminal/Views/Dialogs/UserInputDialog.cs deleted file mode 100644 index 3009fe5..0000000 --- a/UI/Terminal/Views/Dialogs/UserInputDialog.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Terminal.Gui.App; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -/// -/// A base class implementing a template centered around user-facing input. -/// -public class UserInputDialog : Dialog -{ - protected Button OkButton = null!; - private Button cancelButton = null!; - - /// - /// Initializes a new instance of the class. - /// - protected UserInputDialog() - { - this.InitialiseUI(); - } - - private void InitialiseUI() - { - this.OkButton = new Button - { - Title = "OK", - }; - - this.cancelButton = new Button - { - Title = "Cancel", - }; - - this.cancelButton.Accepting += (_, _) => Application.RequestStop(); - - this.AddButton(this.OkButton); - this.AddButton(this.cancelButton); - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/EditorWindow/EditorWindow.cs b/UI/Terminal/Views/EditorWindow/EditorWindow.cs deleted file mode 100644 index 6de7f78..0000000 --- a/UI/Terminal/Views/EditorWindow/EditorWindow.cs +++ /dev/null @@ -1,136 +0,0 @@ -using TagHierarchyManager.Models; -using TagHierarchyManager.UI.TerminalUI.Services; -using Terminal.Gui.App; -using Terminal.Gui.Drawing; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -/// -/// A comprising of a for browsing the tag hierarchy, and a -/// for editing tag data. -/// -// tbh this could be static, but i think i'm done doing this for now lol. -public class EditorWindow : Window -{ - internal ImportDialog? ImportDialog = null; - - /// - /// Initializes a new instance of the class. - /// - /// The top menu to base the window position on. - public EditorWindow(TopMenu topMenu) - { - this.BorderStyle = LineStyle.None; - this.Y = Pos.Bottom(topMenu); - - this.TagPane = new TagBrowserPane - { - X = 0, - Y = 0, - Width = Dim.Percent(20), - Height = Dim.Fill()! - 1, - CanFocus = true, - TabStop = TabBehavior.TabGroup, - }; - this.EditPane = new TagEditorPane - { - X = Pos.Right(this.TagPane), - Y = 0, - Width = Dim.Fill(), - Height = Dim.Fill()! - 1, - CanFocus = true, - TabStop = TabBehavior.TabGroup, - Enabled = false, - }; - this.StatusBar = new MultiModeStatusBar(); - - this.Add(this.TagPane, this.EditPane, this.StatusBar); - TagDatabaseService.InitialisationComplete += this.TagDatabaseService_InitialisationComplete; - TagDatabaseService.DatabaseInitialising += this.TagDatabaseService_OnDatabaseInitialising; - TagDatabaseService.ImportError += this.TagDatabaseService_OnImportError; - TagDatabaseService.InitialisationError += this.TagDatabaseService_OnInitialisationError; - TagDatabaseService.ExportError += this.TagDatabaseService_OnExportError; - TagDatabaseService.ExportStarted += this.TagDatabaseService_OnExportStarted; - TagDatabaseService.ExportCompleted += this.TagDatabaseService_OnExportCompleted; - } - - /// - /// Gets the inside the window. - /// - internal TagEditorPane EditPane { get; } - - - /// - /// Gets the inside the window. - /// - internal TagBrowserPane TagPane { get; } - - private MultiModeStatusBar StatusBar { get; } - - private void TagDatabaseService_InitialisationComplete(object? sender, EventArgs e) - { - if (sender is not TagDatabase) return; - Application.Invoke(() => - { - this.Enabled = true; - this.EditPane.Enabled = false; - }); - } - - - private void TagDatabaseService_OnDatabaseInitialising(object? sender, EventArgs e) - { - Application.Invoke(() => { this.Enabled = false; }); - } - - private void TagDatabaseService_OnExportCompleted(object? sender, string e) - { - Application.Invoke(() => - { - this.Enabled = true; - this.EditPane.EnableIfTagLoaded(); - }); - } - - private void TagDatabaseService_OnExportError(object? sender, string e) - { - Application.Invoke(() => - { - MessageBox.ErrorQuery("Error", - $"An error occurred during the export process.\n\nError message:\n{e}", - "OK"); - this.Enabled = true; - this.EditPane.EnableIfTagLoaded(); - }); - } - - private void TagDatabaseService_OnExportStarted(object? sender, EventArgs e) - { - Application.Invoke(() => { this.Enabled = false; }); - } - - - private void TagDatabaseService_OnImportError(object? sender, string e) - { - Application.Invoke(() => - { - this.Enabled = true; - this.EditPane.Enabled = false; - }); - } - - private void TagDatabaseService_OnInitialisationError(object? sender, string errorMessage) - { - Application.Invoke(() => - { - MessageBox.ErrorQuery("Error", - $"An error has occurred trying to initialise the database.\n\nError message:\n{errorMessage}", - "OK"); - if (Program.CurrentDatabase == null) return; - this.Enabled = true; - this.EditPane.EnableIfTagLoaded(); - }); - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/EditorWindow/MultiModeStatusBar.cs b/UI/Terminal/Views/EditorWindow/MultiModeStatusBar.cs deleted file mode 100644 index d88bee0..0000000 --- a/UI/Terminal/Views/EditorWindow/MultiModeStatusBar.cs +++ /dev/null @@ -1,190 +0,0 @@ -using TagHierarchyManager.Models; -using TagHierarchyManager.UI.TerminalUI.Services; -using Terminal.Gui.App; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -public class MultiModeStatusBar : View -{ - private TextOnly textOnlyView = null!; - private TextWithSpinner textWithSpinnerView = null!; - - public MultiModeStatusBar() - { - this.InitialiseUI(); - } - - private void InitialiseUI() - { - this.X = 0; - this.Y = Pos.AnchorEnd(1); - this.Width = Dim.Fill(); - this.Height = 1; - this.Visible = true; - this.CanFocus = false; - - this.textWithSpinnerView = new TextWithSpinner - { - Visible = false, - }; - - this.textOnlyView = new TextOnly - { - Visible = true, - }; - // Set the label text directly - this.textOnlyView.TextLabel.Text = "Ready."; - - this.Add(this.textWithSpinnerView, this.textOnlyView); - TagDatabaseService.InitialisationError += this.TagDatabaseService_OnInitialisationError; - TagDatabaseService.InitialisationComplete += this.TagDatabaseService_InitialisationComplete; - TagDatabaseService.ImportError += this.TagDatabaseService_OnImportError; - TagDatabaseService.DatabaseInitialising += this.TagDatabaseService_OnDatabaseInitialising; - TagDatabaseService.TagSaved += this.TagDatabaseService_OnTagSaved; - TagDatabaseService.TagDeleted += this.TagDatabaseService_OnTagDeleted; - TagDatabaseService.ExportStarted += this.TagDatabaseService_OnExportStarted; - TagDatabaseService.ExportCompleted += this.TagDatabaseService_OnExportCompleted; - TagDatabaseService.ExportError += this.TagDatabaseService_OnExportError; - TagDatabaseService.SearchFinished += this.TagDatabaseService_OnSearchFinished; - } - - private void TagDatabaseService_InitialisationComplete(object? sender, EventArgs e) - { - this.UpdateStatusBarTextOnly("Database initialized successfully."); - } - - private void TagDatabaseService_OnDatabaseInitialising(object? sender, EventArgs e) - { - this.UpdateStatusBarWithSpinner("Initializing database..."); - } - - private void TagDatabaseService_OnExportCompleted(object? sender, string filePath) - { - this.UpdateStatusBarTextOnly($"Database exported successfully to \"{filePath}\"."); - } - - private void TagDatabaseService_OnExportError(object? sender, string e) - { - this.UpdateStatusBarTextOnly("Export failed."); - } - - private void TagDatabaseService_OnExportStarted(object? sender, EventArgs e) - { - this.UpdateStatusBarWithSpinner("Exporting database..."); - } - - private void TagDatabaseService_OnImportError(object? sender, string e) - { - this.UpdateStatusBarTextOnly("Error importing: " + e); - } - - private void TagDatabaseService_OnSearchFinished(object? sender, List result) - { - if (result.Count == 0) this.Clear(); - else this.UpdateStatusBarTextOnly(result.Count == 1 ? "One result found." : $"{result.Count} results found."); - } - - private void TagDatabaseService_OnInitialisationError(object? sender, string e) - { - this.UpdateStatusBarTextOnly("Error initializing database: " + e); - } - - private void TagDatabaseService_OnTagDeleted(object? sender, (int tagId, string tagName) tagData) - { - this.UpdateStatusBarTextOnly($"Tag \"{tagData.tagName}\" deleted successfully."); - } - - private void TagDatabaseService_OnTagSaved(object? sender, Tag tag) - { - this.UpdateStatusBarTextOnly($"Tag \"{tag.Name}\" saved successfully."); - } - - private void Clear() - { - Application.Invoke(() => - { - this.textWithSpinnerView.Visible = false; - this.textOnlyView.Visible = false; - }); - } - - private void UpdateStatusBarTextOnly(string message) - { - Application.Invoke(() => - { - this.textOnlyView.TextLabel.Text = message; - this.textWithSpinnerView.Visible = false; - this.textOnlyView.Visible = true; - }); - } - - private void UpdateStatusBarWithSpinner(string message) - { - Application.Invoke(() => - { - this.textWithSpinnerView.TextLabel.Text = message; - this.textWithSpinnerView.Visible = true; - this.textOnlyView.Visible = false; - }); - } - - private sealed class TextOnly : View - { - internal readonly Label TextLabel; - - public TextOnly() - { - this.X = 0; - this.Y = 0; - this.Width = Dim.Fill(); - this.Height = 1; - - this.TextLabel = new Label - { - X = 0, - Y = 0, - Width = Dim.Fill(), - Height = 1, - Text = string.Empty, - }; - - this.Add(this.TextLabel); - } - } - - private sealed class TextWithSpinner : View - { - internal readonly Label TextLabel; - - public TextWithSpinner() - { - this.X = 0; - this.Y = 0; - this.Width = Dim.Fill(); - this.Height = 1; - - SpinnerView spinner = new() - { - X = 0, - Y = 0, - Width = 1, - Height = 1, - AutoSpin = true, - Style = new SpinnerStyle.Dots(), - }; - - this.TextLabel = new Label - { - X = Pos.Right(spinner) + 1, - Y = 0, - Width = Dim.Fill(), - Height = 1, - Text = string.Empty, - }; - - this.Add(spinner, this.TextLabel); - } - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/EditorWindow/TagBrowserPane.cs b/UI/Terminal/Views/EditorWindow/TagBrowserPane.cs deleted file mode 100644 index 5f5e87e..0000000 --- a/UI/Terminal/Views/EditorWindow/TagBrowserPane.cs +++ /dev/null @@ -1,240 +0,0 @@ -using System.Collections.ObjectModel; -using TagHierarchyManager.Common; -using TagHierarchyManager.Models; -using TagHierarchyManager.UI.TerminalUI.Services; -using Terminal.Gui.Input; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -/// -/// A paned view for browsing the 's tag hierarchy tree. -/// -public class TagBrowserPane : View -{ - internal readonly Button NewTagButton = new(); - private ComboBox modeSelector = null!; - private TextField queryField = null!; - private ListView resultsList = null!; - private TreeView tagTree = null!; - private CheckBox searchAliasesCheckbox = null!; - private bool resultsListAutoSelect = true; - - /// - /// Initializes a new instance of the class. - /// - public TagBrowserPane() - { - this.InitialiseUI(); - } - - private void TagDatabaseService_OnSearchFinished(object? sender, List results) - { - this.resultsListAutoSelect = true; - if (results.Count > 0) - this.resultsList.SetSource(new ObservableCollection(results)); - else - this.resultsList.SetSource(["No results found."]); - this.resultsList.SelectedItem = 0; - this.resultsListAutoSelect = false; - } - - private void InitialiseUI() - { - TabView tabView = new() - { - X = 0, - Y = 0, - Width = Dim.Fill(), - Height = Dim.Fill(), - CanFocus = true, - }; - - Tab tagTreeTab = new() - { - DisplayText = "Hierarchy Tree", - View = new View - { - Width = Dim.Fill(), - Height = Dim.Fill(), - CanFocus = true, - }, - }; - - this.tagTree = new TreeView - { - X = 0, - Y = 0, - Width = Dim.Fill(), - Height = Dim.Fill() ! - 2, - CanFocus = true, - MultiSelect = false, - }; - - this.NewTagButton.Y = Pos.Bottom(this.tagTree); - this.NewTagButton.Title = "New tag..."; - this.NewTagButton.Width = Dim.Fill(); - this.NewTagButton.CanFocus = true; - - tagTreeTab.View.Add(this.tagTree, this.NewTagButton); - - Tab searchTab = new() - { - DisplayText = "Search", - View = new View - { - Width = Dim.Fill(), - Height = Dim.Fill(), - CanFocus = true, - }, - }; - - this.queryField = new TextField - { - Width = Dim.Percent(75), - CanFocus = true, - Height = 1, - }; - this.modeSelector = new ComboBox - { - Y = Pos.Bottom(this.queryField) + 2, - Width = Dim.Fill(), - Height = 5, - CanFocus = true, - ReadOnly = true, - }; - this.searchAliasesCheckbox = new CheckBox - { - Y = Pos.Top(this.modeSelector) - 1, - Text = "Search in aliases", - CanFocus = true, - }; - Line separatorLine = new() - { - Y = Pos.Bottom(this.modeSelector), - }; - Button searchButton = new() - { - X = Pos.Right(this.queryField), - Y = 0, - Text = "Search", - Width = Dim.Fill(), - CanFocus = true, - }; - this.modeSelector.SetSource(["Fuzzy search", "Starts with", "Ends with", "Exact match"]); - this.modeSelector.SelectedItem = 0; - this.resultsList = new ListView - { - X = 0, - Y = Pos.Bottom(this.modeSelector) + 1, - Width = Dim.Fill(), - Height = Dim.Fill(), - CanFocus = true, - }; - - searchTab.View.Add(this.queryField, searchButton, this.searchAliasesCheckbox, this.modeSelector, - separatorLine, this.resultsList); - tabView.AddTab(tagTreeTab, true); - tabView.AddTab(searchTab, false); - this.Add(tabView); - this.Enabled = false; - - TagDatabaseService.InitialisationComplete += this.TagDatabaseService_OnInitialised; - TagDatabaseService.TagAdded += this.TagDatabaseService_OnTagAdded; - TagDatabaseService.TagDeleted += this.TagDatabaseService_OnTagDeleted; - TagDatabaseService.TagSaved += this.TagDatabaseService_OnTagSaved; - TagDatabaseService.SearchFinished += this.TagDatabaseService_OnSearchFinished; - searchButton.Accepting += (_, args) => - { - if (string.IsNullOrWhiteSpace(this.queryField.Text)) return; - bool isChecked = this.searchAliasesCheckbox.CheckedState == CheckState.Checked; - TagDatabaseService.SearchTags(this.queryField.Text, - (TagDatabaseSearchMode)this.modeSelector.SelectedItem, isChecked); - args.Handled = true; - }; - this.queryField.KeyDown += (_, args) => - { - if (args.KeyCode != Key.Enter || string.IsNullOrWhiteSpace(this.queryField.Text)) return; - bool isChecked = this.searchAliasesCheckbox.CheckedState == CheckState.Checked; - TagDatabaseService.SearchTags(this.queryField.Text, - (TagDatabaseSearchMode)this.modeSelector.SelectedItem, false); - args.Handled = true; - }; - - this.tagTree.ObjectActivated += (_, args) => - { - if (args.ActivatedObject is TagHierarchyTreeNode node) - Program.MainView.EditPane.LoadTag(node.AssociatedTag); - }; - - this.resultsList.OpenSelectedItem += (_, args) => - { - if (args.Value is not Tag selectedTag || this.resultsListAutoSelect) return; - Program.MainView.EditPane.LoadTag(selectedTag); - }; - } - - private void RefreshTagTree(TagDatabase db) - { - this.tagTree.ClearObjects(); - IEnumerable topLevelNodes = - db.Tags - .Where(tag => tag.IsTopLevel) - .OrderBy(tag => tag.Name, StringComparer.CurrentCultureIgnoreCase) - .Select(tag => new TagHierarchyTreeNode(tag)); - this.tagTree.AddObjects(topLevelNodes); - } - - private void TagDatabaseService_OnInitialised(object? sender, EventArgs e) - { - if (sender is not TagDatabase db) return; - this.RefreshTagTree(db); - } - - private void TagDatabaseService_OnTagAdded(object? sender, Tag tag) - { - foreach (int parent in tag.ParentIds) - this.tagTree.Objects.Cast().Where(node => node.AssociatedTag.Id == parent) - .ToList() - .ForEach(node => this.tagTree.RefreshObject(node)); - } - - private void TagDatabaseService_OnTagDeleted(object? sender, (int Id, string Name) tag) - { - this.tagTree.Objects.Cast() - .Where(node => node.AssociatedTag.Id == tag.Id).ToList() - .ForEach(node => this.tagTree.Remove(node)); - this.resultsList.SetSource(new ObservableCollection(Array.Empty())); - } - - private void TagDatabaseService_OnTagSaved(object? sender, Tag tag) - { - if (sender is not TagDatabase db) return; - List existingNodes = this.tagTree.Objects.Cast() - .Where(node => node.AssociatedTag.Id == tag.Id).ToList(); - - switch (tag.IsTopLevel) - { - case false when existingNodes.All(node => !node.IsChildNode): - { - foreach (TagHierarchyTreeNode node in existingNodes) this.tagTree.Remove(node); - - break; - } - case true: - this.tagTree.AddObject(new TagHierarchyTreeNode(tag)); - // this feels jank but it works. - // i wish there was smth better than refreshing the entire tree but terminal.gui doesn't do sorting - // so this'll do. - // the user may expect to see their changes immediately reflected anyway. - this.RefreshTagTree(db); - break; - } - - foreach (TagHierarchyTreeNode node in - this.tagTree.Objects.Cast() - .Where(node => node.AssociatedTag.Id == tag.Id)) - this.tagTree.RefreshObject(node); - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/EditorWindow/TagEditorPane.cs b/UI/Terminal/Views/EditorWindow/TagEditorPane.cs deleted file mode 100644 index d9a51a3..0000000 --- a/UI/Terminal/Views/EditorWindow/TagEditorPane.cs +++ /dev/null @@ -1,280 +0,0 @@ -using TagHierarchyManager.Models; -using TagHierarchyManager.UI.TerminalUI.Services; -using Terminal.Gui.Input; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -/// -/// A paned containing the tag editor functionality of the application. -/// -public class TagEditorPane : View -{ - private readonly TextField aliasesField = new(); - private readonly TextField bindingsField = new(); - - private readonly TextField nameField = new(); - private readonly TextView notesField = new(); - private readonly TextField parentsField = new(); - private readonly List textFields; - private readonly CheckBox topLevelCheckBox = new(); - private Tag? currentTag; - private Button deleteButton = null!; - - /// - /// Initializes a new instance of the class. - /// - public TagEditorPane() - { - this.InitialiseUI(); - this.textFields = [this.nameField, this.parentsField, this.bindingsField, this.aliasesField]; - } - - internal bool CheckForUnsavedChanges() - { - return this.textFields.Any(f => f.HasHistoryChanges) || this.notesField.HasHistoryChanges; - } - - internal void EnableIfTagLoaded() - { - this.Enabled = this.currentTag is not null; - } - - internal void LoadTag(Tag tag) - { - this.currentTag = tag; - this.UpdateTagDetails(); - this.Enabled = true; - } - - private void AddButton_OnAccepting(object? sender, CommandEventArgs e) - { - if (Program.CurrentDatabase != null) - this.currentTag = new Tag - { - Name = string.Empty, - IsTopLevel = false, - TagBindings = Program.CurrentDatabase.DefaultTagBindings, - }; - else return; - - if (!this.Enabled) this.Enabled = true; - this.deleteButton.Enabled = false; - this.UpdateTagDetails(); - e.Handled = true; - } - - private void ClearTextFieldHistory() - { - this.textFields.ForEach(f => f.ClearHistoryChanges()); - this.notesField.ClearHistoryChanges(); - } - - private async void DeleteButton_OnAccepting(object? sender, EventArgs e) - { - try - { - if (this.currentTag is null) return; - int result = MessageBox.Query("Are you sure?", - $"Are you sure you want to delete the tag '{this.currentTag.Name}'?", 1, "Yes", "No"); - if (result != 0) return; - - await TagDatabaseService.DeleteTag(this.currentTag); - this.currentTag = null; - this.Disable(); - } - catch (Exception ex) - { - MessageBox.ErrorQuery( - "Error", - $"An error has occurred trying to delete the tag.\n\nError message:\n{ex.Message}", - "OK"); - } - } - - private void Disable() - { - this.Enabled = false; - this.textFields.ForEach(f => f.Text = string.Empty); - this.notesField.Text = string.Empty; - this.ClearTextFieldHistory(); - this.topLevelCheckBox.CheckedState = CheckState.UnChecked; - } - - private void InitialiseUI() - { - Label nameLabel = new() - { - X = 0, - Text = "Name: ", - }; - Label parentsLabel = new() - { - Y = Pos.Bottom(nameLabel), - Text = "Parents: ", - CanFocus = false, - }; - Label bindingsLabel = new() - { - Y = Pos.Bottom(parentsLabel), - Text = "Tag bindings: ", - CanFocus = false, - }; - Label aliasesLabel = new() - { - Y = Pos.Bottom(bindingsLabel), - Text = "A.K.As: ", - CanFocus = false, - }; - - this.nameField.X = Pos.Right(nameLabel); - this.nameField.Width = Dim.Fill(); - this.nameField.CanFocus = true; - this.nameField.Height = 1; - - this.parentsField.X = Pos.Right(parentsLabel); - this.parentsField.Y = Pos.Bottom(this.nameField); - this.parentsField.Width = Dim.Fill(); - this.parentsField.CanFocus = true; - this.parentsField.Height = 1; - - this.bindingsField.Width = Dim.Fill(); - this.bindingsField.X = Pos.Right(bindingsLabel); - this.bindingsField.Y = Pos.Bottom(this.parentsField); - this.bindingsField.CanFocus = true; - this.bindingsField.Height = 1; - - this.aliasesField.Width = Dim.Fill(); - this.aliasesField.X = Pos.Right(aliasesLabel); - this.aliasesField.Y = Pos.Bottom(this.bindingsField); - this.aliasesField.CanFocus = true; - this.aliasesField.Height = 1; - - this.Add(nameLabel, parentsLabel, bindingsLabel, aliasesLabel); - this.Add(this.nameField, this.parentsField, this.bindingsField, this.aliasesField); - - FrameView notesView = new() - { - Height = Dim.Fill() ! - 2, - Width = Dim.Fill(), - Y = Pos.Bottom(this.aliasesField), - Title = "Notes", - CanFocus = true, - TabStop = TabBehavior.TabStop, - }; - - this.notesField.Height = Dim.Fill(); - this.notesField.Width = Dim.Fill(); - this.notesField.CanFocus = true; - this.notesField.TabStop = TabBehavior.TabStop; - - notesView.Add(this.notesField); - - const int buttonWidth = 10; - const int buttonSpacing = 2; - - this.topLevelCheckBox.Title = "Is top level"; - this.topLevelCheckBox.CanFocus = true; - this.topLevelCheckBox.X = 0; - this.topLevelCheckBox.Y = Pos.Bottom(notesView); - this.topLevelCheckBox.Height = Dim.Fill(); - this.topLevelCheckBox.Width = Dim.Fill(); - - Button saveButton = new() - { - Title = "Save", - X = Pos.AnchorEnd(buttonWidth), - Y = Pos.Bottom(notesView), - CanFocus = true, - }; - Button cancelButton = new() - { - Title = "Cancel", - X = Pos.Left(saveButton) - buttonSpacing - buttonWidth, - Y = Pos.Bottom(notesView), - CanFocus = true, - }; - this.deleteButton = new Button - { - Title = "Delete", - X = Pos.Left(cancelButton) - buttonSpacing - buttonWidth, - Y = Pos.Bottom(notesView), - CanFocus = true, - }; - - this.Add(notesView); - this.Add(this.topLevelCheckBox, this.deleteButton, cancelButton, saveButton); - this.Initialized += this.OnInitialised; - this.deleteButton.Accepting += this.DeleteButton_OnAccepting; - saveButton.Accepting += this.SaveButton_OnAccepting; - cancelButton.Accepting += (_, _) => - { - if (this.currentTag is not null) - this.UpdateTagDetails(false); - }; - TagDatabaseService.InitialisationComplete += this.TagDatabaseService_OnInitialised; - } - - private void OnInitialised(object? sender, EventArgs e) - { - Program.TopView.Window.TagPane.NewTagButton.Accepting += this.AddButton_OnAccepting; - } - - private async void SaveButton_OnAccepting(object? sender, CommandEventArgs e) - { - try - { - if (this.currentTag is null) return; - - this.currentTag.Name = this.nameField.Text; - this.currentTag.Notes = this.notesField.Text; - this.currentTag.IsTopLevel = this.topLevelCheckBox.CheckedState == CheckState.Checked; - this.currentTag.Parents = this.parentsField.Text.Split( - ";", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); - this.currentTag.TagBindings = this.bindingsField.Text.Replace("::", string.Empty) - .Split(";", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); - this.currentTag.Aliases = this.aliasesField.Text.Split( - ";", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); - this.currentTag.Validate(); - - await TagDatabaseService.WriteTagToDatabase(this.currentTag); - - this.ClearTextFieldHistory(); - this.deleteButton.Enabled = true; - - e.Handled = true; - } - catch (Exception ex) - { - MessageBox.ErrorQuery( - "Error", - $"An error has occurred trying to save the tag.\n\nError message:\n{ex.Message}", - "OK"); - e.Handled = true; - } - } - - private void TagDatabaseService_OnInitialised(object? sender, EventArgs e) - { - if (sender is not TagDatabase) return; - this.Disable(); - this.currentTag = null; - } - - private void UpdateTagDetails(bool clearHistory = true) - { - if (this.currentTag is null) return; - this.nameField.Text = this.currentTag.Name; - this.notesField.Text = this.currentTag.Notes; - this.parentsField.Text = string.Join("; ", this.currentTag.Parents); - this.bindingsField.Text = string.Join("; ", this.currentTag.TagBindings); - this.aliasesField.Text = string.Join("; ", this.currentTag.Aliases); - - this.topLevelCheckBox.CheckedState = this.currentTag.IsTopLevel ? CheckState.Checked : CheckState.UnChecked; - - if (!clearHistory) return; - this.textFields.ForEach(f => f.ClearHistoryChanges()); - this.notesField.ClearHistoryChanges(); - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/EditorWindow/TagHierarchyTreeNode.cs b/UI/Terminal/Views/EditorWindow/TagHierarchyTreeNode.cs deleted file mode 100644 index 26af26b..0000000 --- a/UI/Terminal/Views/EditorWindow/TagHierarchyTreeNode.cs +++ /dev/null @@ -1,27 +0,0 @@ -using TagHierarchyManager.Models; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -internal class TagHierarchyTreeNode(Tag tag) : TreeNode -{ - public Tag AssociatedTag { get; } = tag; - public override IList Children => this.GetChildren().ToList(); - public bool IsChildNode { get; private init; } - public override string Text => this.AssociatedTag.ToString(); - - private IEnumerable GetChildren() - { - if (Program.CurrentDatabase is null) return []; - - IEnumerable children = - Program.CurrentDatabase.Tags - .Where(tag => tag.ParentIds.Contains(this.AssociatedTag.Id)) - .Select(tag => new TagHierarchyTreeNode(tag) - { - IsChildNode = true, - }) - .OrderBy(tagNode => tagNode.AssociatedTag.Name, StringComparer.CurrentCultureIgnoreCase); - return children; - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/StandardDialogs.cs b/UI/Terminal/Views/StandardDialogs.cs deleted file mode 100644 index 1f5cb40..0000000 --- a/UI/Terminal/Views/StandardDialogs.cs +++ /dev/null @@ -1,159 +0,0 @@ -using TagHierarchyManager.Common; -using Terminal.Gui.App; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -/// -/// A class storing standard dialogs. -/// -internal static class StandardDialogs -{ - /// - /// Shows a containing the name, copyright, license and GitHub link. - /// - public static void AboutMessageBox() - { - const string message = """ - Tag Hierarchy Manager - (c) Flaky 2025- - - Licensed under MIT - - https://github.com/FlakyBlueJay/TagHierarchyManager - - """; - MessageBox.Query("About Tag Hierarchy Manager", message, "OK"); - } - - /// - /// Calls and cancels Closing event if the user chooses - /// to cancel. - /// - /// The object, for cancelling if the user chooses to cancel. - public static void CallUnsavedChangesMessageBox(ToplevelClosingEventArgs e) - { - int result = UnsavedChangesMessageBox(); - if (result == 1) e.Cancel = true; - } - - /// - /// Opens an with the purpose of opening a database. - /// - /// The result of a , the path of the chosen .thdb file. - public static string? DatabaseOpenDialog() - { - List allowedTypes = - [ - new AllowedType(FileTypes.TagDatabase.Name, FileTypes.TagDatabase.FileExtension), - ]; - - return SingleFileOpenDialog("Open a tag hierarchy database...", allowedTypes); - } - - /// - /// Runs a for the purpose of creating a new database. - /// - /// - /// A created with the file path and choice of whether the user approved - /// overwriting the file from the resulting . - /// - public static (string filePath, bool userAllowedOverwrite)? ExportSaveDialog() - { - List allowedTypes = []; - FileTypes.AllNonDatabaseFileTypes.ForEach(fileType => - allowedTypes.Add(new AllowedType(fileType.Name, fileType.FileExtension))); - return RunSaveDialog(allowedTypes); - } - - /// - /// Opens an with the purpose of choosing a file to import. - /// - /// The result of a , the path of the chosen file to import. - public static string? ImportOpenDialog() - { - List allowedTypes = ConvertFileTypesToAllowedTypes(); - return SingleFileOpenDialog("Choose file to import...", allowedTypes); - } - - /// - /// Runs a for the purpose of creating a new database. - /// - /// - /// A created with the file path and choice of whether the user approved - /// overwriting the file from the resulting . - /// - public static (string fileName, bool userAllowedOverwrite)? NewDatabaseSaveDialog() - { - List allowedTypes = - [ - new AllowedType(FileTypes.TagDatabase.Name, FileTypes.TagDatabase.FileExtension), - ]; - - return RunSaveDialog(allowedTypes); - } - - private static List ConvertFileTypesToAllowedTypes() - { - return FileTypes.AllNonDatabaseFileTypes.Select(IAllowedType (fileType) => - new AllowedType(fileType.Name, fileType.FileExtension)) - .ToList(); - } - - private static int OverwriteMessageBox(string path) - { - return MessageBox.Query( - "Overwrite existing file?", - $"A file already exists at\n{path}\n\nAre you sure you want to overwrite this file?", - 1, - "Yes", - "No"); - } - - private static (string filePath, bool userAllowedOverwrite)? RunSaveDialog(List allowedTypes, - string title = "Save as...") - { - SaveDialog exportDialog = new() - { - AllowedTypes = allowedTypes, - Title = title, - }; - bool overwrite = false; - exportDialog.FilesSelected += (_, e) => - { - if (!File.Exists(e.Dialog.Path)) return; - int result = OverwriteMessageBox(e.Dialog.Path); - if (result == 0) - overwrite = true; - else - e.Cancel = true; - }; - Application.Run(exportDialog); - return !exportDialog.Canceled ? (exportDialog.FileName, overwrite) : null; - } - - private static string? SingleFileOpenDialog(string title, List allowedTypes) - { - OpenDialog openDialog = new() - { - OpenMode = OpenMode.File, - AllowsMultipleSelection = false, - AllowedTypes = allowedTypes, - Title = title, - Width = Dim.Fill(), - }; - Application.Run(openDialog); - return openDialog.FilePaths.Any() ? openDialog.FilePaths[0] : null; - } - - private static int UnsavedChangesMessageBox() - { - return MessageBox.Query( - "Are you sure?", - "You have unsaved changes. Are you sure you want to leave?", - 1, - "Yes", - "No"); - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/TopMenu.cs b/UI/Terminal/Views/TopMenu.cs deleted file mode 100644 index 2ee3ebe..0000000 --- a/UI/Terminal/Views/TopMenu.cs +++ /dev/null @@ -1,144 +0,0 @@ -using TagHierarchyManager.Models; -using TagHierarchyManager.UI.TerminalUI.Services; -using Terminal.Gui.App; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -/// -/// The top menu bar of the application. -/// -public class TopMenu : MenuBarv2 -{ - private const string NullDatabaseName = ""; - private const string TotalTagsString = "Total tags: {0}"; - - /// - /// The menu item that will initiate the MusicBee tag hierarchy exporting process. - /// - private MenuItemv2 exportToTagHierarchyItem = null!; - - private MenuItemv2 totalTagsItem = null!; - - /// - /// Initializes a new instance of the class. - /// - public TopMenu() - { - this.InitialiseUI(); - } - - private MenuBarItemv2 DbMenu { get; set; } = null!; - - private static void OnNewDatabase() - { - (string fileName, bool userAllowedOverwrite)? newDatabase = StandardDialogs.NewDatabaseSaveDialog(); - if (newDatabase is null) return; - - TagDatabaseService.CreateDatabase(newDatabase.Value); - } - - private void InitialiseUI() - { - this.exportToTagHierarchyItem = new MenuItemv2( - "_Export to MusicBee tag hierarchy template", - string.Empty, - TagDatabaseService.StartExportProcess) - { - Enabled = false, - }; - - this.DbMenu = new MenuBarItemv2( - NullDatabaseName) - { - Enabled = false, - }; - - this.totalTagsItem = new MenuItemv2( - TotalTagsString, - string.Empty, - null); - this.totalTagsItem.Data = 0; - - MenuBarItemv2 fileMenu = new("_File"); - PopoverMenu filePopover = new([ - new MenuItemv2("_New database...", string.Empty, OnNewDatabase), - new MenuItemv2( - "New from MusicBee _tag hierarchy template", - string.Empty, - () => - { - Program.MainView.ImportDialog = new ImportDialog(); - Application.Run(Program.MainView.ImportDialog); - }), - new Line(), - new MenuItemv2("_Open database...", string.Empty, TagDatabaseService.PromptDatabaseLoad), - new Line(), - this.exportToTagHierarchyItem, - new Line(), - new MenuItemv2("_Quit", string.Empty, () => Application.RequestStop()), - ]); - - fileMenu.PopoverMenu = filePopover; - - MenuBarItemv2 helpMenu = new("_Help"); - PopoverMenu helpPopover = new([ - new MenuItemv2("About", string.Empty, StandardDialogs.AboutMessageBox), - ]); - - helpMenu.PopoverMenu = helpPopover; - - PopoverMenu dbPopover = new([ - new MenuItemv2( - "Database settings", - string.Empty, - () => Application.Run(new DbSettingsDialog())), - - new Line(), - this.totalTagsItem, - ]); - - View lineContainer = new() - { - Width = 1, - Height = Dim.Fill(), - }; - lineContainer.Add(new Line { Orientation = Orientation.Vertical, Width = 1, Height = Dim.Fill() }); - this.DbMenu.PopoverMenu = dbPopover; - this.Add(fileMenu, helpMenu, lineContainer, this.DbMenu); - TagDatabaseService.InitialisationComplete += this.TagDatabaseService_OnInitialised; - } - - private void TagDatabase_OnTagAdded(object? sender, Tag? tag) - { - int currentTagCount = (int)this.totalTagsItem.Data!; - currentTagCount++; - this.UpdateTotalTagsCount(currentTagCount); - } - - private void TagDatabase_OnTagDeleted(object? sender, (int, string) tag) - { - int currentTagCount = (int)this.totalTagsItem.Data!; - currentTagCount--; - this.UpdateTotalTagsCount(currentTagCount); - } - - private void TagDatabaseService_OnInitialised(object? sender, EventArgs e) - { - if (sender is not TagDatabase db) return; - this.DbMenu.Title = $"Current database: {db.Name}"; - this.DbMenu.Enabled = true; - this.totalTagsItem.Title = string.Format(TotalTagsString, db.Tags.Count); - this.totalTagsItem.Data = db.Tags.Count; - TagDatabaseService.TagAdded += this.TagDatabase_OnTagAdded; - TagDatabaseService.TagDeleted += this.TagDatabase_OnTagDeleted; - this.exportToTagHierarchyItem.Enabled = true; - } - - private void UpdateTotalTagsCount(int newCount) - { - this.totalTagsItem.Data = newCount; - this.totalTagsItem.Title = string.Format(TotalTagsString, newCount); - } -} \ No newline at end of file diff --git a/UI/Terminal/Views/TopView.cs b/UI/Terminal/Views/TopView.cs deleted file mode 100644 index 3fbe659..0000000 --- a/UI/Terminal/Views/TopView.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace TagHierarchyManager.UI.TerminalUI.Views; - -/// -/// A top-level view that encapsulates the main view of the application. -/// -public class TopView : Toplevel -{ - /// - /// Gets the associated with the main view. - /// - public EditorWindow Window = null!; - - private TopMenu topMenu = null!; - - /// - /// Initializes a new instance of the class. - /// - public TopView() - { - this.InitialiseUI(); - } - - private void InitialiseUI() - { - // remember to look at the QuitKey property. - this.topMenu = new TopMenu - { - X = 0, - Y = 0, - Width = Dim.Fill(), - Height = 1, - }; - - this.Window = new EditorWindow(this.topMenu); - - this.Add(this.topMenu, this.Window); - this.Closing += this.OnToplevelClosing; - } - - private void OnToplevelClosing(object? sender, ToplevelClosingEventArgs e) - { - if (this.Window.EditPane.CheckForUnsavedChanges()) StandardDialogs.CallUnsavedChangesMessageBox(e); - } -} \ No newline at end of file -- 2.51.2