From a4e77c1beb6587aa5bee7bc560f91e4baddcec4e Mon Sep 17 00:00:00 2001 From: Maxim Date: Fri, 5 Jul 2024 15:44:01 +0100 Subject: [PATCH] Add ability to load settings to the GUI fro the commandline, fixes #84. Also added UI to deal with loading channels data. --- LibSidWiz/LibSidWiz.csproj | 2 +- SidWiz/HighDpiHelper.cs | 8 +- SidWiz/SidWizPlusGUI.cs | 211 ++++++++++++++++++++++++++++++++++++- 3 files changed, 212 insertions(+), 9 deletions(-) diff --git a/LibSidWiz/LibSidWiz.csproj b/LibSidWiz/LibSidWiz.csproj index 32914d4..3c39f92 100644 --- a/LibSidWiz/LibSidWiz.csproj +++ b/LibSidWiz/LibSidWiz.csproj @@ -15,10 +15,10 @@ + - \ No newline at end of file diff --git a/SidWiz/HighDpiHelper.cs b/SidWiz/HighDpiHelper.cs index 3ff3914..f65a9fe 100644 --- a/SidWiz/HighDpiHelper.cs +++ b/SidWiz/HighDpiHelper.cs @@ -26,13 +26,13 @@ private static void AdjustControlImagesDpiScale(IEnumerable controls, float dpiS { switch (control) { - case ButtonBase button when button.Image != null: + case ButtonBase { Image: not null } button: button.Image = ScaleImage(button.Image, dpiScale); break; case SplitContainer splitContainer: splitContainer.SplitterDistance = (int)(splitContainer.SplitterDistance * dpiScale); break; - case TabControl tabControl when tabControl.ImageList != null: + case TabControl { ImageList: not null } tabControl: { var imageList = new ImageList { @@ -40,7 +40,7 @@ private static void AdjustControlImagesDpiScale(IEnumerable controls, float dpiS ColorDepth = ColorDepth.Depth32Bit }; - for (int i = 0 ; i < tabControl.ImageList.Images.Count; ++i) + for (var i = 0 ; i < tabControl.ImageList.Images.Count; ++i) { imageList.Images.Add( tabControl.ImageList.Images.Keys[i], @@ -98,7 +98,7 @@ private static Image ScaleImage(Image image, float dpiScale) using (var g = Graphics.FromImage(newBitmap)) { g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.InterpolationMode = InterpolationMode.NearestNeighbor; g.DrawImage(image, new Rectangle(new Point(), newSize)); } diff --git a/SidWiz/SidWizPlusGUI.cs b/SidWiz/SidWizPlusGUI.cs index 495845d..13c8c3c 100644 --- a/SidWiz/SidWizPlusGUI.cs +++ b/SidWiz/SidWizPlusGUI.cs @@ -16,6 +16,7 @@ using LibSidWiz; using LibSidWiz.Outputs; using LibSidWiz.Triggers; +using Microsoft.WindowsAPICodePack.Dialogs; using Newtonsoft.Json; using SkiaSharp; @@ -642,7 +643,7 @@ private Channel GetClickedChannel(int clickX, int clickY) var previewAspectRatio = (double) Preview.Width / Preview.Height; if (previewAspectRatio > imageAspectRatio) { - // Preview is wider, we have pillarboxing + // Preview is wider, we have pillar-boxing // So the y one is correct, x needs to be modified: // +--+-----+--+ // | | | | @@ -1033,6 +1034,14 @@ private void Initialize(object sender, EventArgs e) toolStrip1.Items.Insert(indexToInsertAt, newItem); } } + + // Load settings if passed on the commandline + foreach (var arg in Environment.GetCommandLineArgs() + .Skip(1) + .Where(x => x.ToLowerInvariant().EndsWith(".sidwizplus.json"))) + { + LoadSettingsFromFile(arg); + } } private void RemoveSilentChannels(object sender, EventArgs e) @@ -1097,11 +1106,162 @@ private void LoadProject(object sender, EventArgs e) return; } + LoadSettingsFromFile(ofd.FileName); + } + + private void LoadSettingsFromFile(string path) + { lock (_settings) { - JsonConvert.PopulateObject(File.ReadAllText(ofd.FileName), _settings); + var newSettings = JsonConvert.DeserializeObject(File.ReadAllText(path)); - foreach (var channel in _settings.Channels) + // We inspect to decide what to do... + var clearExistingChannels = false; + var discardNewChannels = false; + var removeMissingFiles = false; + var removeDuplicates = false; + if (newSettings.Channels.Any()) + { + // No new channels means nothing to check... + + // If we already have channels, ask if the user wants to add or replace. + if (_settings.Channels.Any()) + { + using var dialog = new TaskDialog(); + dialog.InstructionText = "Loading new channels"; + dialog.Text = + $"The settings being loaded contain {newSettings.Channels.Count} channels. What do you want to do?"; + dialog.Cancelable = true; + dialog.Icon = TaskDialogStandardIcon.Warning; + dialog.StandardButtons = TaskDialogStandardButtons.Cancel; + dialog.DetailsExpandedText = $""" + Existing channels: + {string.Join("\n", PrintFilenames(_settings.Channels, null))} + New channels: + {string.Join("\n", PrintFilenames(newSettings.Channels, _settings.Channels))} + """; + AddDialogButton(dialog, "Add", "Add to the existing channels", () => {}); + AddDialogButton(dialog, "Replace", "Replace the existing channels", + () => { clearExistingChannels = true; }); + AddDialogButton(dialog, "Discard", "Discard the new channels", + () => { discardNewChannels = true; }); + + if (dialog.Show() == TaskDialogResult.Cancel) + { + return; + } + } + } + + if (!discardNewChannels && !clearExistingChannels) + { + // If the new channels refer to files that don't exist, the user may not want them. + var missingFiles = newSettings.Channels + .Select(x => x.Filename).Where(x => !File.Exists(x)) + .Distinct() + .ToList(); + + if (missingFiles.Any()) + { + using var dialog = new TaskDialog(); + dialog.InstructionText = "Missing audio files"; + dialog.Text = + $"The settings being loaded contain {newSettings.Channels.Count} channels, but {missingFiles.Count} refer to files that don't exist. Do you want to add them anyway?"; + dialog.Cancelable = true; + dialog.Icon = TaskDialogStandardIcon.Warning; + dialog.StandardButtons = TaskDialogStandardButtons.Yes | TaskDialogStandardButtons.No | + TaskDialogStandardButtons.Cancel; + dialog.DetailsExpandedText = string.Join("\n", PrintFilenames(newSettings.Channels, null)); + + switch (dialog.Show()) + { + case TaskDialogResult.Cancel: + return; + case TaskDialogResult.Yes: + removeMissingFiles = true; + // Also clear the channels from the settings for the next check + newSettings.Channels.RemoveAll(x => missingFiles.Contains(x.Filename)); + break; + } + } + + // Now also check for duplicates + if (_settings.Channels.Any()) + { + var duplicates = newSettings.Channels + .Where(x => _settings.Channels.Any(y => + Path.GetFullPath(y.Filename) == Path.GetFullPath(x.Filename))) + .ToList(); + if (duplicates.Any()) + { + using var dialog = new TaskDialog(); + dialog.InstructionText = "Duplicate audio files"; + dialog.Text = + $"The settings being loaded contain {duplicates.Count} channels where the file is already loaded. Do you want to add them anyway?"; + dialog.Cancelable = true; + dialog.Icon = TaskDialogStandardIcon.Warning; + dialog.StandardButtons = TaskDialogStandardButtons.Yes | TaskDialogStandardButtons.No | + TaskDialogStandardButtons.Cancel; + dialog.DetailsExpandedText = "New channels:\n" + string.Join("\n", + PrintFilenames(newSettings.Channels, _settings.Channels)); + + switch (dialog.Show()) + { + case TaskDialogResult.Cancel: + return; + case TaskDialogResult.No: + removeDuplicates = true; + break; + } + } + } + } + + // Deserialize again on top, so partial JSON loads will work + if (clearExistingChannels) + { + _settings.Channels.Clear(); + } + var countOfOldChannels = _settings.Channels.Count; + JsonConvert.PopulateObject(File.ReadAllText(path), _settings); + if (discardNewChannels) + { + var oldChannels = _settings.Channels.Take(countOfOldChannels).ToList(); + _settings.Channels.Clear(); + _settings.Channels.AddRange(oldChannels); + } + + // Then remove channels as specified + if (removeMissingFiles) + { + var channelsToRemove = _settings.Channels + .Skip(countOfOldChannels) + .Where(x => !File.Exists(x.Filename)) + .ToList(); + foreach (var channel in channelsToRemove) + { + _settings.Channels.Remove(channel); + } + } + + if (removeDuplicates) + { + var oldChannels = _settings.Channels + .Take(countOfOldChannels) + .Select(x => Path.GetFullPath(x.Filename)) + .ToList(); + var channelsToRemove = _settings.Channels + .Skip(countOfOldChannels) + .Where(x => oldChannels.Contains(Path.GetFullPath(x.Filename))) + .ToList(); + foreach (var channel in channelsToRemove) + { + _settings.Channels.Remove(channel); + } + } + + // Finally, trigger a load on the new channels (and attach our event handler) + foreach (var channel in _settings.Channels.Skip(countOfOldChannels)) { channel.Changed += ChannelOnChanged; channel.LoadDataAsync(); @@ -1111,6 +1271,49 @@ private void LoadProject(object sender, EventArgs e) } } + private static IEnumerable PrintFilenames(List channels, List existingChannels) + { + foreach (var channel in channels) + { + string icon; + if (!File.Exists(channel.Filename)) + { + icon = "❌"; + } + else if ( + existingChannels != null && + !existingChannels.Contains(channel) && + existingChannels.Any(x => x.Filename == channel.Filename)) + { + icon = "💥"; + } + else + { + icon = "🔉"; + } + + yield return $@"{icon} {channel.Filename}"; + } + yield return "❌ = file not found"; + yield return "💥 = file is already present"; + } + + private static void AddDialogButton(TaskDialog dialog, string text, string instruction, Action action) + { + var button = new TaskDialogCommandLink + { + Text = text, + Instruction = instruction, + }; + button.Click += (_, _) => + { + action(); + // ReSharper disable once AccessToDisposedClosure + dialog.Close(TaskDialogResult.Ok); + }; + dialog.Controls.Add(button); + } + private void CopyChannelSettings(object sender, EventArgs e) { if (PropertyGrid.SelectedObject is not Channel source) @@ -1240,7 +1443,7 @@ private void VideoCodec_DropDown(object sender, EventArgs e) try { // We run FFMPEG to find what codecs it supports - using var process = new ProcessWrapper(_programSettings.FfmpegPath, "-encoders", false, true); + using var process = new ProcessWrapper(_programSettings.FfmpegPath, "-encoders"); process.WaitForExit(); foreach (var grouping in process .Lines()