Skip to content

Commit

Permalink
Skyline: More tutorial screenshot improvements (#3291)
Browse files Browse the repository at this point in the history
* Skyline: More tutorial screenshot improvements
- added the ability to us the Git HEAD PNG file as the old image
- added rectangle annotation to ScreenshotProcessingExtensions
- made ANNOTATION_COLOR public and used as the default for grid highlight annotations
- tweaks to annotation and scaling in TestLibraryExplorerTutorial and TestMs1Tutorial
- implemented MultiFormActivator for Skyline and the ScreenshotPreviewForm
  • Loading branch information
brendanx67 authored Dec 20, 2024
1 parent 3a81813 commit a04685b
Show file tree
Hide file tree
Showing 16 changed files with 461 additions and 142 deletions.
19 changes: 18 additions & 1 deletion pwiz_tools/Shared/CommonUtil/SystemUtil/PathEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,26 @@ public static string FindExistingRelativeFile(string relativeFilePath, string fi
return null;
}

/// <summary>
/// Apparently this was added to Path in .NET 5 and later. Several places in the project have
/// now implemented this with either RemovePrefix or some other method.
/// </summary>
/// <param name="relativePathTo">The base path to make the path relative to</param>
/// <param name="path">The full path to make relative</param>
/// <returns>The relative path from the base path to the given path</returns>
public static string GetRelativePath(string relativePathTo, string path)
{
if (!relativePathTo.EndsWith(Path.DirectorySeparatorChar.ToString()))
relativePathTo += Path.DirectorySeparatorChar;
return RemovePrefix(path, relativePathTo);
}

/// <summary>
/// If the path starts with the prefix, then skip over the prefix;
/// otherwise, return the original path.
/// otherwise, return the original path. This works well as a method of getting a relative
/// path in combination with <see cref="GetCommonRoot(System.Collections.Generic.IEnumerable{string})"/>, since
/// it is guaranteed to return a directory path ending with a Path.DirectorySeparatorChar, but not in
/// cases where a path to a directory lacks a terminating separator. In those cases, use <see cref="GetRelativePath"/>.
/// </summary>
public static string RemovePrefix(string path, string prefix)
{
Expand Down
8 changes: 4 additions & 4 deletions pwiz_tools/Skyline/TestTutorial/CustomReportsTutorialTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,11 @@ protected bool DoQualityControlSummaryReports()
using var g = Graphics.FromImage(bmp);
g.SmoothingMode = SmoothingMode.AntiAlias;

g.DrawBoxOnColumn(documentGridForm, 3, 10, Color.Red);
g.DrawBoxOnColumn(documentGridForm, 5, 10, Color.Red);
g.DrawBoxOnColumn(documentGridForm, 6, 10, Color.Red);
g.DrawBoxOnColumn(documentGridForm, 3, 10);
g.DrawBoxOnColumn(documentGridForm, 5, 10);
g.DrawBoxOnColumn(documentGridForm, 6, 10);

g.DrawEllipseOnCell(documentGridForm, 1, 3, Color.Red);
g.DrawEllipseOnCell(documentGridForm, 1, 3);
g.DrawEllipseOnCell(documentGridForm, 3, 3, Color.Orange);
g.DrawEllipseOnCell(documentGridForm, 1, 5, Color.Orange);
g.DrawEllipseOnCell(documentGridForm, 3, 5, Color.Orange);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ protected override void DoTest()
ViewLibraryDlg viewLibraryDlg = ShowDialog<ViewLibraryDlg>(SkylineWindow.ViewSpectralLibraries);
var matchedPepsDlg = WaitForOpenForm<AddModificationsDlg>();
OkDialog(matchedPepsDlg, matchedPepsDlg.CancelDialog);
RunUIForScreenShot(() => viewLibraryDlg.Size = new Size(775, 463));
PauseForScreenShot<ViewLibraryDlg>("Library Explorer", 5);

// Types text in Peptide textbox in the Spectral Library Explorer Window
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,11 @@ protected override void DoTest()
PauseForScreenShot<AlignmentForm>("Retention time alignment form", tutorialPage++);

OkDialog(alignmentForm, alignmentForm.Close);
PauseForScreenShot("Status bar clipped from main window - 4/50 pep 4/51 prec 10/153 tran", tutorialPage++, null, ClipSelectionStatus);
PauseForScreenShot("Status bar clipped from main window - 4/50 pep 4/51 prec 10/153 tran", tutorialPage++, null, bmp =>
{
bmp = ClipSelectionStatus(bmp);
return bmp.DrawAnnotationRectOnBitmap(new RectangleF(0.23F, 0, 0.23F, 1), 2).Inflate(1.5F);
});

string TIP_NAME = SkylineWindow.Document.Settings.MeasuredResults.Chromatograms[TIP3].Name;
string TIB_NAME = SkylineWindow.Document.Settings.MeasuredResults.Chromatograms[TIB_L].Name;
Expand Down
151 changes: 151 additions & 0 deletions pwiz_tools/Skyline/TestUtil/GitFileHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Original author: Brendan MacLean <brendanx .at. u.washington.edu>,
* 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;
using pwiz.Common.SystemUtil;

namespace pwiz.SkylineTestUtil
{
internal static class GitFileHelper
{
/// <summary>
/// Retrieves the committed binary content of a file in a Git repository.
/// </summary>
/// <param name="fullPath">The fully qualified path of the file.</param>
/// <returns>The committed binary content of the file.</returns>
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();
});
}

/// <summary>
/// Gets a list of changed file paths under a specific directory.
/// </summary>
/// <param name="directoryPath">The fully qualified directory path.</param>
/// <returns>An enumerable of file paths that have been modified, added, or deleted.</returns>
public static IEnumerable<string> GetChangedFilePaths(string directoryPath)
{
var output = RunGitCommand(GetPathInfo(directoryPath), "status --porcelain \"{RelativePath}\"", process =>
process.StandardOutput.ReadToEnd());

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);
}
}

