From 47ba0b47ed28a93428c253e8bac397e0b9cb8dec Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 4 May 2023 14:40:58 +0300 Subject: [PATCH] feat: add `set_accelerator` (#64) * feat: add `set_accelerator` closes #63 * unsafe --- .changes/set-accelerator.md | 5 + examples/tao.rs | 2 + src/check_menu_item.rs | 5 + src/icon_menu_item.rs | 5 + src/menu_item.rs | 5 + src/platform_impl/gtk/accelerator.rs | 10 ++ src/platform_impl/gtk/mod.rs | 36 ++++++- src/platform_impl/macos/mod.rs | 40 ++++++++ src/platform_impl/windows/mod.rs | 144 +++++++++++++++++++-------- 9 files changed, 212 insertions(+), 40 deletions(-) create mode 100644 .changes/set-accelerator.md diff --git a/.changes/set-accelerator.md b/.changes/set-accelerator.md new file mode 100644 index 0000000..426e7d5 --- /dev/null +++ b/.changes/set-accelerator.md @@ -0,0 +1,5 @@ +--- +"muda": "minor" +--- + +Add `(MenuItem|CheckMenuItem|IconMenuItem)::set_accelerator` to change or disable the accelerator after creation. diff --git a/examples/tao.rs b/examples/tao.rs index 60ca3fc..a395d4b 100644 --- a/examples/tao.rs +++ b/examples/tao.rs @@ -189,6 +189,8 @@ fn main() { if let Ok(event) = menu_channel.try_recv() { if event.id == custom_i_1.id() { + custom_i_1 + .set_accelerator(Some(Accelerator::new(Some(Modifiers::SHIFT), Code::KeyF))); file_m.insert(&MenuItem::new("New Menu Item", true, None), 2); } println!("{event:?}"); diff --git a/src/check_menu_item.rs b/src/check_menu_item.rs index e7c9f92..5c27c76 100644 --- a/src/check_menu_item.rs +++ b/src/check_menu_item.rs @@ -81,4 +81,9 @@ impl CheckMenuItem { pub fn set_checked(&self, checked: bool) { self.0.set_checked(checked) } + + /// Set this check menu item accelerator. + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.set_accelerator(acccelerator) + } } diff --git a/src/icon_menu_item.rs b/src/icon_menu_item.rs index cd426c7..827bc4d 100644 --- a/src/icon_menu_item.rs +++ b/src/icon_menu_item.rs @@ -75,4 +75,9 @@ impl IconMenuItem { pub fn set_icon(&self, icon: Option) { self.0.set_icon(icon) } + + /// Set this icon menu item accelerator. + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.set_accelerator(acccelerator) + } } diff --git a/src/menu_item.rs b/src/menu_item.rs index 800ca39..776c00b 100644 --- a/src/menu_item.rs +++ b/src/menu_item.rs @@ -63,4 +63,9 @@ impl MenuItem { pub fn set_enabled(&self, enabled: bool) { self.0.set_enabled(enabled) } + + /// Set this menu item accelerator. + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.set_accelerator(acccelerator) + } } diff --git a/src/platform_impl/gtk/accelerator.rs b/src/platform_impl/gtk/accelerator.rs index 28901bb..db4c294 100644 --- a/src/platform_impl/gtk/accelerator.rs +++ b/src/platform_impl/gtk/accelerator.rs @@ -99,6 +99,16 @@ pub fn register_accelerator>( } } +pub fn remove_accelerator>( + item: &M, + accel_group: &AccelGroup, + accelerator: &Accelerator, +) { + if let Ok((mods, key)) = parse_accelerator(accelerator) { + item.remove_accelerator(accel_group, key, mods); + } +} + fn modifiers_to_gdk_modifier_type(modifiers: Modifiers) -> gdk::ModifierType { let mut result = gdk::ModifierType::empty(); diff --git a/src/platform_impl/gtk/mod.rs b/src/platform_impl/gtk/mod.rs index 1d73d6c..c670ff0 100644 --- a/src/platform_impl/gtk/mod.rs +++ b/src/platform_impl/gtk/mod.rs @@ -14,7 +14,9 @@ use crate::{ util::{AddOp, Counter}, MenuEvent, MenuItemType, }; -use accelerator::{from_gtk_mnemonic, parse_accelerator, register_accelerator, to_gtk_mnemonic}; +use accelerator::{ + from_gtk_mnemonic, parse_accelerator, register_accelerator, remove_accelerator, to_gtk_mnemonic, +}; use gtk::{prelude::*, Orientation}; use std::{ cell::RefCell, @@ -180,6 +182,20 @@ impl MenuChild { } } } + + fn set_accelerator(&mut self, accelerator: Option) { + for items in self.gtk_menu_items.values() { + for i in items { + if let Some(accel) = self.accelerator { + remove_accelerator(i, self.accel_group.as_ref().unwrap(), &accel); + } + if let Some(accel) = accelerator.as_ref() { + register_accelerator(i, self.accel_group.as_ref().unwrap(), accel); + } + } + } + self.accelerator = accelerator; + } } struct InnerMenu { @@ -818,6 +834,8 @@ impl MenuItem { .sensitive(self_.enabled) .build(); + self_.accel_group = accel_group.cloned(); + if let Some(accelerator) = &self_.accelerator { if let Some(accel_group) = accel_group { register_accelerator(&item, accel_group, accelerator); @@ -858,6 +876,10 @@ impl MenuItem { pub fn set_enabled(&self, enabled: bool) { self.0.borrow_mut().set_enabled(enabled) } + + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.borrow_mut().set_accelerator(acccelerator) + } } #[derive(Clone)] @@ -1033,6 +1055,8 @@ impl CheckMenuItem { .active(self_.checked) .build(); + self_.accel_group = accel_group.cloned(); + if let Some(accelerator) = &self_.accelerator { if let Some(accel_group) = accel_group { register_accelerator(&item, accel_group, accelerator); @@ -1111,6 +1135,10 @@ impl CheckMenuItem { pub fn set_checked(&self, checked: bool) { self.0.borrow_mut().set_checked(checked) } + + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.borrow_mut().set_accelerator(acccelerator) + } } #[derive(Clone)] @@ -1152,6 +1180,8 @@ impl IconMenuItem { .map(|i| gtk::Image::from_pixbuf(Some(&i.inner.to_pixbuf(16, 16)))) .unwrap_or_else(gtk::Image::default); + self_.accel_group = accel_group.cloned(); + let label = gtk::AccelLabel::builder() .label(&to_gtk_mnemonic(&self_.text)) .use_underline(true) @@ -1223,6 +1253,10 @@ impl IconMenuItem { pub fn set_icon(&self, icon: Option) { self.0.borrow_mut().set_icon(icon) } + + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.borrow_mut().set_accelerator(acccelerator) + } } impl dyn crate::MenuItemExt + '_ { diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 7db9cdd..5a531bb 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -146,6 +146,34 @@ impl MenuChild { } } } + + fn set_accelerator(&mut self, accelerator: Option) { + let key_equivalent = (accelerator) + .as_ref() + .map(|accel| accel.key_equivalent()) + .unwrap_or_default(); + let key_equivalent = unsafe { + NSString::alloc(nil) + .init_str(key_equivalent.as_str()) + .autorelease() + }; + + let modifier_mask = (accelerator) + .as_ref() + .map(|accel| accel.key_modifier_mask()) + .unwrap_or_else(NSEventModifierFlags::empty); + + for ns_items in self.ns_menu_items.values() { + for &ns_item in ns_items { + unsafe { + let _: () = msg_send![ns_item, setKeyEquivalent: key_equivalent]; + ns_item.setKeyEquivalentModifierMask_(modifier_mask); + } + } + } + + self.accelerator = accelerator; + } } #[derive(Clone, Debug)] @@ -556,6 +584,10 @@ impl MenuItem { pub fn set_enabled(&self, enabled: bool) { self.0.borrow_mut().set_enabled(enabled) } + + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.borrow_mut().set_accelerator(acccelerator) + } } #[derive(Clone, Debug)] @@ -719,6 +751,10 @@ impl CheckMenuItem { pub fn set_checked(&self, checked: bool) { self.0.borrow_mut().set_checked(checked) } + + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.borrow_mut().set_accelerator(acccelerator) + } } #[derive(Clone, Debug)] @@ -798,6 +834,10 @@ impl IconMenuItem { pub fn set_icon(&self, icon: Option) { self.0.borrow_mut().set_icon(icon) } + + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.borrow_mut().set_accelerator(acccelerator) + } } impl PredfinedMenuItemType { diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 3878a5c..2575021 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -15,7 +15,12 @@ use crate::{ util::{AddOp, Counter}, MenuEvent, MenuItemType, }; -use std::{cell::RefCell, fmt::Debug, rc::Rc}; +use std::{ + cell::{RefCell, RefMut}, + collections::HashMap, + fmt::Debug, + rc::Rc, +}; use util::{decode_wide, encode_wide, Accel}; use windows_sys::Win32::{ Foundation::{HWND, LPARAM, LRESULT, POINT, WPARAM}, @@ -39,7 +44,7 @@ use windows_sys::Win32::{ const COUNTER_START: u32 = 1000; static COUNTER: Counter = Counter::new_with_start(COUNTER_START); -type AccelWrapper = (HACCEL, Vec); +type AccelWrapper = (HACCEL, HashMap); /// A generic child in a menu /// @@ -51,6 +56,7 @@ struct MenuChild { text: String, enabled: bool, parents_hemnu: Vec, + root_menu_haccel_stores: Option>>>, // menu item fields id: u32, @@ -69,7 +75,6 @@ struct MenuChild { hmenu: HMENU, hpopupmenu: HMENU, children: Option>>>, - root_menu_haccel: Option>>>, } impl MenuChild { @@ -104,12 +109,16 @@ impl MenuChild { } fn set_text(&mut self, text: &str) { - self.text = text.to_string(); + self.text = if let Some(accelerator) = self.accelerator { + format!("{text}\t{}", accelerator) + } else { + text.to_string() + }; for parent in &self.parents_hemnu { let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; info.cbSize = std::mem::size_of::() as _; info.fMask = MIIM_STRING; - info.dwTypeData = encode_wide(text).as_mut_ptr(); + info.dwTypeData = encode_wide(&self.text).as_mut_ptr(); unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) }; } @@ -182,6 +191,21 @@ impl MenuChild { unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) }; } } + + fn set_accelerator(&mut self, accelerator: Option) { + self.accelerator = accelerator; + self.set_text(&self.text.clone()); + + let haccel_stores = self.root_menu_haccel_stores.as_mut().unwrap(); + for store in haccel_stores { + let mut store = store.borrow_mut(); + if let Some(accelerator) = self.accelerator { + AccelAction::add(&mut store, self.id, &accelerator) + } else { + AccelAction::remove(&mut store, self.id) + } + } + } } #[derive(Clone)] @@ -189,7 +213,7 @@ pub(crate) struct Menu { hmenu: HMENU, hpopupmenu: HMENU, hwnds: Rc>>, - haccel: Rc)>>, + haccel_store: Rc>, children: Rc>>>>, } @@ -198,7 +222,7 @@ impl Menu { Self { hmenu: unsafe { CreateMenu() }, hpopupmenu: unsafe { CreatePopupMenu() }, - haccel: Rc::new(RefCell::new((0, Vec::new()))), + haccel_store: Rc::new(RefCell::new((0, HashMap::new()))), children: Rc::new(RefCell::new(Vec::new())), hwnds: Rc::new(RefCell::new(Vec::new())), } @@ -212,12 +236,6 @@ impl Menu { let child = &submenu.0 .0; flags |= MF_POPUP; - child - .borrow_mut() - .root_menu_haccel - .as_mut() - .unwrap() - .push(self.haccel.clone()); child } @@ -275,6 +293,15 @@ impl Menu { } .clone(); + { + child + .borrow_mut() + .root_menu_haccel_stores + .as_mut() + .unwrap() + .push(self.haccel_store.clone()); + } + { let child_ = child.borrow(); if !child_.enabled { @@ -285,15 +312,12 @@ impl Menu { if let Some(accelerator) = &child_.accelerator { let accel_str = accelerator.to_string(); - let accel = accelerator.to_accel(child_.id() as u16); text.push('\t'); text.push_str(&accel_str); - let mut haccel = self.haccel.borrow_mut(); - haccel.1.push(Accel(accel)); - let accels = haccel.1.clone(); - update_haccel(&mut haccel.0, accels) + let mut haccel_store = self.haccel_store.borrow_mut(); + AccelAction::add(&mut haccel_store, child_.id(), accelerator); } let id = child_.id() as usize; @@ -461,7 +485,7 @@ impl Menu { } pub fn haccel(&self) -> HACCEL { - self.haccel.borrow().0 + self.haccel_store.borrow().0 } pub fn hpopupmenu(&self) -> HMENU { @@ -567,7 +591,7 @@ impl Submenu { children: Some(Vec::new()), hmenu: unsafe { CreateMenu() }, hpopupmenu: unsafe { CreatePopupMenu() }, - root_menu_haccel: Some(Vec::new()), + root_menu_haccel_stores: Some(Vec::new()), ..Default::default() }))) } @@ -589,13 +613,6 @@ impl Submenu { flags |= MF_POPUP; - child - .borrow_mut() - .root_menu_haccel - .as_mut() - .unwrap() - .extend_from_slice(self.0.borrow_mut().root_menu_haccel.as_ref().unwrap()); - child } MenuItemType::Normal => { @@ -653,6 +670,21 @@ impl Submenu { } .clone(); + { + child + .borrow_mut() + .root_menu_haccel_stores + .as_mut() + .unwrap() + .extend_from_slice( + self.0 + .borrow_mut() + .root_menu_haccel_stores + .as_ref() + .unwrap(), + ); + } + { let mut self_ = self.0.borrow_mut(); @@ -665,16 +697,13 @@ impl Submenu { if let Some(accelerator) = &child_.accelerator { let accel_str = accelerator.to_string(); - let accel = accelerator.to_accel(child_.id() as u16); text.push('\t'); text.push_str(&accel_str); - for root_menu in self_.root_menu_haccel.as_mut().unwrap() { + for root_menu in self_.root_menu_haccel_stores.as_mut().unwrap() { let mut haccel = root_menu.borrow_mut(); - haccel.1.push(Accel(accel)); - let accels = haccel.1.clone(); - update_haccel(&mut haccel.0, accels) + AccelAction::add(&mut haccel, child_.id(), accelerator); } } @@ -895,6 +924,7 @@ impl MenuItem { parents_hemnu: Vec::new(), id: COUNTER.next(), accelerator, + root_menu_haccel_stores: Some(Vec::new()), ..Default::default() }))) } @@ -918,6 +948,10 @@ impl MenuItem { pub fn set_enabled(&self, enabled: bool) { self.0.borrow_mut().set_enabled(enabled) } + + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.borrow_mut().set_accelerator(acccelerator) + } } #[derive(Clone, Debug)] @@ -933,6 +967,7 @@ impl PredefinedMenuItem { id: COUNTER.next(), accelerator: item_type.accelerator(), predefined_item_type: item_type, + root_menu_haccel_stores: Some(Vec::new()), ..Default::default() }))) } @@ -963,6 +998,7 @@ impl CheckMenuItem { id: COUNTER.next(), accelerator, checked, + root_menu_haccel_stores: Some(Vec::new()), ..Default::default() }))) } @@ -994,6 +1030,10 @@ impl CheckMenuItem { pub fn set_checked(&self, checked: bool) { self.0.borrow_mut().set_checked(checked) } + + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.borrow_mut().set_accelerator(acccelerator) + } } #[derive(Clone, Debug)] @@ -1014,6 +1054,7 @@ impl IconMenuItem { id: COUNTER.next(), accelerator, icon, + root_menu_haccel_stores: Some(Vec::new()), ..Default::default() }))) } @@ -1041,6 +1082,10 @@ impl IconMenuItem { pub fn set_icon(&self, icon: Option) { self.0.borrow_mut().set_icon(icon) } + + pub fn set_accelerator(&self, acccelerator: Option) { + self.0.borrow_mut().set_accelerator(acccelerator) + } } const MENU_SUBCLASS_ID: usize = 200; @@ -1183,13 +1228,34 @@ unsafe extern "system" fn menu_subclass_proc( } } -fn update_haccel(haccel: &mut HMENU, accels: Vec) { - unsafe { - DestroyAcceleratorTable(*haccel); - *haccel = CreateAcceleratorTableW( - accels.iter().map(|i| i.0).collect::>().as_ptr(), - accels.len() as _, - ); +struct AccelAction; + +impl AccelAction { + fn add(haccel_store: &mut RefMut, id: u32, accelerator: &Accelerator) { + let accel = accelerator.to_accel(id as _); + haccel_store.1.insert(id, Accel(accel)); + + Self::update_store(haccel_store) + } + fn remove(haccel_store: &mut RefMut, id: u32) { + haccel_store.1.remove(&id); + + Self::update_store(haccel_store) + } + + fn update_store(haccel_store: &mut RefMut) { + unsafe { + DestroyAcceleratorTable(haccel_store.0); + haccel_store.0 = CreateAcceleratorTableW( + haccel_store + .1 + .values() + .map(|i| i.0) + .collect::>() + .as_ptr(), + haccel_store.1.len() as _, + ); + } } }