Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More control over menus #4031

Open
wants to merge 7 commits into
base: v3-alpha
Choose a base branch
from
Open

More control over menus #4031

wants to merge 7 commits into from

Conversation

FalcoG
Copy link

@FalcoG FalcoG commented Jan 23, 2025

Description

Allows a menu to be cleared and in addition to Menu.Append, you can also now use Menu.Prepend to insert items before an existing menu.

My app has dynamic menu items that are at the top of a menu, which requires more control over the menu than previously possible. More detail and motivation is seen in the linked issue.

Fixes #4030

Type of change

Please select the option that is relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

I've linked up wails (go work use ../../wails/v3) and tried the following: https://gist.github.com/FalcoG/b38517339969008195df7e51346db6be

  • Windows
  • macOS
  • Linux

Test Configuration

# System
┌─────────────────────────────────────────────────┐
| Name          | MacOS                           |
| Version       | 15.1.1                          |
| ID            | 24B91                           |
| Branding      | MacOS 15.1.1                    |
| Platform      | darwin                          |
| Architecture  | arm64                           |
| Apple Silicon | true                            |
| CPU           | Apple M1                        |
| CPU 1         | Apple M1                        |
| CPU 2         | Apple M1                        |
| GPU           | 8 cores, Metal Support: Metal 3 |
| Memory        | 16 GB                           |
└─────────────────────────────────────────────────┘

# Build Environment
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| Wails CLI      | v3.0.0-alpha.8.3                                                                                                          |
| Go Version     | go1.23.4                                                                                                                  |
| -buildmode     | exe                                                                                                                       |
| -compiler      | gc                                                                                                                        |
| CGO_CFLAGS     |                                                                                                                           |
| CGO_CPPFLAGS   |                                                                                                                           |
| CGO_CXXFLAGS   |                                                                                                                           |
| CGO_ENABLED    | 1                                                                                                                         |
| CGO_LDFLAGS    |                                                                                                                           |
| DefaultGODEBUG | asynctimerchan=1,gotypesalias=0,httpservecontentkeepheaders=1,tls3des=1,tlskyber=0,x509keypairleaf=0,x509negativeserial=1 |
| GOARCH         | arm64                                                                                                                     |
| GOARM64        | v8.0                                                                                                                      |
| GOOS           | darwin                                                                                                                    |
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

# Dependencies
┌────────────────────────────────────────────────────────────────────────┐
| npm             | 10.9.2                                               |
| *NSIS           | Not Installed. Install with `brew install makensis`. |
| Xcode cli tools | 2409                                                 |
└─────────────────────── * - Optional Dependency ────────────────────────┘

Checklist:

  • I have updated website/src/pages/changelog.mdx with details of this PR
  • My code follows the general coding style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • New Features

    • Added methods to enhance menu management:
      • Clear all menu items
      • Append menu items to an existing menu
      • Prepend menu items to an existing menu
    • Introduced support for aarch64 AppImage builds and a diagnostics section for wails doctor.
    • Added application.NewServiceWithOptions for service initialization with extra configuration.
  • Bug Fixes

    • Resolved issues with the Windows and Linux Edit Menu and dependency cycles in the binding generator output.
    • Addressed the nil menu issue when calling RegisterContextMenu.
  • Documentation

    • Updated changelog to reflect new features and fixes.
    • Added sections on "Combining menus" and "Clearing a menu" in the documentation.

Copy link
Contributor

coderabbitai bot commented Jan 23, 2025

Walkthrough

The pull request introduces several new methods to the Menu struct in the v3/pkg/application/menu.go file, enhancing its functionality for managing menu items. Key additions include the Clear() method for resetting the menu items, the Destroy() method for cleaning up resources, and the Append() and Prepend() methods for adding items from one menu to another. Documentation updates reflect these changes, including new sections on menu management and enhancements to the service API.

Changes