/// <summary>
/// Executes a Git command in the specified repository and returns the standard output as a string.
/// </summary>
/// <param name="pathInfo">The PathInfo object for the target path.</param>
/// <param name="commandTemplate">The Git command template with placeholders (e.g., {RelativePath}).</param>
/// <param name="processOutput">A function that takes a running process and returns the necessary output</param>
/// <returns>Output of type T from the process.</returns>
private static T RunGitCommand<T>(PathInfo pathInfo, string commandTemplate, Func<Process, T> 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;
}

/// <summary>
/// Retrieves the root directory of the Git repository containing the given directory.
/// </summary>
/// <param name="startPath">The directory or file path to start searching from.</param>
/// <returns>The root directory of the Git repository, or null if not found.</returns>
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
}

/// <summary>
/// Retrieves path information for a given file or directory, including the repository root and relative path.
/// </summary>
/// <param name="fullPath">The fully qualified file or directory path.</param>
/// <returns>A PathInfo object containing the repository root and the relative path.</returns>
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 = PathEx.GetRelativePath(repoRoot, fullPath).Replace(Path.DirectorySeparatorChar, '/');
return new PathInfo { Root = repoRoot, RelativePath = relativePath };
}

private struct PathInfo
{
public string Root { get; set; }
public string RelativePath { get; set; }
}
}

}
54 changes: 37 additions & 17 deletions pwiz_tools/Skyline/TestUtil/MultiFormActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ public MultiFormActivator(params Form[] formsToActivate)
}
}

public void Reset(params Form[] formsToActivate)
{
Clear();
foreach (var form in formsToActivate)
{
AddForm(form);
}
}

public void AddForm(Form form)
{
lock (_formsToActivate)
Expand All @@ -47,18 +56,22 @@ public void AddForm(Form form)
}
}

public void AddRange(IEnumerable<Form> forms)
{
// Add only forms that do not parent another form. Track and activate only the
// deepest child forms. They are the ones that will get activated and bringing them
// forward will drag their parents with them.
var listForms = forms.ToArray();
var setParents = new HashSet<IntPtr>(listForms
.Where(f => f.Parent != null)
.Select(f => f.Parent.Handle));
foreach (var form in listForms.Where(f => !setParents.Contains(f.Handle)))
AddForm(form);
}
// This approach causes cross-thread exceptions
// public void AddRange(IEnumerable<Form> forms)
// {
// lock (_formsToActivate)
// {
// // Add only forms that do not parent another form. Track and activate only the
// // deepest child forms. They are the ones that will get activated and bringing them
// // forward will drag their parents with them.
// var listForms = forms.ToArray();
// var setParents = new HashSet<IntPtr>(listForms
// .Where(f => f.Parent != null)
// .Select(f => f.Parent.Handle));
// foreach (var form in listForms.Where(f => !setParents.Contains(f.Handle)))
// AddForm(form);
// }
// }

public void RemoveForm(Form form)
{
Expand All @@ -73,7 +86,7 @@ public void Clear()
{
lock (_formsToActivate)
{
foreach (var form in _formsToActivate)
foreach (var form in _formsToActivate.ToArray())
RemoveForm(form);
}
}
Expand All @@ -85,7 +98,7 @@ private void FormActivated(object sender, EventArgs e)

lock (_formsToActivate)
{
foreach (var form in _formsToActivate.Where(form => !ReferenceEquals(form, activatedForm) && form.Visible))
foreach (var form in _formsToActivate.Where(form => !ReferenceEquals(form, activatedForm)))
{
ActionUtil.RunAsync(() => ShowForm(form, activatedFormHandle));
}
Expand All @@ -95,13 +108,20 @@ private void FormActivated(object sender, EventArgs e)
private void ShowForm(Form form, IntPtr referenceFormHandle)
{
// Must be done on the form's thread
form.Invoke((Action)(() => form.BringWindowToSameLevelWithoutActivating(referenceFormHandle)));
form.Invoke((Action)(() =>
{
if (form.Visible)
form.BringWindowToSameLevelWithoutActivating(referenceFormHandle);
}));
}

public void Dispose()
{
while (_formsToActivate.Any())
RemoveForm(_formsToActivate.First());
lock (_formsToActivate)
{
while (_formsToActivate.Any())
RemoveForm(_formsToActivate.First());
}
}
}
}
10 changes: 10 additions & 0 deletions pwiz_tools/Skyline/TestUtil/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pwiz_tools/Skyline/TestUtil/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@
<data name="DiskFailure" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\DiskFailure.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="gitsource" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\gitsource.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="hand_cur" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\hand-cur.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
<Setting Name="ShowTextButtons" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="ShowWebImage" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="ManualSizePreview" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="OldImageSource" Type="System.Int32" Scope="User">
<Value Profile="(Default)">0</Value>
</Setting>
</Settings>
</SettingsFile>
Binary file added pwiz_tools/Skyline/TestUtil/Resources/gitsource.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit a04685b

Please sign in to comment.