From b21b512265bc6d065e0f9be80b5721e37cdf2a07 Mon Sep 17 00:00:00 2001 From: Brendan MacLean Date: Sun, 22 Dec 2024 03:33:47 -0800 Subject: [PATCH] =?UTF-8?q?Skyline:=20Create=20ImageComparer=20tool=20as?= =?UTF-8?q?=20an=20easier=20way=20to=20compare=20modifi=E2=80=A6=20(#3296)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Skyline: Create ImageComparer tool as an easier way to compare modified tutorial screenshots in Git --- .../DevTools/ImageComparer/App.config | 33 + .../DevTools/ImageComparer/FileSaver.cs | 145 +++ .../DevTools/ImageComparer/GitFileHelper.cs | 174 +++ .../ImageComparer/ImageComparer.csproj | 125 +++ .../DevTools/ImageComparer/ImageComparer.sln | 27 + .../ImageComparer.sln.DotSettings | 6 + .../ImageComparerWindow.Designer.cs | 366 +++++++ .../ImageComparer/ImageComparerWindow.cs | 988 ++++++++++++++++++ .../ImageComparer/ImageComparerWindow.resx | 126 +++ .../DevTools/ImageComparer/Program.cs | 22 + .../ImageComparer/Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 193 ++++ .../ImageComparer/Properties/Resources.resx | 160 +++ .../Properties/Settings.Designer.cs | 98 ++ .../Properties/Settings.settings | 24 + .../ImageComparer/Resources/DiskFailure.png | Bin 0 -> 5302 bytes .../ImageComparer/Resources/NoPeak.bmp | Bin 0 -> 822 bytes .../DevTools/ImageComparer/Resources/Peak.bmp | Bin 0 -> 1334 bytes .../Resources/autosizeoptimize.png | Bin 0 -> 498 bytes .../ImageComparer/Resources/backwards.png | Bin 0 -> 671 bytes .../ImageComparer/Resources/camera.ico | Bin 0 -> 1206 bytes .../ImageComparer/Resources/forwards.png | Bin 0 -> 669 bytes .../ImageComparer/Resources/gitsource.png | Bin 0 -> 782 bytes .../ImageComparer/Resources/openfolder.png | Bin 0 -> 568 bytes .../DevTools/ImageComparer/Resources/save.png | Bin 0 -> 431 bytes .../DevTools/ImageComparer/Resources/undo.png | Bin 0 -> 513 bytes .../Resources/webdestination.png | Bin 0 -> 611 bytes .../ImageComparer/Resources/websource.png | Bin 0 -> 708 bytes .../KeepResxW/KeepResxW/KeepResxForm.cs | 1 + pwiz_tools/Skyline/Test/CodeInspectionTest.cs | 1 + pwiz_tools/Skyline/TestUtil/GitFileHelper.cs | 17 +- 31 files changed, 2540 insertions(+), 2 deletions(-) create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/App.config create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/FileSaver.cs create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/GitFileHelper.cs create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.csproj create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.sln create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.sln.DotSettings create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.Designer.cs create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.cs create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.resx create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Program.cs create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/AssemblyInfo.cs create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Resources.Designer.cs create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Resources.resx create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Settings.Designer.cs create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Settings.settings create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/DiskFailure.png create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/NoPeak.bmp create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/Peak.bmp create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/autosizeoptimize.png create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/backwards.png create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/camera.ico create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/forwards.png create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/gitsource.png create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/openfolder.png create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/save.png create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/undo.png create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/webdestination.png create mode 100644 pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/websource.png diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/App.config b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/App.config new file mode 100644 index 0000000000..0c408c5559 --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/App.config @@ -0,0 +1,33 @@ + + + + +
+ + + + + + + + + 0, 0 + + + 0, 0 + + + False + + + False + + + 0 + + + + + + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/FileSaver.cs b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/FileSaver.cs new file mode 100644 index 0000000000..fdc8cae6bb --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/FileSaver.cs @@ -0,0 +1,145 @@ +/* + * Original author: Nicholas Shulman , + * MacCoss Lab, Department of Genome Sciences, UW + * + * Copyright 2024 University of Washington - Seattle, WA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace ImageComparer +{ + public sealed class FileSaver : IDisposable + { + public const string TEMP_PREFIX = "~SK"; + /// + /// Construct an instance of to manage saving to a temporary + /// file, and then renaming to the final destination. + /// + /// File path to the final destination + /// IOException + public FileSaver(string fileName) + { + RealName = Path.GetFullPath(fileName); + + string dirName = Path.GetDirectoryName(RealName)!; + string tempName = GetTempFileName(dirName, TEMP_PREFIX); + // If the directory name is returned, then starting path was bogus. + if (!Equals(dirName, tempName)) + SafeName = tempName; + } + + public string SafeName { get; private set; } + + public string RealName { get; private set; } + + public bool Commit() + { + // This is where the file that got written is renamed to the desired file. + // Dispose() will do any necessary temporary file clean-up. + + if (string.IsNullOrEmpty(SafeName)) + return false; + Commit(SafeName, RealName); + Dispose(); + + return true; + } + + private static void Commit(string pathTemp, string pathDestination) + { + try + { + string backupFile = GetBackupFileName(pathDestination); + File.Delete(backupFile); + + // First try replacing the destination file, if it exists + if (File.Exists(pathDestination)) + { + File.Replace(pathTemp, pathDestination, backupFile, true); + File.Delete(backupFile); + return; + } + } + catch (FileNotFoundException) + { + } + + // Or just move, if it does not. + File.Move(pathTemp, pathDestination); + } + + private static string GetBackupFileName(string pathDestination) + { + string backupFile = FileSaver.TEMP_PREFIX + Path.GetFileName(pathDestination) + @".bak"; + string dirName = Path.GetDirectoryName(pathDestination)!; + if (!string.IsNullOrEmpty(dirName)) + backupFile = Path.Combine(dirName, backupFile); + // CONSIDER: Handle failure by trying a different name, or use a true temporary name? + File.Delete(backupFile); + return backupFile; + } + + + public void Dispose() + { + // Get rid of the temporary file, if it still exists. + if (!string.IsNullOrEmpty(SafeName)) + { + try + { + if (File.Exists(SafeName)) + File.Delete(SafeName); + } + catch (Exception e) + { + Trace.TraceWarning(@"Exception in FileSaver.Dispose: {0}", e); + } + // Make sure any further calls to Dispose() do nothing. + SafeName = null; + } + } + public string GetTempFileName(string basePath, string prefix) + { + return GetTempFileName(basePath, prefix, 0); + } + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + static extern uint GetTempFileName(string lpPathName, string lpPrefixString, + uint uUnique, [Out] StringBuilder lpTempFileName); + + private static string GetTempFileName(string basePath, string prefix, uint unique) + { + // 260 is MAX_PATH in Win32 windows.h header + // 'sb' needs >0 size else GetTempFileName throws IndexOutOfRangeException. 260 is the most you'd want. + StringBuilder sb = new StringBuilder(260); + + Directory.CreateDirectory(basePath); + uint result = GetTempFileName(basePath, prefix, unique, sb); + if (result == 0) + { + var lastWin32Error = Marshal.GetLastWin32Error(); + throw new IOException(string.Format("Error {0} GetTempFileName({1}, {2}, {3})", lastWin32Error, + basePath, prefix, unique)); + } + + return sb.ToString(); + } + } +} diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/GitFileHelper.cs b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/GitFileHelper.cs new file mode 100644 index 0000000000..b456eed5d6 --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/GitFileHelper.cs @@ -0,0 +1,174 @@ +/* + * Original author: Brendan MacLean , + * MacCoss Lab, Department of Genome Sciences, UW + * + * Copyright 2024 University of Washington - Seattle, WA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace ImageComparer +{ + internal static class GitFileHelper + { + /// + /// Retrieves the committed binary content of a file in a Git repository. + /// + /// The fully qualified path of the file. + /// The committed binary content of the file. + public static byte[] GetGitFileBinaryContent(string fullPath) + { + return RunGitCommand(GetPathInfo(fullPath), "show HEAD:{RelativePath}", process => + { + using var memoryStream = new MemoryStream(); + process.StandardOutput.BaseStream.CopyTo(memoryStream); + return memoryStream.ToArray(); + }); + } + + /// + /// Gets a list of changed file paths under a specific directory. + /// + /// The fully qualified directory path. + /// An enumerable of file paths that have been modified, added, or deleted. + public static IEnumerable GetChangedFilePaths(string directoryPath) + { + var output = RunGitCommand(GetPathInfo(directoryPath), "status --porcelain \"{RelativePath}\""); + + using var reader = new StringReader(output); + while (reader.ReadLine() is { } line) + { + // 'git status --porcelain' format: XY path/to/file + var filePath = line.Substring(3).Replace('/', Path.DirectorySeparatorChar); + yield return Path.Combine(GetPathInfo(directoryPath).Root, filePath); + } + } + + /// + /// Reverts a file to its state in the HEAD commit. + /// + /// The fully qualified path of the file to revert. + public static void RevertFileToHead(string fullPath) + { + RunGitCommand(GetPathInfo(fullPath), "checkout HEAD -- \"{RelativePath}\""); + } + + /// + /// Executes a Git command in the specified repository and returns the standard output as a string. + /// + /// The PathInfo object for the target path. + /// The Git command template with placeholders (e.g., {RelativePath}). + /// A function that takes a running process and returns the necessary output + /// Output of type T from the process. + private static T RunGitCommand(PathInfo pathInfo, string commandTemplate, Func processOutput) + { + var command = commandTemplate.Replace("{RelativePath}", pathInfo.RelativePath); + var processInfo = new ProcessStartInfo("git", command) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = pathInfo.Root + }; + + using var process = new Process(); + process.StartInfo = processInfo; + process.Start(); + + var output = processOutput(process); + var error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new Exception($"Git error: {error}"); + } + + return output; + } + + private static string RunGitCommand(PathInfo pathInfo, string commandTemplate) + { + return RunGitCommand(pathInfo, commandTemplate, process => process.StandardOutput.ReadToEnd()); + } + + /// + /// Retrieves the root directory of the Git repository containing the given directory. + /// + /// The directory or file path to start searching from. + /// The root directory of the Git repository, or null if not found. + private static string GetGitRepositoryRoot(string startPath) + { + var currentDirectory = File.Exists(startPath) + ? Path.GetDirectoryName(startPath) + : startPath; + + while (!string.IsNullOrEmpty(currentDirectory)) + { + if (Directory.Exists(Path.Combine(currentDirectory, ".git"))) + { + return currentDirectory; + } + + currentDirectory = Directory.GetParent(currentDirectory)?.FullName; + } + + return null; // No Git repository found + } + + /// + /// Retrieves path information for a given file or directory, including the repository root and relative path. + /// + /// The fully qualified file or directory path. + /// A PathInfo object containing the repository root and the relative path. + private static PathInfo GetPathInfo(string fullPath) + { + if (!File.Exists(fullPath) && !Directory.Exists(fullPath)) + { + throw new FileNotFoundException($"The path '{fullPath}' does not exist."); + } + + var repoRoot = GetGitRepositoryRoot(fullPath); + if (repoRoot == null) + { + throw new InvalidOperationException($"The path '{fullPath}' is not part of a Git repository."); + } + + var relativePath = GetRelativePath(repoRoot, fullPath).Replace(Path.DirectorySeparatorChar, '/'); + return new PathInfo { Root = repoRoot, RelativePath = relativePath }; + } + + private static string GetRelativePath(string basePath, string fullPath) + { + if (!basePath.EndsWith(Path.DirectorySeparatorChar.ToString())) + basePath += Path.DirectorySeparatorChar; + if (fullPath.ToLowerInvariant().StartsWith(basePath.ToLowerInvariant())) + { + return fullPath.Substring(basePath.Length); + } + return fullPath; + } + + private struct PathInfo + { + public string Root { get; set; } + public string RelativePath { get; set; } + } + } + +} \ No newline at end of file diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.csproj b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.csproj new file mode 100644 index 0000000000..4abf994bad --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.csproj @@ -0,0 +1,125 @@ + + + + + Debug + AnyCPU + {1B2C0F7C-DE86-42A9-B265-69026ECF2376} + WinExe + ImageComparer + ImageComparer + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + Form + + + ImageComparerWindow.cs + + + + + ImageComparerWindow.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.sln b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.sln new file mode 100644 index 0000000000..37b9961a83 --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34616.47 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageComparer", "ImageComparer.csproj", "{1B2C0F7C-DE86-42A9-B265-69026ECF2376}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8165B446-585C-4CBA-87A9-FD4E558F63B5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1B2C0F7C-DE86-42A9-B265-69026ECF2376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B2C0F7C-DE86-42A9-B265-69026ECF2376}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B2C0F7C-DE86-42A9-B265-69026ECF2376}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B2C0F7C-DE86-42A9-B265-69026ECF2376}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AEBDD11B-5441-4E6B-9696-3B2842787C1A} + EndGlobalSection +EndGlobal diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.sln.DotSettings b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.sln.DotSettings new file mode 100644 index 0000000000..ffda5b37f9 --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparer.sln.DotSettings @@ -0,0 +1,6 @@ + + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + True \ No newline at end of file diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.Designer.cs b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.Designer.cs new file mode 100644 index 0000000000..5bff2a71e1 --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.Designer.cs @@ -0,0 +1,366 @@ +namespace ImageComparer +{ + partial class ImageComparerWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.oldScreenshotPictureBox = new System.Windows.Forms.PictureBox(); + this.newScreenshotPictureBox = new System.Windows.Forms.PictureBox(); + this.previewSplitContainer = new System.Windows.Forms.SplitContainer(); + this.oldScreenshotLabelPanel = new System.Windows.Forms.Panel(); + this.pictureMatching = new System.Windows.Forms.PictureBox(); + this.labelOldSize = new System.Windows.Forms.Label(); + this.buttonImageSource = new System.Windows.Forms.Button(); + this.oldScreenshotLabel = new System.Windows.Forms.Label(); + this.newScreenshotLabelPanel = new System.Windows.Forms.Panel(); + this.labelNewSize = new System.Windows.Forms.Label(); + this.newScreenshotLabel = new System.Windows.Forms.Label(); + this.helpTip = new System.Windows.Forms.ToolTip(this.components); + this.toolStrip = new System.Windows.Forms.ToolStrip(); + this.toolStripOpenFolder = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripPrevious = new System.Windows.Forms.ToolStripButton(); + this.toolStripNext = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripRevert = new System.Windows.Forms.ToolStripButton(); + this.toolStripFileList = new System.Windows.Forms.ToolStripComboBox(); + this.toolStripAutoSize = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripGotoWeb = new System.Windows.Forms.ToolStripButton(); + ((System.ComponentModel.ISupportInitialize)(this.oldScreenshotPictureBox)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.newScreenshotPictureBox)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.previewSplitContainer)).BeginInit(); + this.previewSplitContainer.Panel1.SuspendLayout(); + this.previewSplitContainer.Panel2.SuspendLayout(); + this.previewSplitContainer.SuspendLayout(); + this.oldScreenshotLabelPanel.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureMatching)).BeginInit(); + this.newScreenshotLabelPanel.SuspendLayout(); + this.toolStrip.SuspendLayout(); + this.SuspendLayout(); + // + // oldScreenshotPictureBox + // + this.oldScreenshotPictureBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.oldScreenshotPictureBox.Location = new System.Drawing.Point(0, 30); + this.oldScreenshotPictureBox.Margin = new System.Windows.Forms.Padding(2); + this.oldScreenshotPictureBox.Name = "oldScreenshotPictureBox"; + this.oldScreenshotPictureBox.Size = new System.Drawing.Size(488, 406); + this.oldScreenshotPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.oldScreenshotPictureBox.TabIndex = 0; + this.oldScreenshotPictureBox.TabStop = false; + // + // newScreenshotPictureBox + // + this.newScreenshotPictureBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.newScreenshotPictureBox.Location = new System.Drawing.Point(0, 30); + this.newScreenshotPictureBox.Margin = new System.Windows.Forms.Padding(2); + this.newScreenshotPictureBox.Name = "newScreenshotPictureBox"; + this.newScreenshotPictureBox.Size = new System.Drawing.Size(487, 406); + this.newScreenshotPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.newScreenshotPictureBox.TabIndex = 1; + this.newScreenshotPictureBox.TabStop = false; + // + // previewSplitContainer + // + this.previewSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.previewSplitContainer.IsSplitterFixed = true; + this.previewSplitContainer.Location = new System.Drawing.Point(0, 25); + this.previewSplitContainer.Margin = new System.Windows.Forms.Padding(2); + this.previewSplitContainer.Name = "previewSplitContainer"; + // + // previewSplitContainer.Panel1 + // + this.previewSplitContainer.Panel1.Controls.Add(this.oldScreenshotPictureBox); + this.previewSplitContainer.Panel1.Controls.Add(this.oldScreenshotLabelPanel); + // + // previewSplitContainer.Panel2 + // + this.previewSplitContainer.Panel2.Controls.Add(this.newScreenshotPictureBox); + this.previewSplitContainer.Panel2.Controls.Add(this.newScreenshotLabelPanel); + this.previewSplitContainer.Size = new System.Drawing.Size(976, 436); + this.previewSplitContainer.SplitterDistance = 488; + this.previewSplitContainer.SplitterWidth = 1; + this.previewSplitContainer.TabIndex = 1; + // + // oldScreenshotLabelPanel + // + this.oldScreenshotLabelPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.oldScreenshotLabelPanel.BackColor = System.Drawing.SystemColors.ActiveBorder; + this.oldScreenshotLabelPanel.Controls.Add(this.pictureMatching); + this.oldScreenshotLabelPanel.Controls.Add(this.labelOldSize); + this.oldScreenshotLabelPanel.Controls.Add(this.buttonImageSource); + this.oldScreenshotLabelPanel.Controls.Add(this.oldScreenshotLabel); + this.oldScreenshotLabelPanel.Dock = System.Windows.Forms.DockStyle.Top; + this.oldScreenshotLabelPanel.Location = new System.Drawing.Point(0, 0); + this.oldScreenshotLabelPanel.Margin = new System.Windows.Forms.Padding(2); + this.oldScreenshotLabelPanel.Name = "oldScreenshotLabelPanel"; + this.oldScreenshotLabelPanel.Size = new System.Drawing.Size(488, 30); + this.oldScreenshotLabelPanel.TabIndex = 1; + // + // pictureMatching + // + this.pictureMatching.Location = new System.Drawing.Point(5, 7); + this.pictureMatching.Name = "pictureMatching"; + this.pictureMatching.Size = new System.Drawing.Size(16, 16); + this.pictureMatching.TabIndex = 3; + this.pictureMatching.TabStop = false; + // + // labelOldSize + // + this.labelOldSize.AutoSize = true; + this.labelOldSize.Location = new System.Drawing.Point(26, 9); + this.labelOldSize.Name = "labelOldSize"; + this.labelOldSize.Size = new System.Drawing.Size(42, 13); + this.labelOldSize.TabIndex = 2; + this.labelOldSize.Text = "old size"; + // + // buttonImageSource + // + this.buttonImageSource.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.buttonImageSource.Location = new System.Drawing.Point(455, 4); + this.buttonImageSource.Margin = new System.Windows.Forms.Padding(1); + this.buttonImageSource.Name = "buttonImageSource"; + this.buttonImageSource.Size = new System.Drawing.Size(22, 22); + this.buttonImageSource.TabIndex = 1; + this.buttonImageSource.TabStop = false; + this.helpTip.SetToolTip(this.buttonImageSource, "Switch Source (Ctrl-Tab)\r\nCurrent: Git"); + this.buttonImageSource.UseVisualStyleBackColor = true; + this.buttonImageSource.Click += new System.EventHandler(this.buttonImageSource_Click); + // + // oldScreenshotLabel + // + this.oldScreenshotLabel.BackColor = System.Drawing.Color.Transparent; + this.oldScreenshotLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.oldScreenshotLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 14F); + this.oldScreenshotLabel.ForeColor = System.Drawing.Color.Black; + this.oldScreenshotLabel.Location = new System.Drawing.Point(0, 0); + this.oldScreenshotLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.oldScreenshotLabel.Name = "oldScreenshotLabel"; + this.oldScreenshotLabel.Size = new System.Drawing.Size(488, 30); + this.oldScreenshotLabel.TabIndex = 0; + this.oldScreenshotLabel.Text = "Old Screenshot"; + this.oldScreenshotLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // newScreenshotLabelPanel + // + this.newScreenshotLabelPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.newScreenshotLabelPanel.BackColor = System.Drawing.SystemColors.ActiveCaption; + this.newScreenshotLabelPanel.Controls.Add(this.labelNewSize); + this.newScreenshotLabelPanel.Controls.Add(this.newScreenshotLabel); + this.newScreenshotLabelPanel.Dock = System.Windows.Forms.DockStyle.Top; + this.newScreenshotLabelPanel.Location = new System.Drawing.Point(0, 0); + this.newScreenshotLabelPanel.Margin = new System.Windows.Forms.Padding(2); + this.newScreenshotLabelPanel.Name = "newScreenshotLabelPanel"; + this.newScreenshotLabelPanel.Size = new System.Drawing.Size(487, 30); + this.newScreenshotLabelPanel.TabIndex = 3; + // + // labelNewSize + // + this.labelNewSize.AutoSize = true; + this.labelNewSize.Location = new System.Drawing.Point(5, 9); + this.labelNewSize.Name = "labelNewSize"; + this.labelNewSize.Size = new System.Drawing.Size(48, 13); + this.labelNewSize.TabIndex = 2; + this.labelNewSize.Text = "new size"; + // + // newScreenshotLabel + // + this.newScreenshotLabel.BackColor = System.Drawing.Color.Transparent; + this.newScreenshotLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.newScreenshotLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 14F); + this.newScreenshotLabel.ForeColor = System.Drawing.Color.Black; + this.newScreenshotLabel.Location = new System.Drawing.Point(0, 0); + this.newScreenshotLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.newScreenshotLabel.Name = "newScreenshotLabel"; + this.newScreenshotLabel.Size = new System.Drawing.Size(487, 30); + this.newScreenshotLabel.TabIndex = 0; + this.newScreenshotLabel.Text = "New Screenshot"; + this.newScreenshotLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // toolStrip + // + this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripOpenFolder, + this.toolStripSeparator1, + this.toolStripPrevious, + this.toolStripNext, + this.toolStripSeparator2, + this.toolStripRevert, + this.toolStripFileList, + this.toolStripAutoSize, + this.toolStripSeparator3, + this.toolStripGotoWeb}); + this.toolStrip.Location = new System.Drawing.Point(0, 0); + this.toolStrip.Name = "toolStrip"; + this.toolStrip.Size = new System.Drawing.Size(976, 25); + this.toolStrip.TabIndex = 2; + // + // toolStripOpenFolder + // + this.toolStripOpenFolder.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripOpenFolder.Image = global::ImageComparer.Properties.Resources.openfolder; + this.toolStripOpenFolder.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripOpenFolder.Name = "toolStripOpenFolder"; + this.toolStripOpenFolder.Size = new System.Drawing.Size(23, 22); + this.toolStripOpenFolder.ToolTipText = "Open Folder (Ctrl-O)"; + this.toolStripOpenFolder.Click += new System.EventHandler(this.toolStripOpenFolder_Click); + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25); + // + // toolStripPrevious + // + this.toolStripPrevious.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripPrevious.Image = global::ImageComparer.Properties.Resources.backwards; + this.toolStripPrevious.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripPrevious.Name = "toolStripPrevious"; + this.toolStripPrevious.Size = new System.Drawing.Size(23, 22); + this.toolStripPrevious.ToolTipText = "Previous (Shift-F11)"; + this.toolStripPrevious.Click += new System.EventHandler(this.toolStripPrevious_Click); + // + // toolStripNext + // + this.toolStripNext.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripNext.Image = global::ImageComparer.Properties.Resources.forwards; + this.toolStripNext.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripNext.Name = "toolStripNext"; + this.toolStripNext.Size = new System.Drawing.Size(23, 22); + this.toolStripNext.ToolTipText = "Next (F11)"; + this.toolStripNext.Click += new System.EventHandler(this.toolStripNext_Click); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(6, 25); + // + // toolStripRevert + // + this.toolStripRevert.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripRevert.Image = global::ImageComparer.Properties.Resources.undo; + this.toolStripRevert.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripRevert.Name = "toolStripRevert"; + this.toolStripRevert.Size = new System.Drawing.Size(23, 22); + this.toolStripRevert.ToolTipText = "Revert (F12)"; + this.toolStripRevert.Click += new System.EventHandler(this.toolStripRevert_Click); + // + // toolStripFileList + // + this.toolStripFileList.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.toolStripFileList.Name = "toolStripFileList"; + this.toolStripFileList.Size = new System.Drawing.Size(250, 25); + this.toolStripFileList.SelectedIndexChanged += new System.EventHandler(this.toolStripFileList_SelectedIndexChanged); + // + // toolStripAutoSize + // + this.toolStripAutoSize.Checked = true; + this.toolStripAutoSize.CheckOnClick = true; + this.toolStripAutoSize.CheckState = System.Windows.Forms.CheckState.Checked; + this.toolStripAutoSize.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripAutoSize.Image = global::ImageComparer.Properties.Resources.autosizeoptimize; + this.toolStripAutoSize.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripAutoSize.Name = "toolStripAutoSize"; + this.toolStripAutoSize.Size = new System.Drawing.Size(23, 22); + this.toolStripAutoSize.ToolTipText = "Auto-Size"; + this.toolStripAutoSize.CheckedChanged += new System.EventHandler(this.toolStripAutoSize_CheckedChanged); + // + // toolStripSeparator3 + // + this.toolStripSeparator3.Name = "toolStripSeparator3"; + this.toolStripSeparator3.Size = new System.Drawing.Size(6, 25); + // + // toolStripGotoWeb + // + this.toolStripGotoWeb.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolStripGotoWeb.Image = global::ImageComparer.Properties.Resources.webdestination; + this.toolStripGotoWeb.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripGotoWeb.Name = "toolStripGotoWeb"; + this.toolStripGotoWeb.Size = new System.Drawing.Size(23, 22); + this.toolStripGotoWeb.ToolTipText = "Goto Web (Ctrl-G)"; + this.toolStripGotoWeb.Click += new System.EventHandler(this.toolStripGotoWeb_Click); + // + // ImageComparerWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(976, 461); + this.Controls.Add(this.previewSplitContainer); + this.Controls.Add(this.toolStrip); + this.KeyPreview = true; + this.Margin = new System.Windows.Forms.Padding(2); + this.MinimumSize = new System.Drawing.Size(400, 200); + this.Name = "ImageComparerWindow"; + this.Text = "Compare Images"; + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ScreenshotPreviewForm_KeyDown); + ((System.ComponentModel.ISupportInitialize)(this.oldScreenshotPictureBox)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.newScreenshotPictureBox)).EndInit(); + this.previewSplitContainer.Panel1.ResumeLayout(false); + this.previewSplitContainer.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.previewSplitContainer)).EndInit(); + this.previewSplitContainer.ResumeLayout(false); + this.oldScreenshotLabelPanel.ResumeLayout(false); + this.oldScreenshotLabelPanel.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureMatching)).EndInit(); + this.newScreenshotLabelPanel.ResumeLayout(false); + this.newScreenshotLabelPanel.PerformLayout(); + this.toolStrip.ResumeLayout(false); + this.toolStrip.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.PictureBox oldScreenshotPictureBox; + private System.Windows.Forms.PictureBox newScreenshotPictureBox; + private System.Windows.Forms.SplitContainer previewSplitContainer; + private System.Windows.Forms.Label newScreenshotLabel; + private System.Windows.Forms.Panel newScreenshotLabelPanel; + private System.Windows.Forms.ToolTip helpTip; + private System.Windows.Forms.Panel oldScreenshotLabelPanel; + private System.Windows.Forms.Label oldScreenshotLabel; + private System.Windows.Forms.ToolStrip toolStrip; + private System.Windows.Forms.ToolStripButton toolStripOpenFolder; + private System.Windows.Forms.ToolStripButton toolStripPrevious; + private System.Windows.Forms.ToolStripButton toolStripNext; + private System.Windows.Forms.ToolStripButton toolStripRevert; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripButton toolStripGotoWeb; + private System.Windows.Forms.ToolStripButton toolStripAutoSize; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.Button buttonImageSource; + private System.Windows.Forms.Label labelOldSize; + private System.Windows.Forms.Label labelNewSize; + private System.Windows.Forms.PictureBox pictureMatching; + private System.Windows.Forms.ToolStripComboBox toolStripFileList; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + } +} \ No newline at end of file diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.cs b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.cs new file mode 100644 index 0000000000..88b3e973d5 --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.cs @@ -0,0 +1,988 @@ +/* + * Original authors: Brendan MacLean + * MacCoss Lab, Department of Genome Sciences, UW + * + * Copyright 2024 University of Washington - Seattle, WA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Windows.Forms; +using ImageComparer.Properties; + +namespace ImageComparer +{ + + public partial class ImageComparerWindow : Form + { + private const int SCREENSHOT_MAX_WIDTH = 800; // doubled as side by side + private const int SCREENSHOT_MAX_HEIGHT = 800; + + // these members should only be accessed in a block which locks on _lock (is this necessary for all?) + #region synchronized members + private readonly object _lock = new object(); + private ScreenshotFile _fileToShow; + private OldScreenshot _oldScreenshot; + private OldScreenshot _newScreenshot; + private bool? _oldAndNewMatch; + #endregion + + public ImageComparerWindow() + { + _oldScreenshot = new OldScreenshot(); + _newScreenshot = new OldScreenshot(); + + InitializeComponent(); + + Icon = Resources.camera; + + _defaultImageSourceTipText = helpTip.GetToolTip(buttonImageSource); + + UpdateImageSourceButtons(); + + // Unfortunately there is not enough information about the image sizes to + // the starting location right here, but this is better than using the Windows default + labelOldSize.Text = labelNewSize.Text = string.Empty; + StartPosition = FormStartPosition.Manual; + + var savedLocation = Settings.Default.FormLocation; + if (!savedLocation.IsEmpty) + Location = Settings.Default.FormLocation; + toolStripAutoSize.Checked = !Settings.Default.ManualSize; + if (Settings.Default.ManualSize) + { + var savedSize = Settings.Default.FormSize; + if (!savedSize.IsEmpty) + Size = savedSize; + } + ForceOnScreen(); + if (Settings.Default.FormMaximized) + WindowState = FormWindowState.Maximized; + } + + private readonly string _defaultImageSourceTipText; // Store for later + + protected override void OnShown(EventArgs e) + { + _autoResizeComplete = true; + base.OnShown(e); + + OpenFolder(Settings.Default.LastOpenFolder); + } + + protected override void OnClosed(EventArgs e) + { + Settings.Default.Save(); + + base.OnClosed(e); + } + + private bool HasBackgroundWork { get { lock(_lock) { return !IsOldLoaded || !IsNewLoaded; } } } + private bool IsOldLoaded { get { lock (_lock) { return _oldScreenshot.IsCurrent(_fileToShow, OldImageSource); } } } + private bool IsNewLoaded { get { lock (_lock) { return _newScreenshot.IsCurrent(_fileToShow, ImageSource.disk); } } } + + private void RefreshScreenshots() + { + lock (_lock) + { + _oldScreenshot.FileLoaded = null; + _newScreenshot.FileLoaded = null; + } + + FormStateChanged(); + } + + private void RefreshAll() + { + OpenFolder(Settings.Default.LastOpenFolder); + } + + private void FormStateChangedBackground() + { + if (InvokeRequired) + BeginInvoke((Action)FormStateChanged); + else + FormStateChanged(); + } + + /// + /// Updates the UI and begins any background work that still needs to be done. + /// + private void FormStateChanged() + { + lock (_lock) + { + UpdateToolStrip(); + UpdatePreviewImages(); + } + + if (IsOldLoaded || IsNewLoaded) + { + ResizeComponents(); + } + + if (HasBackgroundWork) + { + var imageSource = OldImageSource; // Get this value now + var threadUpdateScreenshots = new Thread(() => UpdateScreenshotsAsync(imageSource)); + threadUpdateScreenshots.Start(); + } + } + + private void UpdatePreviewImages() + { + lock (_lock) + { + helpTip.SetToolTip(oldScreenshotLabel, _oldScreenshot.FileLoaded); + SetPreviewSize(labelOldSize, _oldScreenshot); + SetPreviewImage(oldScreenshotPictureBox, _oldScreenshot); + helpTip.SetToolTip(newScreenshotLabel, _newScreenshot.FileLoaded); + SetPreviewSize(labelNewSize, _newScreenshot); + SetPreviewImage(newScreenshotPictureBox, _newScreenshot); + if (!_oldAndNewMatch.HasValue) + { + pictureMatching.Visible = false; + labelOldSize.Left = pictureMatching.Left; + } + else + { + pictureMatching.Visible = true; + labelOldSize.Left = pictureMatching.Right; + var bmpDiff = _oldAndNewMatch.Value ? Resources.Peak : Resources.NoPeak; + bmpDiff.MakeTransparent(Color.White); + pictureMatching.Image = bmpDiff; + } + } + } + + private static void SetPreviewSize(Label labelSize, ScreenshotInfo screenshot) + { + var image = screenshot.Image; + if (image == null || screenshot.IsPlaceholder) + labelSize.Text = string.Empty; + else + { + lock (image) + { + labelSize.Text = $@"{image.Width} x {image.Height}px"; + } + } + } + + private void UpdateToolStrip() + { + toolStripPrevious.Enabled = toolStripFileList.SelectedIndex > 0; + toolStripNext.Enabled = toolStripFileList.SelectedIndex < toolStripFileList.Items.Count - 1; + toolStripRevert.Enabled = !(_oldAndNewMatch ?? true); + toolStripGotoWeb.Enabled = _fileToShow != null; + } + + private void UpdateScreenshotsAsync(ImageSource oldImageSource) + { + ScreenshotFile fileToShow; + ScreenshotInfo oldScreenshot; + string oldFileLoaded; + + lock (_lock) + { + fileToShow = _fileToShow; + oldFileLoaded = _oldScreenshot.FileLoaded; + oldScreenshot = new ScreenshotInfo(_oldScreenshot); + } + + string oldFileDescription = fileToShow.GetDescription(oldImageSource); + if (!Equals(oldFileLoaded, oldFileDescription)) + { + oldScreenshot = LoadScreenshot(fileToShow, oldImageSource); + oldFileLoaded = oldFileDescription; + } + + lock (_lock) + { + if (!Equals(fileToShow, _fileToShow)) + return; + _oldScreenshot = new OldScreenshot(oldScreenshot, oldFileLoaded); + } + + ScreenshotInfo newScreenshot; + string newFileLoaded; + bool? oldAndNewMatch = null; + + lock (_lock) + { + newFileLoaded = _newScreenshot.FileLoaded; + newScreenshot = new ScreenshotInfo(_newScreenshot); + } + + var newFileDescription = fileToShow.GetDescription(ImageSource.disk); + if (!Equals(newFileLoaded, newFileDescription)) + { + // New screenshot is the file on disk + newScreenshot = LoadScreenshot(fileToShow, ImageSource.disk); + } + + if (!newScreenshot.IsPlaceholder && !oldScreenshot.IsPlaceholder) + { + Bitmap diffImage; + bool imageChanged; + lock (oldScreenshot.Image) + { + lock (newScreenshot.Image) + { + diffImage = DiffImages(oldScreenshot.Image, newScreenshot.Image); + imageChanged = !ReferenceEquals(diffImage, _oldScreenshot.Image); + } + } + + if (imageChanged || oldScreenshot.ImageSize != newScreenshot.ImageSize) + { + oldAndNewMatch = false; + } + else + { + oldAndNewMatch = true; + } + + oldScreenshot = new ScreenshotInfo(diffImage); + } + + lock (_lock) + { + if (!Equals(fileToShow, _fileToShow)) + return; + + _newScreenshot = new OldScreenshot(newScreenshot, newFileDescription); + _oldScreenshot = new OldScreenshot(oldScreenshot, oldFileDescription); + _oldAndNewMatch = oldAndNewMatch; + } + + FormStateChangedBackground(); + } + + private Bitmap DiffImages(Bitmap bmpOld, Bitmap bmpNew) + { + if (bmpNew == null || bmpOld == null || bmpNew.Size != bmpOld.Size) + return bmpOld; + + try + { + return HighlightDifferences(bmpOld, bmpNew, Color.Red); + } + catch (Exception e) + { + this.BeginInvoke((Action)(() => ShowMessageWithException( + "Failed to diff bitmaps.", e))); + return bmpOld; + } + } + + // ReSharper disable once UnusedParameter.Local + private void ShowMessageWithException(string message, Exception e) + { + // TODO: Make exception stack trace available somehow + MessageBox.Show(this, message); + } + + public static Bitmap HighlightDifferences(Bitmap bmpOld, Bitmap bmpNew, Color highlightColor, int alpha = 128) + { + var result = new Bitmap(bmpOld.Width, bmpOld.Height); + + bool diffFound = false; + for (int y = 0; y < bmpOld.Height; y++) + { + for (int x = 0; x < bmpOld.Width; x++) + { + var pixel1 = bmpOld.GetPixel(x, y); + var pixel2 = bmpNew.GetPixel(x, y); + + if (pixel1 != pixel2) + { + var blendedColor = Color.FromArgb( + alpha, + highlightColor.R * alpha / 255 + pixel1.R * (255 - alpha) / 255, + highlightColor.G * alpha / 255 + pixel1.G * (255 - alpha) / 255, + highlightColor.B * alpha / 255 + pixel1.B * (255 - alpha) / 255 + ); + result.SetPixel(x, y, blendedColor); + diffFound = true; + } + else + { + result.SetPixel(x, y, pixel1); + } + } + } + + return diffFound ? result : bmpOld; + } + + private ScreenshotInfo LoadScreenshot(ScreenshotFile file, ImageSource source) + { + try + { + byte[] imageBytes; + switch (source) + { + case ImageSource.git: + imageBytes = GitFileHelper.GetGitFileBinaryContent(file.Path); + break; + case ImageSource.disk: + imageBytes = File.ReadAllBytes(file.Path); + break; + case ImageSource.web: + default: + { + using var webClient = new WebClient(); + using var fileSaverTemp = new FileSaver(file.Path); // Temporary. Never saved + webClient.DownloadFile(file.UrlToDownload, fileSaverTemp.SafeName); + imageBytes = File.ReadAllBytes(fileSaverTemp.SafeName); + } + break; + } + var ms = new MemoryStream(imageBytes); + return new ScreenshotInfo(new Bitmap(ms)); + } + catch (Exception e) + { + this.BeginInvoke((Action) (() => ShowMessageWithException( + string.Format("Failed to load a bitmap from {0}.", file.GetDescription(source)), e))); + var failureBmp = Resources.DiskFailure; + failureBmp.MakeTransparent(Color.White); + return new ScreenshotInfo(failureBmp, true); + } + } + + private void SetPreviewImage(PictureBox previewBox, ScreenshotInfo screenshot) + { + var newImage = screenshot.Image; + if (newImage != null && !screenshot.IsPlaceholder) + { + lock (newImage) + { + var containerSize = !toolStripAutoSize.Checked ? previewBox.Size : Size.Empty; + var screenshotSize = CalcBitmapSize(screenshot, containerSize); + // Always make a copy to avoid having PictureBox lock the bitmap + // which can cause issues with future image diffs + newImage = new Bitmap(screenshot.Image, screenshotSize); + } + } + + previewBox.Image = newImage; + if (newImage != null && Equals(newImage.RawFormat, ImageFormat.Gif)) + { + // Unfortunately the animated progress GIF has a white background + // and it cannot be made transparent without removing the animation + previewBox.BackColor = Color.White; + } + else + { + // The oldScreenshotPictureBox never gets a white background + previewBox.BackColor = oldScreenshotPictureBox.BackColor; + } + } + + private Size CalcBitmapSize(ScreenshotInfo screenshot, Size containerSize) + { + var startingSize = Size.Empty; + if (screenshot == null) + return startingSize; + + startingSize = screenshot.ImageSize; + var scaledHeight = (double)SCREENSHOT_MAX_HEIGHT / startingSize.Height; + var scaledWidth = (double)SCREENSHOT_MAX_WIDTH / startingSize.Width; + + // If a container size is specified then the bitmap is fit to it + if (!containerSize.IsEmpty) + { + scaledHeight = (double)containerSize.Height / startingSize.Height; + scaledWidth = (double)containerSize.Width / startingSize.Width; + } + + // If constraints are not breached then use existing size + if (scaledHeight >= 1 && scaledWidth >= 1) + { + return startingSize; + } + + var scale = Math.Min(scaledHeight, scaledWidth); + return new Size((int)(startingSize.Width * scale), (int)(startingSize.Height * scale)); + } + + private void ResizeComponents() + { + if (WindowState == FormWindowState.Minimized) + return; + + try + { + // Just to be extra safe about splitter panel proportions which should be 50/50 + // This line makes it a hard rule of this form + previewSplitContainer.SplitterDistance = previewSplitContainer.Width / 2; + } + catch (InvalidOperationException) + { + // Do nothing. This can happen when the window is minimized. + } + + if (!toolStripAutoSize.Checked) + return; + + var autoSize = CalcAutoSize(); + if (autoSize.IsEmpty || ClientSize == autoSize) + return; + + _autoResizeComplete = false; + WindowState = FormWindowState.Normal; // Make sure the window is not maximized + ClientSize = autoSize; + _autoResizeComplete = true; + } + + private const int CELL_PADDING = 5; + + private Size CalcAutoSize() + { + lock (_lock) + { + var newImageSize = CalcBitmapSize(_newScreenshot, Size.Empty); + var oldImageSize = CalcBitmapSize(_oldScreenshot, Size.Empty); + if (newImageSize.IsEmpty && oldImageSize.IsEmpty) + return Size.Empty; + + var minFormWidth = Math.Max(newImageSize.Width, oldImageSize.Width) * 2 + CELL_PADDING * 4; + // May want this calculation to be possible before the form is fully shown. That means + // it cannot be based on the Visible property, since it is false when the form is not visible + var minFormHeight = Math.Max(newImageSize.Height, oldImageSize.Height) + + toolStrip.Height + oldScreenshotLabelPanel.Height + CELL_PADDING * 2; + var minClientSize = MinimumSize - (Size - ClientSize); + minFormHeight = Math.Max(minFormHeight, minClientSize.Height); + + return new Size(minFormWidth, minFormHeight); + } + } + + private void SetPath(ScreenshotFile screenshotFile) + { + lock (_lock) + { + _fileToShow = screenshotFile; + } + + FormStateChanged(); + } + + private string GetOpenFolder() + { + using var chooseFolder = new FolderBrowserDialog(); + chooseFolder.SelectedPath = Settings.Default.LastOpenFolder; + if (chooseFolder.ShowDialog(this) == DialogResult.OK) + { + Settings.Default.LastOpenFolder = chooseFolder.SelectedPath; + return chooseFolder.SelectedPath; + } + + return null; + } + + private void OpenFolder(string folderPath = null) + { + if (string.IsNullOrEmpty(folderPath)) + { + folderPath = GetOpenFolder(); + if (string.IsNullOrEmpty(folderPath)) + return; + } + + var comboBox = toolStripFileList.ComboBox; + if (comboBox == null) + toolStripFileList.Items.Clear(); + else + { + var changedFiles = GitFileHelper.GetChangedFilePaths(folderPath); + var listScreenshots = changedFiles.Where(ScreenshotFile.IsMatch).Select(f => new ScreenshotFile(f)).ToArray(); + comboBox.BeginUpdate(); + comboBox.DataSource = listScreenshots; + comboBox.DisplayMember = "RelativePath"; + comboBox.EndUpdate(); + } + if (toolStripFileList.Items.Count > 0) + { + if (toolStripFileList.SelectedIndex != 0) + toolStripFileList.SelectedIndex = 0; + } + else + { + ShowMessageWithException(string.Format("No changed PNG files found in {0}", folderPath), null); + } + + FormStateChanged(); + } + + private void Previous() + { + if (toolStripFileList.SelectedIndex > 0) + toolStripFileList.SelectedIndex--; + } + + private void Next() + { + if (toolStripFileList.SelectedIndex < toolStripFileList.Items.Count - 1) + toolStripFileList.SelectedIndex++; + } + + private void Revert() + { + string filePath = _fileToShow.Path; + if (File.Exists(filePath) && !IsWritable(filePath)) + { + ShowMessageWithException(LineSeparate(string.Format("The file {0} is locked.", filePath), + "Check that it is not open in another program such as TortoiseIDiff."), null); + return; + } + try + { + GitFileHelper.RevertFileToHead(filePath); + RefreshScreenshots(); + } + catch (Exception e) + { + ShowMessageWithException(string.Format("Failed to revert screenshot {0}", filePath), e); + } + } + + private string LineSeparate(params string[] lines) + { + return LineSeparate(lines.ToList()); + } + + private string LineSeparate(IEnumerable lines) + { + var sb = new StringBuilder(); + foreach (var line in lines) + sb.AppendLine(line.Trim()); + return sb.ToString(); + } + + public static bool IsWritable(string path) + { + try + { + using var fs = new FileStream(path, FileMode.Open, FileAccess.Write, FileShare.None); + return true; + } + catch (IOException) + { + // An IOException means the file is locked + return false; + } + catch (UnauthorizedAccessException) + { + // Or we lack permissions can also make it not possible to write to + return false; + } + } + + private void GotoLink() + { + if (_fileToShow is { IsEmpty: false }) + { + OpenLink(_fileToShow.UrlInTutorial); + } + } + + private bool _autoResizeComplete; + + protected override void OnMove(EventArgs e) + { + StoreFormBoundsState(); + base.OnMove(e); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); // Let default re-layout happen + + if (WindowState != FormWindowState.Minimized) + { + if (toolStripAutoSize.Checked && _autoResizeComplete) + { + // The user is resizing the form + toolStripAutoSize.Checked = false; + } + + StoreFormBoundsState(); + + ResizeComponents(); + UpdatePreviewImages(); + } + } + + private void StoreFormBoundsState() + { + // Only store sizing information when not automatically resizing the form + if (!_autoResizeComplete) + return; + + Settings.Default.ManualSize = !toolStripAutoSize.Checked; + if (WindowState == FormWindowState.Normal) + Settings.Default.FormLocation = Location; + if (WindowState == FormWindowState.Normal) + Settings.Default.FormSize = Size; + Settings.Default.FormMaximized = + (WindowState == FormWindowState.Maximized); + } + + private enum ImageSource { disk, web, git } + + private static readonly ImageSource[] OLD_IMAGE_SOURCES = { ImageSource.git, ImageSource.web }; + + private ImageSource OldImageSource + { + get => OLD_IMAGE_SOURCES[Settings.Default.OldImageSource % OLD_IMAGE_SOURCES.Length]; + set + { + for (int i = 0; i < OLD_IMAGE_SOURCES.Length; i++) + { + if (OLD_IMAGE_SOURCES[i] == value) + Settings.Default.OldImageSource = i; + } + } + } + + private void NextOldImageSource() + { + OldImageSource = OLD_IMAGE_SOURCES[(Settings.Default.OldImageSource + 1) % OLD_IMAGE_SOURCES.Length]; + + UpdateImageSourceButtons(); + + lock (_lock) + { + _oldScreenshot = new OldScreenshot(_oldScreenshot, null, OldImageSource); + } + + FormStateChanged(); + } + + private void UpdateImageSourceButtons() + { + switch (OldImageSource) + { + case ImageSource.web: + buttonImageSource.Image = Resources.websource; + helpTip.SetToolTip(buttonImageSource, + LineSeparate(_defaultImageSourceTipText.Split('\n').First(), + "Current: Web")); + break; + case ImageSource.git: + buttonImageSource.Image = Resources.gitsource; + helpTip.SetToolTip(buttonImageSource, + LineSeparate(_defaultImageSourceTipText.Split('\n').First(), + "Current: Git HEAD")); + break; + case ImageSource.disk: + default: + buttonImageSource.Image = Resources.save; + helpTip.SetToolTip(buttonImageSource, _defaultImageSourceTipText); + break; + } + } + + private void toolStripOpenFolder_Click(object sender, EventArgs e) + { + OpenFolder(); + } + + private void toolStripPrevious_Click(object sender, EventArgs e) + { + Previous(); + } + + private void toolStripNext_Click(object sender, EventArgs e) + { + Next(); + } + + private void toolStripRevert_Click(object sender, EventArgs e) + { + Revert(); + } + + // private void toolStripRefresh_Click(object sender, EventArgs e) + // { + // RefreshScreenshots(); + // } + + private void toolStripGotoWeb_Click(object sender, EventArgs e) + { + GotoLink(); + } + + private void toolStripAutoSize_CheckedChanged(object sender, EventArgs e) + { + if (toolStripAutoSize.Checked) + ResizeComponents(); + + StoreFormBoundsState(); + } + + private void buttonImageSource_Click(object sender, EventArgs e) + { + NextOldImageSource(); + } + + private void ScreenshotPreviewForm_KeyDown(object sender, KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.Tab: + if (e.Control) + { + NextOldImageSource(); + e.Handled = true; + } + break; + case Keys.F11: + if (e.Shift) + { + Previous(); + } + else + { + Next(); + } + e.Handled = true; + break; + case Keys.Down: + if (e.Control) + { + Next(); + e.Handled = true; + } + break; + case Keys.Up: + if (e.Control) + { + Previous(); + e.Handled = true; + } + break; + case Keys.PageDown: + Next(); + e.Handled = true; + break; + case Keys.PageUp: + Previous(); + e.Handled = true; + break; + case Keys.Z: + if (e.Control) + { + Revert(); + e.Handled = true; + } + break; + case Keys.F12: + Revert(); + e.Handled = true; + break; + case Keys.F5: + RefreshAll(); + e.Handled = true; + return; + case Keys.R: + if (e.Control) + { + RefreshScreenshots(); + e.Handled = true; + } + break; + case Keys.G: + if (e.Control) + { + GotoLink(); + e.Handled = true; + } + break; + case Keys.C: + if (e.Control) + { + Bitmap imageToCopy; + lock (_lock) + { + imageToCopy = e.Shift ? _oldScreenshot.Image : _newScreenshot.Image; + } + CopyBitmap(imageToCopy); + e.Handled = true; + } + break; + } + } + + private void CopyBitmap(Bitmap bitmap) + { + try + { + Clipboard.SetImage(bitmap); + } + catch (Exception e) + { + ShowMessageWithException("Failed clipboard operation.", e); + } + } + + public static void OpenLink(string link) + { + try + { + Process.Start(link); + } + catch (Exception) + { + throw new IOException(string.Format("Could not open web Browser to show link {0}", link)); + } + } + + public void ForceOnScreen() + { + ForceOnScreen(this); + } + + public static void ForceOnScreen(Form form) + { + var location = form.Location; + location.X = Math.Max(GetScreen(form.Left, form.Top).WorkingArea.Left, + Math.Min(location.X, GetScreen(form.Right, form.Top).WorkingArea.Right - form.Size.Width)); + location.Y = Math.Max(GetScreen(form.Left, form.Top).WorkingArea.Top, + Math.Min(location.Y, GetScreen(form.Left, form.Bottom).WorkingArea.Bottom - form.Size.Height)); + form.Location = location; + } + + private static Screen GetScreen(int x, int y) + { + return Screen.FromPoint(new Point(x, y)); + } + + private class ScreenshotFile + { + private static readonly Regex PATTERN = new Regex(@"\\(\w+)\\(\w\w)\\s-(\d\d)\.png"); + + public static bool IsMatch(string filePath) + { + return PATTERN.Match(filePath).Success; + } + + public ScreenshotFile(string filePath) + { + Path = filePath; + + var match = PATTERN.Match(filePath); + if (match.Success) + { + Name = match.Groups[1].Value; + Locale = match.Groups[2].Value; + Number = int.Parse(match.Groups[3].Value); + } + } + + public string Path { get; } + private string Name { get; } + private string Locale { get; } + private int Number { get; } + + public bool IsEmpty => string.IsNullOrEmpty(Name); + + private const string BASE_URL = "https://skyline.ms/tutorials/24-1"; + public string UrlInTutorial => $"{BASE_URL}/{Name}/{Locale}/index.html#s-{Number}"; + public string UrlToDownload => $"{BASE_URL}/{RelativePath}"; + // RelativePath is used for ComboBox display + // ReSharper disable once MemberCanBePrivate.Local + public string RelativePath => $"{Name}/{Locale}/s-{Number}.png"; + + public string GetDescription(ImageSource source) + { + switch (source) + { + case ImageSource.git: + return $"Git HEAD: {RelativePath}"; + case ImageSource.web: + return UrlToDownload; + case ImageSource.disk: + default: + return Path; + } + } + } + + private class ScreenshotInfo + { + public ScreenshotInfo(Bitmap image = null, bool isPlaceholder = false) + { + Image = image; + ImageSize = image?.Size ?? Size.Empty; + IsPlaceholder = isPlaceholder; + } + + public ScreenshotInfo(ScreenshotInfo info) + { + Image = info.Image; + ImageSize = info.ImageSize; + IsPlaceholder = info.IsPlaceholder; + } + + public Bitmap Image { get; private set; } + public Size ImageSize { get; private set; } + public bool IsPlaceholder { get; private set; } + } + + private class OldScreenshot : ScreenshotInfo + { + public OldScreenshot(Bitmap image = null, bool isPlaceholder = false, string fileLoaded = null, ImageSource source = ImageSource.disk) + : base(image, isPlaceholder) + { + FileLoaded = fileLoaded; + Source = source; + } + + public OldScreenshot(ScreenshotInfo info, string fileLoaded, ImageSource source = ImageSource.disk) + : base(info) + { + FileLoaded = fileLoaded; + Source = source; + } + + public string FileLoaded { get; set; } + + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public ImageSource Source { get; private set; } + + public bool IsCurrent(ScreenshotFile screenshot, ImageSource currentSource) + { + return Equals(FileLoaded, screenshot?.GetDescription(currentSource)); + } + } + + private void toolStripFileList_SelectedIndexChanged(object sender, EventArgs e) + { + SetPath(toolStripFileList.SelectedItem as ScreenshotFile); + } + } +} diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.resx b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.resx new file mode 100644 index 0000000000..8838497947 --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/ImageComparerWindow.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 110, 17 + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Program.cs b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Program.cs new file mode 100644 index 0000000000..e03f4d8af1 --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ImageComparer +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new ImageComparerWindow()); + } + } +} diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/AssemblyInfo.cs b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b9e9f9166d --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageComparer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ImageComparer")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1b2c0f7c-de86-42a9-b265-69026ecf2376")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Resources.Designer.cs b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..9ff28b4bad --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Resources.Designer.cs @@ -0,0 +1,193 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ImageComparer.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ImageComparer.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap autosizeoptimize { + get { + object obj = ResourceManager.GetObject("autosizeoptimize", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap backwards { + get { + object obj = ResourceManager.GetObject("backwards", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + internal static System.Drawing.Icon camera { + get { + object obj = ResourceManager.GetObject("camera", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap DiskFailure { + get { + object obj = ResourceManager.GetObject("DiskFailure", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap forwards { + get { + object obj = ResourceManager.GetObject("forwards", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap gitsource { + get { + object obj = ResourceManager.GetObject("gitsource", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap NoPeak { + get { + object obj = ResourceManager.GetObject("NoPeak", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap openfolder { + get { + object obj = ResourceManager.GetObject("openfolder", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Peak { + get { + object obj = ResourceManager.GetObject("Peak", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap save { + get { + object obj = ResourceManager.GetObject("save", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap undo { + get { + object obj = ResourceManager.GetObject("undo", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap webdestination { + get { + object obj = ResourceManager.GetObject("webdestination", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap websource { + get { + object obj = ResourceManager.GetObject("websource", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Resources.resx b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Resources.resx new file mode 100644 index 0000000000..ee2aec67cb --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Resources.resx @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\autosizeoptimize.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\backwards.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\camera.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\DiskFailure.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\forwards.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\gitsource.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\NoPeak.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\openfolder.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\Peak.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\save.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\undo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\webdestination.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\websource.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Settings.Designer.cs b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..8fdd2c08a2 --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Settings.Designer.cs @@ -0,0 +1,98 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ImageComparer.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.9.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] + public global::System.Drawing.Point FormLocation { + get { + return ((global::System.Drawing.Point)(this["FormLocation"])); + } + set { + this["FormLocation"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] + public global::System.Drawing.Size FormSize { + get { + return ((global::System.Drawing.Size)(this["FormSize"])); + } + set { + this["FormSize"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool FormMaximized { + get { + return ((bool)(this["FormMaximized"])); + } + set { + this["FormMaximized"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ManualSize { + get { + return ((bool)(this["ManualSize"])); + } + set { + this["ManualSize"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public int OldImageSource { + get { + return ((int)(this["OldImageSource"])); + } + set { + this["OldImageSource"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string LastOpenFolder { + get { + return ((string)(this["LastOpenFolder"])); + } + set { + this["LastOpenFolder"] = value; + } + } + } +} diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Settings.settings b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Settings.settings new file mode 100644 index 0000000000..9377e3d52a --- /dev/null +++ b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Properties/Settings.settings @@ -0,0 +1,24 @@ + + + + + + 0, 0 + + + 0, 0 + + + False + + + False + + + 0 + + + + + + \ No newline at end of file diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/DiskFailure.png b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/DiskFailure.png new file mode 100644 index 0000000000000000000000000000000000000000..7bda951f7135b5380d8d3c181e9bf01bd0a5bc61 GIT binary patch literal 5302 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 jDWb>MGh>P;oL`*XtKPkrTEGL$8bP=9P`c&Xj@m literal 0 HcmV?d00001 diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/NoPeak.bmp b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/NoPeak.bmp new file mode 100644 index 0000000000000000000000000000000000000000..09566732f1e2a76036f23265f64b1d3f6c963daa GIT binary patch literal 822 zcmZ?rHDhJ~12Z700mK4O%*Y@C76%bW_#hZ2@Sl!g-p+TSk>?qNPD4;w)cFNF-(j`? z=*xfc8CT#ckr|1ZSAnu-=?54ub9cN&GnrH_06+aEX{r41i%r0%4$ii@gpc9^QKhDgTu^Za3LhbT3iHpr4ML(} zR^NJT&zl|Xd++m?`}eZP{X3CQ%s$@S$>nVD{o}()?noAq5s8r?Mof?gd5~At)UTSC zpWjB*;9#vL3O!;1g~1U>6nex33WFn%DD>@jm1yvN28kpVdL#u3gCmeA^hgO521g)K z=#dsE430pe&|@M{7#x8_p~qC9FgOBTzg&uQ(!r%xb3OyDAg~1U>6nZQL z3WFn%DD+qf6b45iQRuN2C=8B3qR_Ydtwe*L_g<1%=m`}l430pe&=V$57#x8_p(k9R zFgOBTzg`Nt5!r%xb3O$tqg~1U>6qUs<>Q zPmvkfpJB3-HNpRl%j5O9KK@zzbe!YqtgLi)Wfv|howIOZ^0?Epu=cpF?!&p_dWLy8 rZiU_7d+oyNbNyT4Sl55Q literal 0 HcmV?d00001 diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/autosizeoptimize.png b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/autosizeoptimize.png new file mode 100644 index 0000000000000000000000000000000000000000..f385c4ae130cce192b094c361d074c1f1a491378 GIT binary patch literal 498 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e4+aJMvnAJ8?A`(-yQ`^=jW*f2O5V{6p`$9eEga z-zUixYFfsQ$!A17S(Ub~gOAu4_Sd0W~mqy85}Sb4q9e0P^;@ A_W%F@ literal 0 HcmV?d00001 diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/backwards.png b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/backwards.png new file mode 100644 index 0000000000000000000000000000000000000000..5ce48c8b620e4548f71f1a31982bc88d93cac6d9 GIT binary patch literal 671 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e4(TlX&>S zSzVJZHB?V`dabZfWue7S`yHoDJNjNekzc^Vb4g29=JvvB&612=R~5~_&wE@jyC8p^ zpPt=^PoL^+&Rd>8+*sctl2^7bMDV`b8ZMm#Glrji9vUeb9hUGFrzjBW366LY}`**1mmhHD$;n8Gt`nARD*72+`$ycW@UFU{J6 zwX9mY<2F-v!Z+==zH$$jSpKy9wEf45#vRY!e7*LW=~dK!4|1&`iFMY|Fbd zS-(q3IBe=w^WMp7aHaZXb=Uz5wP%r0s{c4Y2{rNc9e3r9Vz!=l%E4*Q7LB9D!h)A; zC$L)<9kFo|*3sd+3z3Ue;5-QL|?5@~n9LS6Iw!~}71oyRqt zf4CmS8NA-7G^fz;^Q-fVZH~p|%T6_O6HOF+Z1puqvHh#?KkGkilRT%(cdw7O1ID0g ziEBhjN@7W>RdP`(kYX@0Ff!0JFxNG*3^6dWGP1NXwa_*&ure_Ca9-pwiiX_$l+3hB W+!`7ut%(O}VDNPHb6Mw<&;$U-qyH!X literal 0 HcmV?d00001 diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/camera.ico b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/camera.ico new file mode 100644 index 0000000000000000000000000000000000000000..428faa0bdba15f6ade9f38ebcc76e0684f7c711f GIT binary patch literal 1206 zcmdUuJ5B>J5Qb+-q;&5hIssR5yArv<>oT#HO(Sv-w^0tcMjGle;Qs-pX?i>lw#=fw-f%JI zb3F|01%!3WqMk7A-zhG<`K6`s##lZ2`Jw0F{6_t^_ZchLmDs`#VU-*S0Rx|2-#DaL z#7NU+)3Tt|(*meB=PO;0`Xhcy{HTcM-JA=peVO}C-`P5k@YNuFgZZf*Gn({XvO(h_ zUSfkf;`?Nbo;zEYUiiCjmA)s7v-g&ic+n-qD+iib0pj;T{zCj21 ZGFbSW5B{@Tg-B#)m7ji3v8C@)!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e4lluh|}aS4_yrj z^?wEj8*Q8vHcoI%YkR;P@r$KM*5#7#JV(hB3Z6%t-UKb-SJX5ri#l_5Z*}yU{(C#4 zZtj@H`+UyV!f#0j?Y+3vXUl7OWGKclJ}Z3RETbrNIr9IP@&nENjZYnWGI&3mC)k>N z*;vAOQA>&U%>Cl9dk-H3T5WsDvcaaqG4Ej*>uqMe4PUr+99ZjY65$?^s9E{_U!E($%Yr&i}C~C`72b+?n#qV zh+}*$%Cz+h_X*>UTf!X|m?Nd_ITu^nbMcs1oe!=t`67D5+3TXJ=Qac0A8ogkj#*B- zd*Y|*9me~vZ&dks-dH)=?{6yhIQ2Nm`-$EUy%gS_MD`=A9<&Ebdh~cr4ywvsrMkVa z+Rjk@jH1bb$4L{-cIUQ;It9ruvt8O^nf65Xkm1~@)e%cJf8smG8M(9FB|4)-m{*m< zyTdLu?7Lu}<=o1T&$aru-ib>!cW(@g+6D$z1_og}Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0;5SpK~y+TeNx>^ zlVKG9z3=AU)GGtGw$)u9I(0dnxTp(}=%OwJnQpm8RFEJ@A|e!0NG5)Z(4Ww^ibX_S z6^MN-zt?xE-59tOL`wI*?&I`4PisyuI&j9>@BGerpL2dkA|j;P)btDu4vioXc#Gwg z74W<mjA#!HZ8 zSu_$2G;NdW8yYb>_6qTMJf-;G$F}~md zRaN0jp%7`BCUR}Q8+G*!^y%}LUp4_tBfc_zs-#2Wmx(Fu>a1u>b%7 M07*qoM6N<$f~&)4dH?_b literal 0 HcmV?d00001 diff --git a/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/openfolder.png b/pwiz_tools/Skyline/Executables/DevTools/ImageComparer/Resources/openfolder.png new file mode 100644 index 0000000000000000000000000000000000000000..eef4ec068b3f7efdfad756fa692d801bb745fd00 GIT binary patch literal 568 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e4>Ijwekv)jpCV#eXM> z?Ob{KIfwnHLcZPHQ*vwROKF9o-iyXY;-48ddMx|;iQ(f!X9aGnXU!IDK^N6J_~jdx z2^jZIwVc9s?G3B&oM$V$C%oCPCoNn`7c_hxufZb#Mby2PM*K1 zAGkCcw64ldZ)HfZ_hdHbxcW}|0ml^)8NqkX+8jBOhjUw2_G(O4n$yC-CBMp~>iG$l zfctij^kf$O=$#j|%wWet0Z}>DYaimnx4!%SOK*yD?Kk~jY-)kQ6F-TGzXArWYKdz^ zNlIc#s#S7PDv)9@GB7gIH89dOvIsFUv@$WWGBnaQFt9Q(P};_+jiMnpKP5A*5?zCd Zm7xVh!>`tP^MD!{JYD@<);T3K0RT!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e42);wF|R{7 zfrBZ!-Kl_UmYS)BiFI>E&?*0gTl%Kw7$tqL&QD`!^J4JWB&<|;+*~~5*_!lfhVVoc zx7wCp91GUX_b;vNywRpI`yS(dkM-^sL~eX#W|$!>Ao-o$#H4Cz@PnpLdyQS$8|Ej} z3U@>+3e~;*-;;TZX@bDl?tTXGoY|c0{2%!lt_t+-`S*L9Y{dp%ub!F}mJH^if>x!T zGA}^^rdr|}QIe8al4_M)lnSI6j0}tnbqy?a4UIz#Ev*bKtc=XH4GgRd3{-BcWk=DF bo1c=IR*73fXshUMpaup{S3j3^P6!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e4XYs=0S->)kgWkY8A* zs3hie<4WW-*_E$k8;Ti>8RoD0b>a+vYUH-Rdp&ioT85Xze$Pvrx@z8%%&LqubC)d{ zvz}R=|NOE~Md{k|my6l#!v~ohZf`SI`gMzwLy@DdaaEk)MejAu@`dL_8K$|I z$UkOe*z>(OEw1|g`F~Gyzx``kB{%zKYG~-{i|hLzO54PysO!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e4lFzu{gbU(q1oTM}gM;H>+87YZ>AlF0rm=c`L+y zG|2TEPvu2I&>Io^wA7z>B^z3<1QeF1^ z@tm2h=T1H4sn-zBuJgN_=pEe5q_XTrk<=HFtFDLMT+L^YkG@p)^0mfqhezcOikIIV zns)htU&B>Lr5_(9Ga63VBtDahp66W3t0+11vX+2A+WD&U3e%0BeCCV3-gYr=Ud4_~;!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e4nt{>#_FIRK1>#-n_fJCO2ffJ*H&3uVLr^p6=>wp-FT3 z{>*-OX6DS|XYKVoy{YSRWtqYntuFb@`=YwwE*4qFxd&<_ zKc~io9tr&AIo<8~p_C@Kbio?l=SF$=8_E-8H}HM>E^&K83~RlC&!U4jUODY>N}KQT zmpM=Sz;UK>Db?>MEu1HvUG&9rW67kmJL94l!UWk5SWV%IQ(b@ji{?k!u1R0Ma}}7! zJFZGDWb77=@m+Agb==gMC?kT#?1uG2@ObMGR`)5UK-+_SsX(B4d; zz|v4;+OEZ%-?$#WtK6?}pgs16+Nvmt&&(SBJ7yZGR9iBJE?K`}eaDw9g<0Y@p0zbx z>lZ&sG<>mlq3E90-}*YMFQ!XAn enumerable of file paths that have been modified, added, or deleted. public static IEnumerable GetChangedFilePaths(string directoryPath) { - var output = RunGitCommand(GetPathInfo(directoryPath), "status --porcelain \"{RelativePath}\"", process => - process.StandardOutput.ReadToEnd()); + var output = RunGitCommand(GetPathInfo(directoryPath), "status --porcelain \"{RelativePath}\""); using var reader = new StringReader(output); while (reader.ReadLine() is { } line) @@ -60,6 +59,15 @@ public static IEnumerable GetChangedFilePaths(string directoryPath) } } + /// + /// Reverts a file to its state in the HEAD commit. + /// + /// The fully qualified path of the file to revert. + public static void RevertFileToHead(string fullPath) + { + RunGitCommand(GetPathInfo(fullPath), "checkout HEAD -- \"{RelativePath}\""); + } + /// /// Executes a Git command in the specified repository and returns the standard output as a string. /// @@ -95,6 +103,11 @@ private static T RunGitCommand(PathInfo pathInfo, string commandTemplate, Fun return output; } + private static string RunGitCommand(PathInfo pathInfo, string commandTemplate) + { + return RunGitCommand(pathInfo, commandTemplate, process => process.StandardOutput.ReadToEnd()); + } + /// /// Retrieves the root directory of the Git repository containing the given directory. ///