From d97ba8d0d94d33c743537eb22bc5e7950888b4b0 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Wed, 7 Dec 2022 22:17:08 +0200 Subject: [PATCH] fix: fix menu mnemonics --- .changes/fix-mnemonic-on-linux.md | 5 +++++ .changes/macos-strip-mnemonics.md | 5 +++++ src/menu.rs | 25 +++++++++++++++++++++++-- src/platform_impl/linux/menu.rs | 27 +++++++++++++++++++++++---- src/platform_impl/macos/menu.rs | 10 +++++++--- src/platform_impl/macos/util/mod.rs | 11 +++++++++++ 6 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 .changes/fix-mnemonic-on-linux.md create mode 100644 .changes/macos-strip-mnemonics.md diff --git a/.changes/fix-mnemonic-on-linux.md b/.changes/fix-mnemonic-on-linux.md new file mode 100644 index 000000000..df8957d9e --- /dev/null +++ b/.changes/fix-mnemonic-on-linux.md @@ -0,0 +1,5 @@ +--- +"tao": "patch" +--- + +On Linux, fix menu item mnemonics. \ No newline at end of file diff --git a/.changes/macos-strip-mnemonics.md b/.changes/macos-strip-mnemonics.md new file mode 100644 index 000000000..8f4223b89 --- /dev/null +++ b/.changes/macos-strip-mnemonics.md @@ -0,0 +1,5 @@ +--- +"tao": "patch" +--- + +On macOS, strip menu mnemonics for consistency with other platforms. \ No newline at end of file diff --git a/src/menu.rs b/src/menu.rs index a197d83c5..f6f81fc20 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -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), @@ -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 @@ -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 @@ -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 diff --git a/src/platform_impl/linux/menu.rs b/src/platform_impl/linux/menu.rs index bbc054f71..8d16bb80b 100644 --- a/src/platform_impl/linux/menu.rs +++ b/src/platform_impl/linux/menu.rs @@ -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) { @@ -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::() } else { - GtkMenuItem::with_label(title) + GtkMenuItem::with_label(&title) }; let custom_menu = MenuItemAttributes { id: menu_id, @@ -403,3 +405,20 @@ fn modifiers_to_gdk_modifier_type(modifiers: ModifiersState) -> gdk::ModifierTyp result } + +pub fn to_gtk_mnemonic>(string: S) -> String { + string + .as_ref() + .replace("&&", "[~~]") + .replace('&', "_") + .replace("[~~]", "&&") + .replace("[~~]", "&") +} + +pub fn from_gtk_mnemonic>(string: S) -> String { + string + .as_ref() + .replace("__", "[~~]") + .replace('_', "&") + .replace("[~~]", "__") +} diff --git a/src/platform_impl/macos/menu.rs b/src/platform_impl/macos/menu.rs index 50ce4a1ce..3c2709ba7 100644 --- a/src/platform_impl/macos/menu.rs +++ b/src/platform_impl/macos/menu.rs @@ -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); } } @@ -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]; @@ -360,6 +362,8 @@ pub(crate) fn make_custom_menu_item( accelerators: Option, 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); @@ -367,7 +371,7 @@ pub(crate) fn make_custom_menu_item( 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) } } diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 80f554560..d70cf521e 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -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>(string: S) -> String { + string + .as_ref() + .replace("&&", "[~~]") + .replace('&', "") + .replace("[~~]", "&") +}