Skip to content

Conversation

@TheJoeFin
Copy link
Owner

Fixes #604

Introduce a configurable system for post-capture actions in Fullscreen Grab, including a new settings dialog, dynamic menu generation, and persistent user preferences. Refactor related models and UI for extensibility and maintainability. Add unit tests for action management logic.
Introduces a user setting and UI toggle to control whether the post-grab menu stays open after selecting an action. Also adds InputGestureText to ButtonInfo.
Removed InputGestureText from ButtonInfo and related tests. Input gestures for post-grab actions are now set dynamically in FullscreenGrab.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements customizable post-grab actions for the Fullscreen Grab feature, allowing users to configure which text transformation actions are available after capturing text. The hardcoded menu items in the fullscreen grab context menu have been replaced with a dynamic system managed by PostGrabActionManager.

Changes:

  • Introduced PostGrabActionManager utility class to manage post-grab actions configuration
  • Added PostGrabActionEditor UI for users to customize available actions and their order
  • Refactored FullscreenGrab to dynamically load and execute actions based on user preferences
  • Added three new settings properties for persisting action configurations and check states

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
Text-Grab/Utilities/PostGrabActionManager.cs New utility class managing post-grab actions (loading, saving, executing)
Text-Grab/Views/FullscreenGrab.xaml.cs Refactored to dynamically load actions via LoadDynamicPostGrabActions() and execute them
Text-Grab/Views/FullscreenGrab.xaml Commented out hardcoded menu items in favor of dynamic generation
Text-Grab/Controls/PostGrabActionEditor.xaml.cs New editor dialog for managing enabled actions list
Text-Grab/Controls/PostGrabActionEditor.xaml UI for the post-grab action editor with add/remove/reorder functionality
Text-Grab/Pages/FullscreenGrabSettings.xaml.cs Added button to open editor and display current actions count
Text-Grab/Pages/FullscreenGrabSettings.xaml Added UI section for customizing post-capture actions (removed ScrollViewer)
Text-Grab/Models/ButtonInfo.cs Added enum DefaultCheckState and new properties for post-grab action support
Text-Grab/Properties/Settings.settings Added three new settings for post-grab configuration
Text-Grab/Properties/Settings.Designer.cs Generated code for new settings properties
Text-Grab/App.config Added configuration entries for new settings
Text-Grab/Pages/GeneralSettings.xaml.cs Added Clear() before populating WebSearchersComboBox to prevent duplicates
Tests/PostGrabActionManagerTests.cs Comprehensive unit tests for PostGrabActionManager functionality
Files not reviewed (1)
  • Text-Grab/Properties/Settings.Designer.cs: Language not supported

Comment on lines 44 to 47
public override int GetHashCode()
{
return System.HashCode.Combine(ButtonText, SymbolText, Background, Command, ClickEvent);
return System.HashCode.Combine(ButtonText, SymbolText, Background, Command, ClickEvent, IsRelevantForFullscreenGrab, IsRelevantForEditWindow);
}
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GetHashCode implementation doesn't include DefaultCheckState, but it was added to the HashCode.Combine call. However, other important properties like OrderNumber, SymbolIcon, and IsSymbol are still missing from the hash code calculation. This creates inconsistent equality semantics - two ButtonInfo objects with the same text but different OrderNumber or SymbolIcon values will have the same hash code. Either include all semantically significant properties in the hash code, or reconsider which properties should determine equality.

Copilot uses AI. Check for mistakes.
Comment on lines 204 to 209
string[] stringSplit = text.Split(Environment.NewLine);
string finalString = "";
foreach (string line in stringSplit)
if (!string.IsNullOrWhiteSpace(line))
finalString += line.Trim() + Environment.NewLine;
result = finalString;
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String concatenation in a loop is inefficient. Each concatenation creates a new string object, resulting in O(n²) complexity for n lines. Consider using StringBuilder for better performance, especially when processing text with many lines.

Copilot uses AI. Check for mistakes.
menuItem.Click += PostActionMenuItem_Click;

// Add keyboard handling
contextMenu.PreviewKeyDown += FullscreenGrab_KeyDown;
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event handler for PreviewKeyDown is being added to the ContextMenu inside the loop for each MenuItem. This means the same event handler (FullscreenGrab_KeyDown) will be attached multiple times - once for each action. This should be moved outside the loop to attach only once, or the handler should be attached to individual menu items instead of the context menu.

