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

fix: fix menu mnemonics #640

Merged
merged 1 commit into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/fix-mnemonic-on-linux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tao": "patch"
---

On Linux, fix menu item mnemonics.
5 changes: 5 additions & 0 deletions .changes/macos-strip-mnemonics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tao": "patch"
---

On macOS, strip menu mnemonics for consistency with other platforms.
25 changes: 23 additions & 2 deletions src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ pub struct MenuItemAttributes<'a> {
impl<'a> MenuItemAttributes<'a> {
/// Creates a new custom menu item.
///
/// ## Platform-specific
/// `title` could optionally contain an `&` before a character to assign this character as the mnemonic
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`
///
/// ## Platform-specific:
///
/// - **Linux:** `selected` render a regular item
/// - **macOS**: mnemonics are not supported but single `&` will be rmeoved anyways for consistency with other platforms.
pub fn new(title: &'a str) -> Self {
Self {
id: MenuId::new(title),
Expand Down Expand Up @@ -92,6 +95,10 @@ impl<'a> MenuItemAttributes<'a> {
}

/// Assign default checkbox style.
///
/// Default is `false`
///
/// If `selected` is `false`, renders a regular menu item.
pub fn with_selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
Expand All @@ -117,6 +124,13 @@ impl ContextMenu {
}

/// Add a submenu.
///
/// `title` could optionally contain an `&` before a character to assign this character as the mnemonic
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`
///
/// ## Platform-specific:
///
/// - **macOS**: mnemonics are not supported but single `&` will be rmeoved anyways for consistency with other platforms.
pub fn add_submenu(&mut self, title: &str, enabled: bool, submenu: ContextMenu) {
self
.0
Expand Down Expand Up @@ -158,6 +172,13 @@ impl MenuBar {
}

/// Add a submenu.
///
/// `title` could optionally contain an `&` before a character to assign this character as the mnemonic
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`
///
/// ## Platform-specific:
///
/// - **macOS**: mnemonics are not supported but single `&` will be rmeoved anyways for consistency with other platforms.
pub fn add_submenu(&mut self, title: &str, enabled: bool, submenu: MenuBar) {
self
.0
Expand Down
27 changes: 23 additions & 4 deletions src/platform_impl/linux/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ impl MenuItemAttributes {
.gtk_item
.label()
.map(|gstr| gstr.as_str().to_owned())
.unwrap_or("".to_owned())
.map(|label| from_gtk_mnemonic(&label))
.unwrap_or_default()
}
pub fn set_enabled(&mut self, is_enabled: bool) {
self.gtk_item.set_sensitive(is_enabled);
}
pub fn set_title(&mut self, title: &str) {
self.gtk_item.set_label(title);
self.gtk_item.set_label(&to_gtk_mnemonic(title));
}

pub fn set_selected(&mut self, is_selected: bool) {
Expand Down Expand Up @@ -129,12 +130,13 @@ impl Menu {
selected: bool,
menu_type: MenuType,
) -> CustomMenuItem {
let title = to_gtk_mnemonic(&title);
let gtk_item = if selected {
let item = CheckMenuItem::with_label(title);
let item = CheckMenuItem::with_label(&title);
item.set_active(true);
item.upcast::<GtkMenuItem>()
} else {
GtkMenuItem::with_label(title)
GtkMenuItem::with_label(&title)
};
let custom_menu = MenuItemAttributes {
id: menu_id,
Expand Down Expand Up @@ -403,3 +405,20 @@ fn modifiers_to_gdk_modifier_type(modifiers: ModifiersState) -> gdk::ModifierTyp

result
}

pub fn to_gtk_mnemonic<S: AsRef<str>>(string: S) -> String {
string
.as_ref()
.replace("&&", "[~~]")
.replace('&', "_")
.replace("[~~]", "&&")
.replace("[~~]", "&")
}

pub fn from_gtk_mnemonic<S: AsRef<str>>(string: S) -> String {
string
.as_ref()
.replace("__", "[~~]")
.replace('_', "&")
.replace("[~~]", "__")
}
10 changes: 7 additions & 3 deletions src/platform_impl/macos/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ impl MenuItemAttributes {
}
}
pub fn set_title(&mut self, title: &str) {
let title = super::util::strip_mnemonic(title);
unsafe {
let menu_title = NSString::alloc(nil).init_str(title);
let menu_title = NSString::alloc(nil).init_str(&title);
self.1.setTitle_(menu_title);
}
}
Expand Down Expand Up @@ -160,7 +161,8 @@ impl Menu {

pub fn add_submenu(&mut self, title: &str, enabled: bool, submenu: Menu) {
unsafe {
let menu_title = NSString::alloc(nil).init_str(title);
let title = super::util::strip_mnemonic(title);
let menu_title = NSString::alloc(nil).init_str(&title);
let menu_item = NSMenuItem::alloc(nil).autorelease();
let () = msg_send![submenu.menu, setTitle: menu_title];
let () = msg_send![menu_item, setTitle: menu_title];
Expand Down Expand Up @@ -360,14 +362,16 @@ pub(crate) fn make_custom_menu_item(
accelerators: Option<Accelerator>,
menu_type: MenuType,
) -> *mut Object {
let title = super::util::strip_mnemonic(title);

let alloc = make_menu_alloc();
let menu_id = Box::new(Action(Box::new(id.0)));
let ptr = Box::into_raw(menu_id);

unsafe {
(&mut *alloc).set_ivar(BLOCK_PTR, ptr as usize);
let _: () = msg_send![&*alloc, setTarget:&*alloc];
let title = NSString::alloc(nil).init_str(title);
let title = NSString::alloc(nil).init_str(&title);
make_menu_item_from_alloc(alloc, title, selector, accelerators, menu_type)
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/platform_impl/macos/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,14 @@ pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, o
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}

/// Strips single `&` characters from the string.
///
/// `&` can be escaped as `&&` to prevent stripping, in which case a single `&` will be output.
pub fn strip_mnemonic<S: AsRef<str>>(string: S) -> String {
string
.as_ref()
.replace("&&", "[~~]")
.replace('&', "")
.replace("[~~]", "&")
}