File Change Summary
v3/pkg/application/menu.go Added methods: Clear(), Destroy(), Append(in *Menu), and Prepend(in *Menu) to the Menu struct.
docs/src/content/docs/changelog.mdx Updated changelog with new methods Menu.Clear, Menu.Prepend, and application.NewServiceWithOptions, along with other enhancements and fixes.
docs/src/content/docs/guides/menus.mdx Added sections on "Combining menus" and "Clearing a menu" with code examples.
v3/pkg/application/menuitem.go Added method Destroy() to MenuItem struct for resource cleanup. Updated menuItemImpl interface to include destroy().
v3/pkg/application/linux_cgo.go Added function menuItemDestroy(widget pointer) for GTK widget destruction.
v3/pkg/application/menuitem_darwin.go Added method destroy() to macosMenuItem struct and function destroyMenuItem(void* nsMenuItem).
v3/pkg/application/menuitem_linux.go Added method destroy() to linuxMenuItem struct for controlled destruction.
v3/pkg/application/menuitem_windows.go Added method destroy() to windowsMenuItem struct for menu item removal.

Assessment against linked issues

Objective Addressed Explanation
Clear menu items
Prepend menu items
Update menu dynamically

Possibly related PRs

  • New ContextMenu API + example #4013: The changes in the main PR introduce new methods to the Menu struct, which are directly related to the context menu functionality introduced in the retrieved PR, specifically the ContextMenu type that embeds Menu and the methods Clear() and Prepend() that enhance menu management.

Suggested labels

TODO

Poem

🐰 A rabbit's menu dance, so neat and light,
Clearing, appending with pure delight!
Prepend or add, the menu's now free,
Hopping through items with glee! 🥕
Code magic sprinkled, just right! 🍃

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@FalcoG FalcoG changed the title V3 alpha More control over menus Jan 23, 2025
@FalcoG FalcoG changed the base branch from master to v3-alpha January 23, 2025 10:34
@leaanthony
Copy link
Member

leaanthony commented Jan 23, 2025

Thanks 🙏 Please could you add an entry to the changelog located at docs/src/content/docs/changelog.mdx? Thanks!
We also need to change the menu documentation too in docs/src/content/docs/guides/menus.mdx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 631c8a1 and 7f5ae65.

📒 Files selected for processing (1)
  • v3/pkg/application/menu.go (2 hunks)
🔇 Additional comments (1)
v3/pkg/application/menu.go (1)

Line range hint 72-202: Add tests and update documentation for new menu methods.

As requested in the PR comments, please:

  1. Add an entry to the changelog in docs/src/content/docs/changelog.mdx
  2. Update the menu documentation in docs/src/content/docs/guides/menus.mdx

Additionally, consider adding test cases for:

  • Clear method with nested submenus
  • Append/Prepend with nil input
  • Append/Prepend with empty menus
  • Append/Prepend maintaining proper menu state
✅ Verification successful

Review comment is accurate - documentation and tests are missing

The verification confirms that:

  • No tests exist for the new Clear, Append, and Prepend methods
  • Documentation updates are missing in both:
    • docs/src/content/docs/changelog.mdx
    • docs/src/content/docs/guides/menus.mdx
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for existing tests of the new methods
rg -l "TestMenu.*(?:Clear|Append|Prepend)" --type go

Length of output: 54


Script:

#!/bin/bash
# Find all test files and menu-related tests
echo "=== All test files ==="
fd "_test\.go$"

echo -e "\n=== Menu-related test content ==="
rg "func Test.*Menu" --type go

echo -e "\n=== Check documentation files ==="
ls docs/src/content/docs/changelog.mdx docs/src/content/docs/guides/menus.mdx 2>/dev/null || echo "Documentation files not found"

Length of output: 3727


Script:

#!/bin/bash
# Check documentation content for mentions of new methods
echo "=== Changelog content ==="
rg -A 5 "Clear|Append|Prepend" docs/src/content/docs/changelog.mdx || echo "No mentions in changelog"

echo -e "\n=== Menu documentation content ==="
rg -A 5 "Clear|Append|Prepend" docs/src/content/docs/guides/menus.mdx || echo "No mentions in menu guide"

Length of output: 406

v3/pkg/application/menu.go Outdated Show resolved Hide resolved
v3/pkg/application/menu.go Show resolved Hide resolved
v3/pkg/application/menu.go Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
docs/src/content/docs/guides/menus.mdx (1)

59-76: LGTM! Consider adding error handling details.

The "Combining menus" section is well-documented with clear examples. The code demonstrates both Append and Prepend operations effectively.

Consider adding information about:

  • Return values of these methods
  • Error handling scenarios
  • Thread safety considerations when modifying menus
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5ae65 and a66e1f0.

📒 Files selected for processing (2)
  • docs/src/content/docs/changelog.mdx (1 hunks)
  • docs/src/content/docs/guides/menus.mdx (1 hunks)
🔇 Additional comments (2)
docs/src/content/docs/guides/menus.mdx (1)

78-91: LGTM! The example effectively demonstrates menu clearing.

The "Clearing a menu" section provides a clear explanation and practical example of using the Clear method.

docs/src/content/docs/changelog.mdx (1)

43-43: LGTM! Changelog entry is accurate and well-formatted.

The entry correctly documents the addition of Menu.Clear and Menu.Prepend methods, with proper attribution and PR reference.

@FalcoG
Copy link
Author

FalcoG commented Jan 23, 2025

Done! Added it to the changelog & have written in the docs on how to use the new functions. Might also write some tray docs soon but that's out of scope for this change.

Add notes to documentation.
@leaanthony
Copy link
Member

@FalcoG - I've added some docs and a Destroy method for those who want to release the memory of menus. LMK what you think 👍

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89ea21a and e521102.

📒 Files selected for processing (7)
  • docs/src/content/docs/guides/menus.mdx (1 hunks)
  • v3/pkg/application/linux_cgo.go (1 hunks)
  • v3/pkg/application/menu.go (2 hunks)
  • v3/pkg/application/menuitem.go (2 hunks)
  • v3/pkg/application/menuitem_darwin.go (2 hunks)
  • v3/pkg/application/menuitem_linux.go (1 hunks)
  • v3/pkg/application/menuitem_windows.go (1 hunks)
🧰 Additional context used
📓 Learnings (1)
v3/pkg/application/menu.go (1)
Learnt from: leaanthony
PR: wailsapp/wails#4031
File: v3/pkg/application/menu.go:199-202
Timestamp: 2025-01-24T22:41:18.566Z
Learning: In the Wails menu system (v3/pkg/application/menu.go), shared state between menus is intentionally designed and desirable. Methods like `Append()` and `Prepend()` should maintain shared references to menu items rather than creating deep copies.
🔇 Additional comments (10)
v3/pkg/application/menuitem_windows.go (1)

99-101: LGTM! Clean implementation of menu item destruction.

The implementation correctly uses the Windows API to remove the menu item using its handle and identifier.

v3/pkg/application/menuitem.go (1)

44-44: LGTM! Interface update for resource cleanup.

The addition of destroy() to the menuItemImpl interface ensures consistent cleanup across all platform implementations.

v3/pkg/application/menuitem_linux.go (1)

24-30: LGTM! Thread-safe implementation with proper signal handling.

The implementation correctly:

  • Uses InvokeSync for thread safety
  • Handles signal blocking/unblocking
  • Follows the established pattern in the file
v3/pkg/application/menuitem_darwin.go (1)

352-354: LGTM! Simple and effective Go wrapper.

The Go implementation correctly wraps the C function for cleanup.

v3/pkg/application/linux_cgo.go (1)

590-592: LGTM! Clean implementation of widget destruction.

The function correctly uses gtk_widget_destroy to clean up GTK widgets, which is essential for proper resource management.

v3/pkg/application/menu.go (4)

72-75: LGTM! Simple and effective menu clearing.

The implementation efficiently clears menu items by setting the slice to nil, allowing Go's garbage collector to handle cleanup.


77-82: LGTM! Thorough cleanup in Destroy method.

The implementation properly calls Destroy() on each item before clearing the slice, ensuring complete resource cleanup.


201-204: LGTM! Maintains intended shared state.

The implementation correctly maintains shared menu item references as intended by the design.


206-209: LGTM! Maintains intended shared state.

The implementation correctly maintains shared menu item references as intended by the design.

docs/src/content/docs/guides/menus.mdx (1)

59-117: LGTM! Comprehensive and well-structured documentation.

The documentation effectively:

  • Explains the new menu manipulation features
  • Provides clear code examples
  • Includes important notes about state sharing and memory management
  • Maintains consistency with the implementation

Comment on lines 430 to 451
func (m *MenuItem) Destroy() {
// Clean up resources
if m.impl != nil {
m.impl.destroy()
}
if m.submenu != nil {
m.submenu.Destroy()
m.submenu = nil
}

if m.contextMenuData != nil {
m.contextMenuData = nil
}

if m.accelerator != nil {
m.accelerator = nil
}

m.callback = nil
m.radioGroupMembers = nil

}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add cleanup of menuItemMap in Destroy().