Copilot uses AI. Check for mistakes.
Comment on lines 259 to 316
private void LoadDynamicPostGrabActions()
{
if (NextStepDropDownButton.Flyout is not ContextMenu contextMenu)
return;

// Clear existing items
contextMenu.Items.Clear();

// Get enabled post-grab actions from settings
List<ButtonInfo> enabledActions = PostGrabActionManager.GetEnabledPostGrabActions();

// Get the PostGrabStayOpen setting
bool stayOpen = DefaultSettings.PostGrabStayOpen;

int index = 1;
foreach (ButtonInfo action in enabledActions)
{
MenuItem menuItem = new()
{
Header = action.ButtonText,
IsCheckable = true,
Tag = action,
IsChecked = PostGrabActionManager.GetCheckState(action),
StaysOpenOnClick = stayOpen,
InputGestureText = $"Ctrl+{index}"
};

// Wire up click handler
menuItem.Click += PostActionMenuItem_Click;

// Add keyboard handling
contextMenu.PreviewKeyDown += FullscreenGrab_KeyDown;

contextMenu.Items.Add(menuItem);
index++;
}

contextMenu.Items.Add(new Separator());

// Add "Edit this list..." menu item
MenuItem editPostGrabMenuItem = new()
{
Header = "Edit this list..."
};
editPostGrabMenuItem.Click += EditPostGrabActions_Click;
contextMenu.Items.Add(editPostGrabMenuItem);

// Add "Close this menu" menu item
MenuItem hidePostGrabMenuItem = new()
{
Header = "Close this menu"
};
hidePostGrabMenuItem.Click += HidePostGrabActions_Click;
contextMenu.Items.Add(hidePostGrabMenuItem);

// Update the dropdown button appearance
CheckIfAnyPostActionsSelected();
}
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event handlers attached to dynamically created MenuItems (menuItem.Click and contextMenu.PreviewKeyDown) are not being cleaned up in Window_Unloaded. This creates a potential memory leak, especially if the window is opened and closed multiple times. Consider either: 1) Storing references to dynamically created items and removing handlers in Window_Unloaded, or 2) Using weak event patterns, or 3) Clearing and recreating the menu each time it opens rather than in Window_Loaded.

Copilot uses AI. Check for mistakes.
Comment on lines 25 to 31
foreach (ButtonInfo button in ButtonInfo.AllButtons)
{
if (button.IsRelevantForFullscreenGrab && !allPostGrabActions.Any(b => b.ButtonText == button.ButtonText))
{
allPostGrabActions.Add(button);
}
}
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.

Copilot uses AI. Check for mistakes.
Comment on lines 205 to 209
string finalString = "";
foreach (string line in stringSplit)
if (!string.IsNullOrWhiteSpace(line))
finalString += line.Trim() + Environment.NewLine;
result = finalString;
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.

Suggested change
string finalString = "";
foreach (string line in stringSplit)
if (!string.IsNullOrWhiteSpace(line))
finalString += line.Trim() + Environment.NewLine;
result = finalString;
var trimmedLines = stringSplit
.Where(line => !string.IsNullOrWhiteSpace(line))
.Select(line => line.Trim())
.ToArray();
result = trimmedLines.Length == 0
? string.Empty
: string.Join(Environment.NewLine, trimmedLines) + Environment.NewLine;

Copilot uses AI. Check for mistakes.
Comment on lines 147 to 151
if (checkStates is not null && checkStates.TryGetValue(action.ButtonText, out bool storedState))
{
// If the action is set to LastUsed, use the stored state
if (action.DefaultCheckState == DefaultCheckState.LastUsed)
return storedState;
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 'if' statements can be combined.

Suggested change
if (checkStates is not null && checkStates.TryGetValue(action.ButtonText, out bool storedState))
{
// If the action is set to LastUsed, use the stored state
if (action.DefaultCheckState == DefaultCheckState.LastUsed)
return storedState;
if (checkStates is not null
&& checkStates.TryGetValue(action.ButtonText, out bool storedState)
&& action.DefaultCheckState == DefaultCheckState.LastUsed)
{
// If the action is set to LastUsed, use the stored state
return storedState;

Copilot uses AI. Check for mistakes.
Comment on lines 1103 to 1108
if (sender is MenuItem menuItem && menuItem.Tag is ButtonInfo action)
{
if (action.DefaultCheckState == DefaultCheckState.LastUsed)
{
PostGrabActionManager.SaveCheckState(action, menuItem.IsChecked);
}
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 'if' statements can be combined.

Suggested change
if (sender is MenuItem menuItem && menuItem.Tag is ButtonInfo action)
{
if (action.DefaultCheckState == DefaultCheckState.LastUsed)
{
PostGrabActionManager.SaveCheckState(action, menuItem.IsChecked);
}
if (sender is MenuItem menuItem
&& menuItem.Tag is ButtonInfo action
&& action.DefaultCheckState == DefaultCheckState.LastUsed)
{
PostGrabActionManager.SaveCheckState(action, menuItem.IsChecked);

Copilot uses AI. Check for mistakes.
@TheJoeFin
Copy link
Owner Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Contributor

Copilot AI commented Jan 11, 2026

@TheJoeFin I've opened a new pull request, #607, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 7 commits January 11, 2026 20:38
Refactor post-grab actions: fix memory leaks, improve performance, enhance maintainability
Reduced editor window height and updated ListView scrollbars. Commented out the "Translate to system language" post-grab action.
Adjusted test expectations after removing the action from defaults.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants