feat: add set_accelerator (#64)

* feat: add `set_accelerator`

closes #63

* unsafe
This commit is contained in:
Amr Bashir 2023-05-04 14:40:58 +03:00 committed by GitHub
parent dfd7b9e437
commit 47ba0b47ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 212 additions and 40 deletions

View file

@ -0,0 +1,5 @@
---
"muda": "minor"
---
Add `(MenuItem|CheckMenuItem|IconMenuItem)::set_accelerator` to change or disable the accelerator after creation.

View file

@ -189,6 +189,8 @@ fn main() {
if let Ok(event) = menu_channel.try_recv() { if let Ok(event) = menu_channel.try_recv() {
if event.id == custom_i_1.id() { 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); file_m.insert(&MenuItem::new("New Menu Item", true, None), 2);
} }
println!("{event:?}"); println!("{event:?}");

View file

@ -81,4 +81,9 @@ impl CheckMenuItem {
pub fn set_checked(&self, checked: bool) { pub fn set_checked(&self, checked: bool) {
self.0.set_checked(checked) self.0.set_checked(checked)
} }
/// Set this check menu item accelerator.
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.set_accelerator(acccelerator)
}
} }

View file

@ -75,4 +75,9 @@ impl IconMenuItem {
pub fn set_icon(&self, icon: Option<Icon>) { pub fn set_icon(&self, icon: Option<Icon>) {
self.0.set_icon(icon) self.0.set_icon(icon)
} }
/// Set this icon menu item accelerator.
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.set_accelerator(acccelerator)
}
} }

View file

@ -63,4 +63,9 @@ impl MenuItem {
pub fn set_enabled(&self, enabled: bool) { pub fn set_enabled(&self, enabled: bool) {
self.0.set_enabled(enabled) self.0.set_enabled(enabled)
} }
/// Set this menu item accelerator.
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.set_accelerator(acccelerator)
}
} }

View file