The implementation thoroughly cleans up resources but should also remove the menu item from the global menuItemMap to prevent memory leaks.

Apply this diff to add the cleanup:

 func (m *MenuItem) Destroy() {
+    menuItemMapLock.Lock()
+    delete(menuItemMap, m.id)
+    menuItemMapLock.Unlock()
+
     // Clean up resources
     if m.impl != nil {
         m.impl.destroy()
     }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +299 to +302
void destroyMenuItem(void* nsMenuItem) {
MenuItem *menuItem = (MenuItem *)nsMenuItem;
[menuItem release];
}
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 25, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use dispatch_async for thread-safe cleanup.

While the implementation correctly releases the NSMenuItem, it should use dispatch_async to ensure thread safety, consistent with other methods in the file.

Apply this diff to make the cleanup thread-safe:

 void destroyMenuItem(void* nsMenuItem) {
-    MenuItem *menuItem = (MenuItem *)nsMenuItem;
-    [menuItem release];
+    dispatch_async(dispatch_get_main_queue(), ^{
+        MenuItem *menuItem = (MenuItem *)nsMenuItem;
+        [menuItem release];
+    });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void destroyMenuItem(void* nsMenuItem) {
MenuItem *menuItem = (MenuItem *)nsMenuItem;
[menuItem release];
}
void destroyMenuItem(void* nsMenuItem) {
dispatch_async(dispatch_get_main_queue(), ^{
MenuItem *menuItem = (MenuItem *)nsMenuItem;
[menuItem release];
});
}

Copy link
Member

Choose a reason for hiding this comment

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

Give me a good reason to run a memory release async

Copy link
Contributor

Choose a reason for hiding this comment

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

@leaanthony You're right - there's no good reason to run memory release asynchronously. The current synchronous implementation is correct because:

  1. Memory should be released immediately to prevent memory leaks
  2. Unlike UI operations, memory release doesn't require main thread coordination
  3. Adding async dispatch would unnecessarily delay cleanup with no benefits

✏️ Learnings added
Learnt from: leaanthony
PR: wailsapp/wails#4031
File: v3/pkg/application/menuitem_darwin.go:299-302
Timestamp: 2025-01-25T04:10:53.851Z
Learning: Memory release operations in Objective-C/Cocoa should be performed synchronously, not asynchronously via dispatch_async, as they don't require main thread coordination and should complete immediately to prevent memory leaks.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Member

Choose a reason for hiding this comment

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

Correct

Copy link
Contributor

Choose a reason for hiding this comment

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

@leaanthony Thank you for confirming!


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
v3/pkg/application/menuitem.go (1)

436-460: Add documentation for the Destroy method.

Please add documentation that explains:

  • The purpose and cleanup process
  • Whether it's safe to call multiple times
  • Any side effects users should be aware of

Example documentation:

+// Destroy releases all resources associated with the MenuItem.
+// This includes cleaning up platform-specific implementations,
+// removing it from the global menuItemMap, and recursively
+// destroying any submenu. It's safe to call this method
+// multiple times on the same MenuItem.
 func (m *MenuItem) Destroy() {
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e521102 and b8d6062.

📒 Files selected for processing (1)
  • v3/pkg/application/menuitem.go (3 hunks)
🔇 Additional comments (2)
v3/pkg/application/menuitem.go (2)

36-40: LGTM! Thread-safe implementation of map cleanup.

The function is well-implemented with proper synchronization using mutex lock/unlock and follows Go best practices.


436-460: LGTM! Thorough cleanup implementation.

The implementation thoroughly handles resource cleanup by:

  • Removing the item from the global map
  • Cleaning up platform-specific resources
  • Recursively destroying submenus
  • Clearing all references to prevent memory leaks

@FalcoG
Copy link
Author

FalcoG commented Jan 26, 2025

@FalcoG - I've added some docs and a Destroy method for those who want to release the memory of menus. LMK what you think 👍

Great addition! That's a convenient way to remove it completely. Also didn't know about the submenu memory thing, good catch.

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.

[v3] More update control over existing menus
2 participants