@ -99,6 +99,16 @@ pub fn register_accelerator<M: IsA<gtk::Widget>>(
} }
} }
pub fn remove_accelerator<M: IsA<gtk::Widget>>(
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 { fn modifiers_to_gdk_modifier_type(modifiers: Modifiers) -> gdk::ModifierType {
let mut result = gdk::ModifierType::empty(); let mut result = gdk::ModifierType::empty();

View file

@ -14,7 +14,9 @@ use crate::{
util::{AddOp, Counter}, util::{AddOp, Counter},
MenuEvent, MenuItemType, 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 gtk::{prelude::*, Orientation};
use std::{ use std::{
cell::RefCell, cell::RefCell,
@ -180,6 +182,20 @@ impl MenuChild {
} }
} }
} }
fn set_accelerator(&mut self, accelerator: Option<Accelerator>) {
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 { struct InnerMenu {
@ -818,6 +834,8 @@ impl MenuItem {
.sensitive(self_.enabled) .sensitive(self_.enabled)
.build(); .build();
self_.accel_group = accel_group.cloned();
if let Some(accelerator) = &self_.accelerator { if let Some(accelerator) = &self_.accelerator {
if let Some(accel_group) = accel_group { if let Some(accel_group) = accel_group {
register_accelerator(&item, accel_group, accelerator); register_accelerator(&item, accel_group, accelerator);
@ -858,6 +876,10 @@ impl MenuItem {
pub fn set_enabled(&self, enabled: bool) { pub fn set_enabled(&self, enabled: bool) {
self.0.borrow_mut().set_enabled(enabled) self.0.borrow_mut().set_enabled(enabled)
} }
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.borrow_mut().set_accelerator(acccelerator)
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -1033,6 +1055,8 @@ impl CheckMenuItem {
.active(self_.checked) .active(self_.checked)
.build(); .build();
self_.accel_group = accel_group.cloned();
if let Some(accelerator) = &self_.accelerator { if let Some(accelerator) = &self_.accelerator {
if let Some(accel_group) = accel_group { if let Some(accel_group) = accel_group {
register_accelerator(&item, accel_group, accelerator); register_accelerator(&item, accel_group, accelerator);
@ -1111,6 +1135,10 @@ impl CheckMenuItem {
pub fn set_checked(&self, checked: bool) { pub fn set_checked(&self, checked: bool) {
self.0.borrow_mut().set_checked(checked) self.0.borrow_mut().set_checked(checked)
} }
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.borrow_mut().set_accelerator(acccelerator)
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -1152,6 +1180,8 @@ impl IconMenuItem {
.map(|i| gtk::Image::from_pixbuf(Some(&i.inner.to_pixbuf(16, 16)))) .map(|i| gtk::Image::from_pixbuf(Some(&i.inner.to_pixbuf(16, 16))))
.unwrap_or_else(gtk::Image::default); .unwrap_or_else(gtk::Image::default);
self_.accel_group = accel_group.cloned();
let label = gtk::AccelLabel::builder() let label = gtk::AccelLabel::builder()
.label(&to_gtk_mnemonic(&self_.text)) .label(&to_gtk_mnemonic(&self_.text))
.use_underline(true) .use_underline(true)
@ -1223,6 +1253,10 @@ impl IconMenuItem {
pub fn set_icon(&self, icon: Option<Icon>) { pub fn set_icon(&self, icon: Option<Icon>) {
self.0.borrow_mut().set_icon(icon) self.0.borrow_mut().set_icon(icon)
} }
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.borrow_mut().set_accelerator(acccelerator)
}
} }
impl dyn crate::MenuItemExt + '_ { impl dyn crate::MenuItemExt + '_ {

View file

@ -146,6 +146,34 @@ impl MenuChild {
} }
} }
} }
fn set_accelerator(&mut self, accelerator: Option<Accelerator>) {
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)] #[derive(Clone, Debug)]
@ -556,6 +584,10 @@ impl MenuItem {
pub fn set_enabled(&self, enabled: bool) { pub fn set_enabled(&self, enabled: bool) {
self.0.borrow_mut().set_enabled(enabled) self.0.borrow_mut().set_enabled(enabled)
} }
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.borrow_mut().set_accelerator(acccelerator)
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -719,6 +751,10 @@ impl CheckMenuItem {
pub fn set_checked(&self, checked: bool) { pub fn set_checked(&self, checked: bool) {
self.0.borrow_mut().set_checked(checked) self.0.borrow_mut().set_checked(checked)
} }
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.borrow_mut().set_accelerator(acccelerator)
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -798,6 +834,10 @@ impl IconMenuItem {
pub fn set_icon(&self, icon: Option<Icon>) { pub fn set_icon(&self, icon: Option<Icon>) {
self.0.borrow_mut().set_icon(icon) self.0.borrow_mut().set_icon(icon)
} }
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.borrow_mut().set_accelerator(acccelerator)
}
} }
impl PredfinedMenuItemType { impl PredfinedMenuItemType {

View file

@ -15,7 +15,12 @@ use crate::{
util::{AddOp, Counter}, util::{AddOp, Counter},
MenuEvent, MenuItemType, 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 util::{decode_wide, encode_wide, Accel};
use windows_sys::Win32::{ use windows_sys::Win32::{
Foundation::{HWND, LPARAM, LRESULT, POINT, WPARAM}, Foundation::{HWND, LPARAM, LRESULT, POINT, WPARAM},
@ -39,7 +44,7 @@ use windows_sys::Win32::{
const COUNTER_START: u32 = 1000; const COUNTER_START: u32 = 1000;
static COUNTER: Counter = Counter::new_with_start(COUNTER_START); static COUNTER: Counter = Counter::new_with_start(COUNTER_START);
type AccelWrapper = (HACCEL, Vec<Accel>); type AccelWrapper = (HACCEL, HashMap<u32, Accel>);
/// A generic child in a menu /// A generic child in a menu
/// ///
@ -51,6 +56,7 @@ struct MenuChild {
text: String, text: String,
enabled: bool, enabled: bool,
parents_hemnu: Vec<HMENU>, parents_hemnu: Vec<HMENU>,
root_menu_haccel_stores: Option<Vec<Rc<RefCell<AccelWrapper>>>>,
// menu item fields // menu item fields
id: u32, id: u32,
@ -69,7 +75,6 @@ struct MenuChild {
hmenu: HMENU, hmenu: HMENU,
hpopupmenu: HMENU, hpopupmenu: HMENU,
children: Option<Vec<Rc<RefCell<MenuChild>>>>, children: Option<Vec<Rc<RefCell<MenuChild>>>>,
root_menu_haccel: Option<Vec<Rc<RefCell<AccelWrapper>>>>,
} }
impl MenuChild { impl MenuChild {
@ -104,12 +109,16 @@ impl MenuChild {
} }
fn set_text(&mut self, text: &str) { 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 { for parent in &self.parents_hemnu {
let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _; info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _;
info.fMask = MIIM_STRING; 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) }; unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) };
} }
@ -182,6 +191,21 @@ impl MenuChild {
unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) }; unsafe { SetMenuItemInfoW(*parent, self.id(), false.into(), &info) };
} }
} }
fn set_accelerator(&mut self, accelerator: Option<Accelerator>) {
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)] #[derive(Clone)]
@ -189,7 +213,7 @@ pub(crate) struct Menu {
hmenu: HMENU, hmenu: HMENU,
hpopupmenu: HMENU, hpopupmenu: HMENU,
hwnds: Rc<RefCell<Vec<HWND>>>, hwnds: Rc<RefCell<Vec<HWND>>>,
haccel: Rc<RefCell<(HACCEL, Vec<Accel>)>>, haccel_store: Rc<RefCell<AccelWrapper>>,
children: Rc<RefCell<Vec<Rc<RefCell<MenuChild>>>>>, children: Rc<RefCell<Vec<Rc<RefCell<MenuChild>>>>>,
} }
@ -198,7 +222,7 @@ impl Menu {
Self { Self {
hmenu: unsafe { CreateMenu() }, hmenu: unsafe { CreateMenu() },
hpopupmenu: unsafe { CreatePopupMenu() }, 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())), children: Rc::new(RefCell::new(Vec::new())),
hwnds: Rc::new(RefCell::new(Vec::new())), hwnds: Rc::new(RefCell::new(Vec::new())),
} }
@ -212,12 +236,6 @@ impl Menu {
let child = &submenu.0 .0; let child = &submenu.0 .0;
flags |= MF_POPUP; flags |= MF_POPUP;
child
.borrow_mut()
.root_menu_haccel
.as_mut()
.unwrap()
.push(self.haccel.clone());
child child
} }
@ -275,6 +293,15 @@ impl Menu {
} }
.clone(); .clone();
{
child
.borrow_mut()
.root_menu_haccel_stores
.as_mut()
.unwrap()
.push(self.haccel_store.clone());
}
{ {
let child_ = child.borrow(); let child_ = child.borrow();
if !child_.enabled { if !child_.enabled {
@ -285,15 +312,12 @@ impl Menu {
if let Some(accelerator) = &child_.accelerator { if let Some(accelerator) = &child_.accelerator {
let accel_str = accelerator.to_string(); let accel_str = accelerator.to_string();
let accel = accelerator.to_accel(child_.id() as u16);
text.push('\t'); text.push('\t');
text.push_str(&accel_str); text.push_str(&accel_str);
let mut haccel = self.haccel.borrow_mut(); let mut haccel_store = self.haccel_store.borrow_mut();
haccel.1.push(Accel(accel)); AccelAction::add(&mut haccel_store, child_.id(), accelerator);
let accels = haccel.1.clone();
update_haccel(&mut haccel.0, accels)
} }
let id = child_.id() as usize; let id = child_.id() as usize;
@ -461,7 +485,7 @@ impl Menu {
} }
pub fn haccel(&self) -> HACCEL { pub fn haccel(&self) -> HACCEL {
self.haccel.borrow().0 self.haccel_store.borrow().0
} }
pub fn hpopupmenu(&self) -> HMENU { pub fn hpopupmenu(&self) -> HMENU {
@ -567,7 +591,7 @@ impl Submenu {
children: Some(Vec::new()), children: Some(Vec::new()),
hmenu: unsafe { CreateMenu() }, hmenu: unsafe { CreateMenu() },
hpopupmenu: unsafe { CreatePopupMenu() }, hpopupmenu: unsafe { CreatePopupMenu() },
root_menu_haccel: Some(Vec::new()), root_menu_haccel_stores: Some(Vec::new()),
..Default::default() ..Default::default()
}))) })))
} }
@ -589,13 +613,6 @@ impl Submenu {
flags |= MF_POPUP; 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 child
} }
MenuItemType::Normal => { MenuItemType::Normal => {
@ -653,6 +670,21 @@ impl Submenu {
} }
.clone(); .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(); let mut self_ = self.0.borrow_mut();
@ -665,16 +697,13 @@ impl Submenu {
if let Some(accelerator) = &child_.accelerator { if let Some(accelerator) = &child_.accelerator {
let accel_str = accelerator.to_string(); let accel_str = accelerator.to_string();
let accel = accelerator.to_accel(child_.id() as u16);
text.push('\t'); text.push('\t');
text.push_str(&accel_str); 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(); let mut haccel = root_menu.borrow_mut();
haccel.1.push(Accel(accel)); AccelAction::add(&mut haccel, child_.id(), accelerator);
let accels = haccel.1.clone();
update_haccel(&mut haccel.0, accels)
} }
} }
@ -895,6 +924,7 @@ impl MenuItem {
parents_hemnu: Vec::new(), parents_hemnu: Vec::new(),
id: COUNTER.next(), id: COUNTER.next(),
accelerator, accelerator,
root_menu_haccel_stores: Some(Vec::new()),
..Default::default() ..Default::default()
}))) })))
} }
@ -918,6 +948,10 @@ impl MenuItem {
pub fn set_enabled(&self, enabled: bool) { pub fn set_enabled(&self, enabled: bool) {
self.0.borrow_mut().set_enabled(enabled) self.0.borrow_mut().set_enabled(enabled)
} }
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.borrow_mut().set_accelerator(acccelerator)
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -933,6 +967,7 @@ impl PredefinedMenuItem {
id: COUNTER.next(), id: COUNTER.next(),
accelerator: item_type.accelerator(), accelerator: item_type.accelerator(),
predefined_item_type: item_type, predefined_item_type: item_type,
root_menu_haccel_stores: Some(Vec::new()),
..Default::default() ..Default::default()
}))) })))
} }
@ -963,6 +998,7 @@ impl CheckMenuItem {
id: COUNTER.next(), id: COUNTER.next(),
accelerator, accelerator,
checked, checked,
root_menu_haccel_stores: Some(Vec::new()),
..Default::default() ..Default::default()
}))) })))
} }
@ -994,6 +1030,10 @@ impl CheckMenuItem {
pub fn set_checked(&self, checked: bool) { pub fn set_checked(&self, checked: bool) {
self.0.borrow_mut().set_checked(checked) self.0.borrow_mut().set_checked(checked)
} }
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.borrow_mut().set_accelerator(acccelerator)
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -1014,6 +1054,7 @@ impl IconMenuItem {
id: COUNTER.next(), id: COUNTER.next(),
accelerator, accelerator,
icon, icon,
root_menu_haccel_stores: Some(Vec::new()),
..Default::default() ..Default::default()
}))) })))
} }
@ -1041,6 +1082,10 @@ impl IconMenuItem {
pub fn set_icon(&self, icon: Option<Icon>) { pub fn set_icon(&self, icon: Option<Icon>) {
self.0.borrow_mut().set_icon(icon) self.0.borrow_mut().set_icon(icon)
} }
pub fn set_accelerator(&self, acccelerator: Option<Accelerator>) {
self.0.borrow_mut().set_accelerator(acccelerator)
}
} }
const MENU_SUBCLASS_ID: usize = 200; 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<Accel>) { struct AccelAction;
unsafe {
DestroyAcceleratorTable(*haccel); impl AccelAction {
*haccel = CreateAcceleratorTableW( fn add(haccel_store: &mut RefMut<AccelWrapper>, id: u32, accelerator: &Accelerator) {
accels.iter().map(|i| i.0).collect::<Vec<_>>().as_ptr(), let accel = accelerator.to_accel(id as _);
accels.len() as _, haccel_store.1.insert(id, Accel(accel));
);
Self::update_store(haccel_store)
}
fn remove(haccel_store: &mut RefMut<AccelWrapper>, id: u32) {
haccel_store.1.remove(&id);
Self::update_store(haccel_store)
}
fn update_store(haccel_store: &mut RefMut<AccelWrapper>) {
unsafe {
DestroyAcceleratorTable(haccel_store.0);
haccel_store.0 = CreateAcceleratorTableW(
haccel_store
.1
.values()
.map(|i| i.0)
.collect::<Vec<_>>()
.as_ptr(),
haccel_store.1.len() as _,
);
}
} }
} }