refactor: impl drop for inner types (#92)

* refactor: impl drop for inner types

* macos

* impl todos in macos .remove()

* remove code

* fix gtk accelerator not working after creating the context menu
This commit is contained in:
Amr Bashir 2023-08-08 19:32:27 +03:00 committed by GitHub
parent 043026c30d
commit 662e17d0ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 710 additions and 360 deletions

5
.changes/drop.md Normal file
View file

@ -0,0 +1,5 @@
---
"muda": "minor"
---
Add `Drop` implementation for the inner types to release memory and OS resources.

View file

@ -0,0 +1,5 @@
---
"muda": "patch"
---
On Windows, fix `.set_text()` sometimes adding gebberish characters after multiple calls.

View file

@ -15,7 +15,7 @@ use crate::{
IsMenuItem, MenuEvent, MenuId, MenuItemKind, MenuItemType, Position, IsMenuItem, MenuEvent, MenuId, MenuItemKind, MenuItemType, Position,
}; };
use accelerator::{from_gtk_mnemonic, parse_accelerator, to_gtk_mnemonic}; use accelerator::{from_gtk_mnemonic, parse_accelerator, to_gtk_mnemonic};
use gtk::{prelude::*, Orientation}; use gtk::{prelude::*, Container, Orientation};
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::HashMap, collections::HashMap,
@ -25,41 +25,59 @@ use std::{
static COUNTER: Counter = Counter::new(); static COUNTER: Counter = Counter::new();
macro_rules! return_if_predefined_item_not_supported { macro_rules! is_item_supported {
($item:tt) => { ($item:tt) => {{
let child = $item.child(); let child = $item.child();
let child_ = child.borrow(); let child_ = child.borrow();
match (&child_.item_type, &child_.predefined_item_type) { let supported = if let Some(predefined_item_type) = &child_.predefined_item_type {
( matches!(
MenuItemType::Predefined, predefined_item_type,
PredefinedMenuItemType::Separator PredefinedMenuItemType::Separator
| PredefinedMenuItemType::Copy | PredefinedMenuItemType::Copy
| PredefinedMenuItemType::Cut | PredefinedMenuItemType::Cut
| PredefinedMenuItemType::Paste | PredefinedMenuItemType::Paste
| PredefinedMenuItemType::SelectAll | PredefinedMenuItemType::SelectAll
| PredefinedMenuItemType::About(_), | PredefinedMenuItemType::About(_)
) => {} )
( } else {
MenuItemType::Submenu true
| MenuItemType::MenuItem };
| MenuItemType::Check
| MenuItemType::Icon,
_,
) => {}
_ => return Ok(()),
}
drop(child_); drop(child_);
supported
}};
}
macro_rules! return_if_item_not_supported {
($item:tt) => {
if !is_item_supported!($item) {
return Ok(());
}
}; };
} }
pub struct Menu { pub struct Menu {
id: MenuId, id: MenuId,
children: Vec<Rc<RefCell<MenuChild>>>, children: Vec<Rc<RefCell<MenuChild>>>,
// TODO: maybe save a reference to the window?
gtk_menubars: HashMap<u32, gtk::MenuBar>, gtk_menubars: HashMap<u32, gtk::MenuBar>,
accel_group: Option<gtk::AccelGroup>, accel_group: Option<gtk::AccelGroup>,
gtk_menu: (u32, Option<gtk::Menu>), // dedicated menu for tray or context menus gtk_menu: (u32, Option<gtk::Menu>), // dedicated menu for tray or context menus
} }
impl Drop for Menu {
fn drop(&mut self) {
for (id, menu) in &self.gtk_menubars {
drop_children_from_menu_and_destroy(*id, menu, &self.children);
unsafe { menu.destroy() }
}
if let (id, Some(menu)) = &self.gtk_menu {
drop_children_from_menu_and_destroy(*id, menu, &self.children);
unsafe { menu.destroy() }
}
}
}
impl Menu { impl Menu {
pub fn new(id: Option<MenuId>) -> Self { pub fn new(id: Option<MenuId>) -> Self {
Self { Self {
@ -76,28 +94,28 @@ impl Menu {
} }
pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> { pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> {
return_if_predefined_item_not_supported!(item); if is_item_supported!(item) {
for (menu_id, menu_bar) in &self.gtk_menubars {
for (menu_id, menu_bar) in &self.gtk_menubars {
let gtk_item = item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?;
match op {
AddOp::Append => menu_bar.append(&gtk_item),
AddOp::Insert(position) => menu_bar.insert(&gtk_item, position as i32),
}
gtk_item.show();
}
{
let (menu_id, menu) = &self.gtk_menu;
if let Some(menu) = menu {
let gtk_item = let gtk_item =
item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?;
match op { match op {
AddOp::Append => menu.append(&gtk_item), AddOp::Append => menu_bar.append(&gtk_item),
AddOp::Insert(position) => menu.insert(&gtk_item, position as i32), AddOp::Insert(position) => menu_bar.insert(&gtk_item, position as i32),
} }
gtk_item.show(); gtk_item.show();
} }
{
if let (menu_id, Some(menu)) = &self.gtk_menu {
let gtk_item =
item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?;
match op {
AddOp::Append => menu.append(&gtk_item),
AddOp::Insert(position) => menu.insert(&gtk_item, position as i32),
}
gtk_item.show();
}
}
} }
match op { match op {
@ -109,7 +127,7 @@ impl Menu {
} }
fn add_menu_item_with_id(&self, item: &dyn crate::IsMenuItem, id: u32) -> crate::Result<()> { fn add_menu_item_with_id(&self, item: &dyn crate::IsMenuItem, id: u32) -> crate::Result<()> {
return_if_predefined_item_not_supported!(item); return_if_item_not_supported!(item);
for (menu_id, menu_bar) in self.gtk_menubars.iter().filter(|m| *m.0 == id) { for (menu_id, menu_bar) in self.gtk_menubars.iter().filter(|m| *m.0 == id) {
let gtk_item = item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; let gtk_item = item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?;
@ -121,7 +139,7 @@ impl Menu {
} }
fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> {
return_if_predefined_item_not_supported!(item); return_if_item_not_supported!(item);
let (menu_id, menu) = &self.gtk_menu; let (menu_id, menu) = &self.gtk_menu;
if let Some(menu) = menu { if let Some(menu) = menu {
@ -137,12 +155,13 @@ impl Menu {
self.remove_inner(item, true, None) self.remove_inner(item, true, None)
} }
pub fn remove_inner( fn remove_inner(
&mut self, &mut self,
item: &dyn crate::IsMenuItem, item: &dyn crate::IsMenuItem,
remove_from_cache: bool, remove_from_cache: bool,
id: Option<u32>, id: Option<u32>,
) -> crate::Result<()> { ) -> crate::Result<()> {
// get child
let child = { let child = {
let index = self let index = self
.children .children
@ -156,46 +175,64 @@ impl Menu {
} }
}; };
if let MenuItemKind::Submenu(i) = item.kind() {
let gtk_menus = i.inner.borrow().gtk_menus.clone();
for (menu_id, _) in gtk_menus {
for item in i.items() {
i.inner
.borrow_mut()
.remove_inner(item.as_ref(), false, Some(menu_id))?;
}
}
}
for (menu_id, menu_bar) in &self.gtk_menubars { for (menu_id, menu_bar) in &self.gtk_menubars {
// check if we are removing this item from all gtk_menubars
// which is usually when this is the item the user is actaully removing
// or if we are removing from a specific menu (id)
// which is when the actual item being removed is a submenu
// and we are iterating through its children and removing
// each child gtk items that are related to this submenu.
if id.map(|i| i == *menu_id).unwrap_or(true) { if id.map(|i| i == *menu_id).unwrap_or(true) {
if let Some(items) = child // bail this is not a supported item like a close_window predefined menu item
.borrow_mut() if is_item_supported!(item) {
.gtk_menu_items let mut child_ = child.borrow_mut();
.borrow_mut()
.remove(menu_id) if child_.item_type == MenuItemType::Submenu {
{ let menus = child_.gtk_menus.as_ref().unwrap().get(menu_id).cloned();
for item in items { if let Some(menus) = menus {
menu_bar.remove(&item); for (id, menu) in menus {
// iterate through children and only remove the gtk items
// related to this submenu
for item in child_.items() {
child_.remove_inner(item.as_ref(), false, Some(id))?;
}
unsafe { menu.destroy() };
}
}
child_.gtk_menus.as_mut().unwrap().remove(menu_id);
} }
// remove all the gtk items that are related to this gtk menubar and destroy it
if let Some(items) = child_.gtk_menu_items.borrow_mut().remove(menu_id) {
for item in items {
menu_bar.remove(&item);
if let Some(accel_group) = &child_.accel_group {
if let Some((mods, key)) = child_.gtk_accelerator {
item.remove_accelerator(accel_group, key, mods);
}
}
unsafe { item.destroy() };
}
};
} }
} }
} }
// remove from the gtk menu assigned to the context menu
if remove_from_cache { if remove_from_cache {
let (menu_id, menu) = &self.gtk_menu; if let (id, Some(menu)) = &self.gtk_menu {
if let Some(menu) = menu { let child_ = child.borrow_mut();
if let Some(items) = child if let Some(items) = child_.gtk_menu_items.borrow_mut().remove(id) {
.borrow_mut()
.gtk_menu_items
.borrow_mut()
.remove(menu_id)
{
for item in items { for item in items {
menu.remove(&item); menu.remove(&item);
if let Some(accel_group) = &child_.accel_group {
if let Some((mods, key)) = child_.gtk_accelerator {
item.remove_accelerator(accel_group, key, mods);
}
}
unsafe { item.destroy() };
} }
} };
} }
} }
Ok(()) Ok(())
@ -365,22 +402,85 @@ pub struct MenuChild {
gtk_accelerator: Option<(gdk::ModifierType, u32)>, gtk_accelerator: Option<(gdk::ModifierType, u32)>,
// predefined menu item fields // predefined menu item fields
predefined_item_type: PredefinedMenuItemType, predefined_item_type: Option<PredefinedMenuItemType>,
// check menu item fields // check menu item fields
checked: Rc<AtomicBool>, checked: Option<Rc<AtomicBool>>,
is_syncing_checked_state: Rc<AtomicBool>, is_syncing_checked_state: Option<Rc<AtomicBool>>,
// icon menu item fields // icon menu item fields
icon: Option<Icon>, icon: Option<Icon>,
// submenu fields // submenu fields
pub children: Option<Vec<Rc<RefCell<MenuChild>>>>, pub children: Option<Vec<Rc<RefCell<MenuChild>>>>,
gtk_menus: HashMap<u32, Vec<(u32, gtk::Menu)>>, gtk_menus: Option<HashMap<u32, Vec<(u32, gtk::Menu)>>>,
gtk_menu: (u32, Option<gtk::Menu>), // dedicated menu for tray or context menus gtk_menu: Option<(u32, Option<gtk::Menu>)>, // dedicated menu for tray or context menus
accel_group: Option<gtk::AccelGroup>, accel_group: Option<gtk::AccelGroup>,
} }
impl Drop for MenuChild {
fn drop(&mut self) {
if self.item_type == MenuItemType::Submenu {
for menus in self.gtk_menus.as_ref().unwrap().values() {
for (id, menu) in menus {
drop_children_from_menu_and_destroy(*id, menu, self.children.as_ref().unwrap());
unsafe { menu.destroy() };
}
}
if let Some((id, Some(menu))) = &self.gtk_menu {
drop_children_from_menu_and_destroy(*id, menu, self.children.as_ref().unwrap());
unsafe { menu.destroy() };
}
}
for items in self.gtk_menu_items.borrow().values() {
for item in items {
if let Some(accel_group) = &self.accel_group {
if let Some((mods, key)) = self.gtk_accelerator {
item.remove_accelerator(accel_group, key, mods);
}
}
unsafe { item.destroy() };
}
}
}
}
fn drop_children_from_menu_and_destroy(
id: u32,
menu: &impl IsA<Container>,
children: &Vec<Rc<RefCell<MenuChild>>>,
) {
for child in children {
let mut child_ = child.borrow_mut();
{
let mut menu_items = child_.gtk_menu_items.borrow_mut();
if let Some(items) = menu_items.remove(&id) {
for item in items {
menu.remove(&item);
if let Some(accel_group) = &child_.accel_group {
if let Some((mods, key)) = child_.gtk_accelerator {
item.remove_accelerator(accel_group, key, mods);
}
}
unsafe { item.destroy() }
}
}
}
if child_.item_type == MenuItemType::Submenu {
if let Some(menus) = child_.gtk_menus.as_mut().unwrap().remove(&id) {
for (id, menu) in menus {
let children = child_.children.as_ref().unwrap();
drop_children_from_menu_and_destroy(id, &menu, children);
unsafe { menu.destroy() }
}
}
}
}
}
/// Constructors /// Constructors
impl MenuChild { impl MenuChild {
pub fn new( pub fn new(
@ -396,7 +496,15 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
item_type: MenuItemType::MenuItem, item_type: MenuItemType::MenuItem,
gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
..Default::default() accel_group: None,
checked: None,
children: None,
gtk_accelerator: None,
gtk_menu: None,
gtk_menus: None,
icon: None,
is_syncing_checked_state: None,
predefined_item_type: None,
} }
} }
@ -407,10 +515,16 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
children: Some(Vec::new()), children: Some(Vec::new()),
item_type: MenuItemType::Submenu, item_type: MenuItemType::Submenu,
gtk_menu: (COUNTER.next(), None), gtk_menu: Some((COUNTER.next(), None)),
gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
gtk_menus: HashMap::new(), gtk_menus: Some(HashMap::new()),
..Default::default() accel_group: None,
gtk_accelerator: None,
icon: None,
is_syncing_checked_state: None,
predefined_item_type: None,
accelerator: None,
checked: None,
} }
} }
@ -421,9 +535,16 @@ impl MenuChild {
accelerator: item_type.accelerator(), accelerator: item_type.accelerator(),
id: MenuId(COUNTER.next().to_string()), id: MenuId(COUNTER.next().to_string()),
item_type: MenuItemType::Predefined, item_type: MenuItemType::Predefined,
predefined_item_type: item_type, predefined_item_type: Some(item_type),
gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
..Default::default() accel_group: None,
checked: None,
children: None,
gtk_accelerator: None,
gtk_menu: None,
gtk_menus: None,
icon: None,
is_syncing_checked_state: None,
} }
} }
@ -437,13 +558,19 @@ impl MenuChild {
Self { Self {
text: text.to_string(), text: text.to_string(),
enabled, enabled,
checked: Rc::new(AtomicBool::new(checked)), checked: Some(Rc::new(AtomicBool::new(checked))),
is_syncing_checked_state: Some(Rc::new(AtomicBool::new(false))),
accelerator, accelerator,
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
item_type: MenuItemType::Check, item_type: MenuItemType::Check,
gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
is_syncing_checked_state: Rc::new(AtomicBool::new(false)), accel_group: None,
..Default::default() children: None,
gtk_accelerator: None,
gtk_menu: None,
gtk_menus: None,
icon: None,
predefined_item_type: None,
} }
} }
@ -462,8 +589,14 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
item_type: MenuItemType::Icon, item_type: MenuItemType::Icon,
gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
is_syncing_checked_state: Rc::new(AtomicBool::new(false)), accel_group: None,
..Default::default() checked: None,
children: None,
gtk_accelerator: None,
gtk_menu: None,
gtk_menus: None,
is_syncing_checked_state: None,
predefined_item_type: None,
} }
} }
@ -481,8 +614,15 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
item_type: MenuItemType::Icon, item_type: MenuItemType::Icon,
gtk_menu_items: Rc::new(RefCell::new(HashMap::new())), gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
is_syncing_checked_state: Rc::new(AtomicBool::new(false)), accel_group: None,
..Default::default() checked: None,
children: None,
gtk_accelerator: None,
gtk_menu: None,
gtk_menus: None,
icon: None,
is_syncing_checked_state: None,
predefined_item_type: None,
} }
} }
} }
@ -591,13 +731,17 @@ impl MenuChild {
.map(|e| e.map(|i| i.downcast_ref::<gtk::CheckMenuItem>().unwrap().is_active())) .map(|e| e.map(|i| i.downcast_ref::<gtk::CheckMenuItem>().unwrap().is_active()))
{ {
Some(Some(checked)) => checked, Some(Some(checked)) => checked,
_ => self.checked.load(Ordering::Relaxed), _ => self.checked.as_ref().unwrap().load(Ordering::Relaxed),
} }
} }
pub fn set_checked(&mut self, checked: bool) { pub fn set_checked(&mut self, checked: bool) {
self.checked.store(checked, Ordering::Release); self.checked
self.is_syncing_checked_state.store(true, Ordering::Release); .as_ref()
.unwrap()
.store(checked, Ordering::Release);
let is_syncing = self.is_syncing_checked_state.as_ref().unwrap();
is_syncing.store(true, Ordering::Release);
for items in self.gtk_menu_items.borrow().values() { for items in self.gtk_menu_items.borrow().values() {
for i in items { for i in items {
i.downcast_ref::<gtk::CheckMenuItem>() i.downcast_ref::<gtk::CheckMenuItem>()
@ -605,8 +749,7 @@ impl MenuChild {
.set_active(checked); .set_active(checked);
} }
} }
self.is_syncing_checked_state is_syncing.store(false, Ordering::Release);
.store(false, Ordering::Release);
} }
} }
@ -631,30 +774,29 @@ impl MenuChild {
/// Submenu methods /// Submenu methods
impl MenuChild { impl MenuChild {
pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> { pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> {
return_if_predefined_item_not_supported!(item); if is_item_supported!(item) {
for menus in self.gtk_menus.as_ref().unwrap().values() {
for menus in self.gtk_menus.values() { for (menu_id, menu) in menus {
for (menu_id, menu) in menus { let gtk_item =
let gtk_item = item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?;
item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; match op {
match op { AddOp::Append => menu.append(&gtk_item),
AddOp::Append => menu.append(&gtk_item), AddOp::Insert(position) => menu.insert(&gtk_item, position as i32),
AddOp::Insert(position) => menu.insert(&gtk_item, position as i32), }
gtk_item.show();
} }
gtk_item.show();
} }
}
{ {
let (menu_id, menu) = &self.gtk_menu; if let (menu_id, Some(menu)) = self.gtk_menu.as_ref().unwrap() {
if let Some(menu) = menu { let gtk_item =
let gtk_item = item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?;
item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; match op {
match op { AddOp::Append => menu.append(&gtk_item),
AddOp::Append => menu.append(&gtk_item), AddOp::Insert(position) => menu.insert(&gtk_item, position as i32),
AddOp::Insert(position) => menu.insert(&gtk_item, position as i32), }
gtk_item.show();
} }
gtk_item.show();
} }
} }
@ -671,9 +813,9 @@ impl MenuChild {
} }
fn add_menu_item_with_id(&self, item: &dyn crate::IsMenuItem, id: u32) -> crate::Result<()> { fn add_menu_item_with_id(&self, item: &dyn crate::IsMenuItem, id: u32) -> crate::Result<()> {
return_if_predefined_item_not_supported!(item); return_if_item_not_supported!(item);
for menus in self.gtk_menus.values() { for menus in self.gtk_menus.as_ref().unwrap().values() {
for (menu_id, menu) in menus.iter().filter(|m| m.0 == id) { for (menu_id, menu) in menus.iter().filter(|m| m.0 == id) {
let gtk_item = let gtk_item =
item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?; item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?;
@ -686,11 +828,11 @@ impl MenuChild {
} }
fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> {
return_if_predefined_item_not_supported!(item); return_if_item_not_supported!(item);
let (menu_id, menu) = &self.gtk_menu; let (menu_id, menu) = self.gtk_menu.as_ref().unwrap();
if let Some(menu) = menu { if let Some(menu) = menu {
let gtk_item = item.make_gtk_menu_item(*menu_id, None, true)?; let gtk_item = item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true)?;
menu.append(&gtk_item); menu.append(&gtk_item);
gtk_item.show(); gtk_item.show();
} }
@ -708,6 +850,7 @@ impl MenuChild {
remove_from_cache: bool, remove_from_cache: bool,
id: Option<u32>, id: Option<u32>,
) -> crate::Result<()> { ) -> crate::Result<()> {
// get child
let child = { let child = {
let index = self let index = self
.children .children
@ -723,48 +866,66 @@ impl MenuChild {
} }
}; };
if let MenuItemKind::Submenu(i) = item.kind() { for menus in self.gtk_menus.as_ref().unwrap().values() {
let gtk_menus = i.inner.borrow().gtk_menus.clone();
for (menu_id, _) in gtk_menus {
for item in i.items() {
i.inner
.borrow_mut()
.remove_inner(item.as_ref(), false, Some(menu_id))?;
}
}
}
for menus in self.gtk_menus.values() {
for (menu_id, menu) in menus { for (menu_id, menu) in menus {
// check if we are removing this item from all gtk_menus
// which is usually when this is the item the user is actaully removing
// or if we are removing from a specific menu (id)
// which is when the actual item being removed is a submenu
// and we are iterating through its children and removing
// each child gtk items that are related to this submenu.
if id.map(|i| i == *menu_id).unwrap_or(true) { if id.map(|i| i == *menu_id).unwrap_or(true) {
if let Some(items) = child // bail this is not a supported item like a close_window predefined menu item
.borrow_mut() if is_item_supported!(item) {
.gtk_menu_items let mut child_ = child.borrow_mut();
.borrow_mut()
.remove(menu_id) if child_.item_type == MenuItemType::Submenu {
{ let menus = child_.gtk_menus.as_ref().unwrap().get(menu_id).cloned();
for item in items { if let Some(menus) = menus {
menu.remove(&item); for (id, menu) in menus {
// iterate through children and only remove the gtk items
// related to this submenu
for item in child_.items() {
child_.remove_inner(item.as_ref(), false, Some(id))?;
}
unsafe { menu.destroy() };
}
}
child_.gtk_menus.as_mut().unwrap().remove(menu_id);
} }
// remove all the gtk items that are related to this gtk menu and destroy it
if let Some(items) = child_.gtk_menu_items.borrow_mut().remove(menu_id) {
for item in items {
menu.remove(&item);
if let Some(accel_group) = &child_.accel_group {
if let Some((mods, key)) = child_.gtk_accelerator {
item.remove_accelerator(accel_group, key, mods);
}
}
unsafe { item.destroy() };
}
};
} }
} }
} }
} }
// remove from the gtk menu assigned to the context menu
if remove_from_cache { if remove_from_cache {
let (menu_id, menu) = &self.gtk_menu; if let (id, Some(menu)) = self.gtk_menu.as_ref().unwrap() {
if let Some(menu) = menu { let child_ = child.borrow_mut();
if let Some(items) = child if let Some(items) = child_.gtk_menu_items.borrow_mut().remove(id) {
.borrow_mut()
.gtk_menu_items
.borrow_mut()
.remove(menu_id)
{
for item in items { for item in items {
menu.remove(&item); menu.remove(&item);
if let Some(accel_group) = &child_.accel_group {
if let Some((mods, key)) = child_.gtk_accelerator {
item.remove_accelerator(accel_group, key, mods);
}
}
unsafe { item.destroy() };
} }
} };
} }
} }
@ -791,8 +952,9 @@ impl MenuChild {
pub fn gtk_context_menu(&mut self) -> gtk::Menu { pub fn gtk_context_menu(&mut self) -> gtk::Menu {
let mut add_items = false; let mut add_items = false;
{ {
if self.gtk_menu.1.is_none() { let gtk_menu = self.gtk_menu.as_mut().unwrap();
self.gtk_menu.1 = Some(gtk::Menu::new()); if gtk_menu.1.is_none() {
gtk_menu.1 = Some(gtk::Menu::new());
add_items = true; add_items = true;
} }
} }
@ -803,7 +965,7 @@ impl MenuChild {
} }
} }
self.gtk_menu.1.as_ref().unwrap().clone() self.gtk_menu.as_ref().unwrap().1.as_ref().unwrap().clone()
} }
} }
@ -860,6 +1022,8 @@ impl MenuChild {
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(item.clone()); .push(item.clone());
self.gtk_menus self.gtk_menus
.as_mut()
.unwrap()
.entry(menu_id) .entry(menu_id)
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push((id, submenu.clone())); .push((id, submenu.clone()));
@ -921,7 +1085,7 @@ impl MenuChild {
.as_ref() .as_ref()
.map(parse_accelerator) .map(parse_accelerator)
.transpose()?; .transpose()?;
let predefined_item_type = self.predefined_item_type.clone(); let predefined_item_type = self.predefined_item_type.clone().unwrap();
let make_item = || { let make_item = || {
gtk::MenuItem::builder() gtk::MenuItem::builder()
@ -1038,7 +1202,7 @@ impl MenuChild {
.label(&to_gtk_mnemonic(&self.text)) .label(&to_gtk_mnemonic(&self.text))
.use_underline(true) .use_underline(true)
.sensitive(self.enabled) .sensitive(self.enabled)
.active(self.checked.load(Ordering::Relaxed)) .active(self.checked.as_ref().unwrap().load(Ordering::Relaxed))
.build(); .build();
self.accel_group = accel_group.cloned(); self.accel_group = accel_group.cloned();
@ -1046,8 +1210,8 @@ impl MenuChild {
register_accel!(self, item, accel_group); register_accel!(self, item, accel_group);
let id = self.id.clone(); let id = self.id.clone();
let is_syncing_checked_state = self.is_syncing_checked_state.clone(); let is_syncing_checked_state = self.is_syncing_checked_state.clone().unwrap();
let checked = self.checked.clone(); let checked = self.checked.clone().unwrap();
let store = self.gtk_menu_items.clone(); let store = self.gtk_menu_items.clone();
item.connect_toggled(move |i| { item.connect_toggled(move |i| {
let should_dispatch = is_syncing_checked_state let should_dispatch = is_syncing_checked_state

View file

@ -47,17 +47,33 @@ extern "C" {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
const NSAboutPanelOptionCopyright: &str = "Copyright"; const NSAboutPanelOptionCopyright: &str = "Copyright";
#[derive(Debug, Clone)]
struct NsMenuRef(u32, id);
impl Drop for NsMenuRef {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.1, removeAllItems];
let _: () = msg_send![self.1, release];
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Menu { pub struct Menu {
id: MenuId, id: MenuId,
ns_menu: id, ns_menu: NsMenuRef,
children: Rc<RefCell<Vec<Rc<RefCell<MenuChild>>>>>, children: Vec<Rc<RefCell<MenuChild>>>,
} }
impl Drop for Menu { impl Drop for Menu {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { for child in &self.children {
let _: () = msg_send![self.ns_menu, release]; let mut child_ = child.borrow_mut();
child_.ns_menu_items.remove(&self.ns_menu.0);
if child_.item_type == MenuItemType::Submenu {
child_.ns_menus.as_mut().unwrap().remove(&self.ns_menu.0);
}
} }
} }
} }
@ -66,13 +82,13 @@ impl Menu {
pub fn new(id: Option<MenuId>) -> Self { pub fn new(id: Option<MenuId>) -> Self {
Self { Self {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
ns_menu: unsafe { ns_menu: NsMenuRef(COUNTER.next(), unsafe {
let ns_menu = NSMenu::new(nil); let ns_menu = NSMenu::new(nil);
ns_menu.setAutoenablesItems(NO); ns_menu.setAutoenablesItems(NO);
let _: () = msg_send![ns_menu, retain]; let _: () = msg_send![ns_menu, retain];
ns_menu ns_menu
}, }),
children: Rc::new(RefCell::new(Vec::new())), children: Vec::new(),
} }
} }
@ -81,18 +97,18 @@ impl Menu {
} }
pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> { pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> {
let ns_menu_item: *mut Object = item.make_ns_item_for_menu(&self.id)?; let ns_menu_item: id = item.make_ns_item_for_menu(self.ns_menu.0)?;
let child = item.child(); let child = item.child();
unsafe { unsafe {
match op { match op {
AddOp::Append => { AddOp::Append => {
self.ns_menu.addItem_(ns_menu_item); self.ns_menu.1.addItem_(ns_menu_item);
self.children.borrow_mut().push(child); self.children.push(child);
} }
AddOp::Insert(position) => { AddOp::Insert(position) => {
let () = msg_send![self.ns_menu, insertItem: ns_menu_item atIndex: position as NSInteger]; let () = msg_send![self.ns_menu.1, insertItem: ns_menu_item atIndex: position as NSInteger];
self.children.borrow_mut().insert(position, child); self.children.insert(position, child);
} }
} }
} }
@ -100,46 +116,51 @@ impl Menu {
Ok(()) Ok(())
} }
pub fn remove(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { pub fn remove(&mut self, item: &dyn crate::IsMenuItem) -> crate::Result<()> {
// get a list of instances of the specified `NSMenuItem` in this menu // get child
let child = match item.kind() { let child = {
MenuItemKind::Submenu(i) => i.inner.clone(), let index = self
MenuItemKind::MenuItem(i) => i.inner.clone(), .children
MenuItemKind::Predefined(i) => i.inner.clone(), .iter()
MenuItemKind::Check(i) => i.inner.clone(), .position(|e| e.borrow().id == item.id())
MenuItemKind::Icon(i) => i.inner.clone(), .ok_or(crate::Error::NotAChildOfThisMenu)?;
self.children.remove(index)
}; };
let mut child_ = child.borrow_mut(); let mut child_ = child.borrow_mut();
if let Some(ns_menu_items) = child_.ns_menu_items.remove(&self.id) {
// remove each NSMenuItem from the NSMenu if child_.item_type == MenuItemType::Submenu {
unsafe { let menu_id = &self.ns_menu.0;
for item in ns_menu_items { let menus = child_.ns_menus.as_ref().unwrap().get(menu_id).cloned();
let () = msg_send![self.ns_menu, removeItem: item]; if let Some(menus) = menus {
for menu in menus {
for item in child_.items() {
child_.remove_inner(item.as_ref(), false, Some(menu.0))?;
}
} }
} }
child_.ns_menus.as_mut().unwrap().remove(menu_id);
} }
// remove the item from our internal list of children // remove each NSMenuItem from the NSMenu
let mut children = self.children.borrow_mut(); if let Some(ns_menu_items) = child_.ns_menu_items.remove(&self.ns_menu.0) {
let index = children for item in ns_menu_items {
.iter() let () = unsafe { msg_send![self.ns_menu.1, removeItem: item] };
.position(|e| e.borrow().id() == item.id()) }
.ok_or(crate::Error::NotAChildOfThisMenu)?; }
children.remove(index);
Ok(()) Ok(())
} }
pub fn items(&self) -> Vec<MenuItemKind> { pub fn items(&self) -> Vec<MenuItemKind> {
self.children self.children
.borrow()
.iter() .iter()
.map(|c| c.borrow().kind(c.clone())) .map(|c| c.borrow().kind(c.clone()))
.collect() .collect()
} }
pub fn init_for_nsapp(&self) { pub fn init_for_nsapp(&self) {
unsafe { NSApp().setMainMenu_(self.ns_menu) } unsafe { NSApp().setMainMenu_(self.ns_menu.1) }
} }
pub fn remove_for_nsapp(&self) { pub fn remove_for_nsapp(&self) {
@ -147,16 +168,16 @@ impl Menu {
} }
pub fn show_context_menu_for_nsview(&self, view: id, position: Option<Position>) { pub fn show_context_menu_for_nsview(&self, view: id, position: Option<Position>) {
show_context_menu(self.ns_menu, view, position) show_context_menu(self.ns_menu.1, view, position)
} }
pub fn ns_menu(&self) -> *mut std::ffi::c_void { pub fn ns_menu(&self) -> *mut std::ffi::c_void {
self.ns_menu as _ self.ns_menu.1 as _
} }
} }
/// A generic child in a menu /// A generic child in a menu
#[derive(Debug)] #[derive(Debug, Default)]
pub struct MenuChild { pub struct MenuChild {
// shared fields between submenus and menu items // shared fields between submenus and menu items
item_type: MenuItemType, item_type: MenuItemType,
@ -164,13 +185,13 @@ pub struct MenuChild {
text: String, text: String,
enabled: bool, enabled: bool,
ns_menu_items: HashMap<MenuId, Vec<id>>, ns_menu_items: HashMap<u32, Vec<id>>,
// menu item fields // menu item fields
accelerator: Option<Accelerator>, accelerator: Option<Accelerator>,
// predefined menu item fields // predefined menu item fields
predefined_item_type: PredefinedMenuItemType, predefined_item_type: Option<PredefinedMenuItemType>,
// check menu item fields // check menu item fields
checked: bool, checked: bool,
@ -181,37 +202,37 @@ pub struct MenuChild {
// submenu fields // submenu fields
pub children: Option<Vec<Rc<RefCell<MenuChild>>>>, pub children: Option<Vec<Rc<RefCell<MenuChild>>>>,
ns_menus: HashMap<MenuId, Vec<id>>, ns_menus: Option<HashMap<u32, Vec<NsMenuRef>>>,
ns_menu: NsMenuRef, ns_menu: Option<NsMenuRef>,
} }
#[derive(Debug)] impl Drop for MenuChild {
struct NsMenuRef(MenuId, id);
impl Drop for NsMenuRef {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { fn drop_children(id: u32, children: &Vec<Rc<RefCell<MenuChild>>>) {
let _: () = msg_send![self.1, release]; for child in children {
} let mut child_ = child.borrow_mut();
} child_.ns_menu_items.remove(&id);
}
impl Default for MenuChild { if child_.item_type == MenuItemType::Submenu {
fn default() -> Self { if let Some(menus) = child_.ns_menus.as_mut().unwrap().remove(&id) {
Self { for menu in menus {
item_type: Default::default(), drop_children(menu.0, child_.children.as_ref().unwrap());
id: Default::default(), }
text: Default::default(), }
enabled: Default::default(), }
ns_menu_items: Default::default(), }
accelerator: Default::default(), }
predefined_item_type: Default::default(),
checked: Default::default(), if self.item_type == MenuItemType::Submenu {
icon: Default::default(), for menus in self.ns_menus.as_ref().unwrap().values() {
native_icon: Default::default(), for menu in menus {
children: Default::default(), drop_children(menu.0, self.children.as_ref().unwrap())
ns_menus: Default::default(), }
ns_menu: NsMenuRef(MenuId(COUNTER.next().to_string()), 0 as _), }
if let Some(menu) = &self.ns_menu {
drop_children(menu.0, self.children.as_ref().unwrap());
}
} }
} }
} }
@ -230,7 +251,14 @@ impl MenuChild {
enabled, enabled,
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
accelerator, accelerator,
..Default::default() checked: false,
children: None,
icon: None,
native_icon: None,
ns_menu: None,
ns_menu_items: HashMap::new(),
ns_menus: None,
predefined_item_type: None,
} }
} }
@ -241,12 +269,18 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
enabled, enabled,
children: Some(Vec::new()), children: Some(Vec::new()),
ns_menu: NsMenuRef(MenuId(COUNTER.next().to_string()), unsafe { ns_menu: Some(NsMenuRef(COUNTER.next(), unsafe {
let menu = NSMenu::new(nil); let menu = NSMenu::new(nil);
let _: () = msg_send![menu, retain]; let _: () = msg_send![menu, retain];
menu menu
}), })),
..Default::default() accelerator: None,
checked: false,
icon: None,
native_icon: None,
ns_menu_items: HashMap::new(),
ns_menus: Some(HashMap::new()),
predefined_item_type: None,
} }
} }
@ -279,9 +313,14 @@ impl MenuChild {
enabled: true, enabled: true,
id: MenuId(COUNTER.next().to_string()), id: MenuId(COUNTER.next().to_string()),
accelerator, accelerator,
predefined_item_type: item_type, predefined_item_type: Some(item_type),
// ns_menu_item, checked: false,
..Default::default() children: None,
icon: None,
native_icon: None,
ns_menu: None,
ns_menu_items: HashMap::new(),
ns_menus: None,
} }
} }
@ -299,7 +338,13 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
accelerator, accelerator,
checked, checked,
..Default::default() children: None,
icon: None,
native_icon: None,
ns_menu: None,
ns_menu_items: HashMap::new(),
ns_menus: None,
predefined_item_type: None,
} }
} }
@ -317,7 +362,13 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
icon, icon,
accelerator, accelerator,
..Default::default() checked: false,
children: None,
native_icon: None,
ns_menu: None,
ns_menu_items: HashMap::new(),
ns_menus: None,
predefined_item_type: None,
} }
} }
@ -335,7 +386,13 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())), id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
native_icon, native_icon,
accelerator, accelerator,
..Default::default() checked: false,
children: None,
icon: None,
ns_menu: None,
ns_menu_items: HashMap::new(),
ns_menus: None,
predefined_item_type: None,
} }
} }
} }
@ -361,7 +418,7 @@ impl MenuChild {
for ns_items in self.ns_menu_items.values() { for ns_items in self.ns_menu_items.values() {
for &ns_item in ns_items { for &ns_item in ns_items {
let () = msg_send![ns_item, setTitle: title]; let () = msg_send![ns_item, setTitle: title];
let ns_submenu: *mut Object = msg_send![ns_item, submenu]; let ns_submenu: id = msg_send![ns_item, submenu];
if ns_submenu != nil { if ns_submenu != nil {
let () = msg_send![ns_submenu, setTitle: title]; let () = msg_send![ns_submenu, setTitle: title];
} }
@ -468,28 +525,32 @@ impl MenuChild {
unsafe { unsafe {
match op { match op {
AddOp::Append => { AddOp::Append => {
for menus in self.ns_menus.values() { for menus in self.ns_menus.as_ref().unwrap().values() {
for ns_menu in menus { for ns_menu in menus {
let ns_menu_item: *mut Object = item.make_ns_item_for_menu(&self.id)?; let ns_menu_item: id =
ns_menu.addItem_(ns_menu_item); item.make_ns_item_for_menu(self.ns_menu.as_ref().unwrap().0)?;
ns_menu.1.addItem_(ns_menu_item);
} }
} }
let ns_menu_item: *mut Object = item.make_ns_item_for_menu(&self.ns_menu.0)?; let ns_menu_item: id =
self.ns_menu.1.addItem_(ns_menu_item); item.make_ns_item_for_menu(self.ns_menu.as_ref().unwrap().0)?;
self.ns_menu.as_ref().unwrap().1.addItem_(ns_menu_item);
self.children.as_mut().unwrap().push(child); self.children.as_mut().unwrap().push(child);
} }
AddOp::Insert(position) => { AddOp::Insert(position) => {
for menus in self.ns_menus.values() { for menus in self.ns_menus.as_ref().unwrap().values() {
for &ns_menu in menus { for ns_menu in menus {
let ns_menu_item: *mut Object = item.make_ns_item_for_menu(&self.id)?; let ns_menu_item: id =
let () = msg_send![ns_menu, insertItem: ns_menu_item atIndex: position as NSInteger]; item.make_ns_item_for_menu(self.ns_menu.as_ref().unwrap().0)?;
let () = msg_send![ns_menu.1, insertItem: ns_menu_item atIndex: position as NSInteger];
} }
} }
let ns_menu_item: *mut Object = item.make_ns_item_for_menu(&self.ns_menu.0)?; let ns_menu_item: id =
let () = msg_send![ self.ns_menu.1, insertItem: ns_menu_item atIndex: position as NSInteger]; item.make_ns_item_for_menu(self.ns_menu.as_ref().unwrap().0)?;
let () = msg_send![ self.ns_menu.as_ref().unwrap().1, insertItem: ns_menu_item atIndex: position as NSInteger];
self.children.as_mut().unwrap().insert(position, child); self.children.as_mut().unwrap().insert(position, child);
} }
@ -500,40 +561,77 @@ impl MenuChild {
} }
pub fn remove(&mut self, item: &dyn crate::IsMenuItem) -> crate::Result<()> { pub fn remove(&mut self, item: &dyn crate::IsMenuItem) -> crate::Result<()> {
let child = item.child(); self.remove_inner(item, true, None)
}
pub fn remove_inner(
&mut self,
item: &dyn crate::IsMenuItem,
remove_from_cache: bool,
id: Option<u32>,
) -> crate::Result<()> {
// get child
let child = {
let index = self
.children
.as_ref()
.unwrap()
.iter()
.position(|e| e.borrow().id == item.id())
.ok_or(crate::Error::NotAChildOfThisMenu)?;
if remove_from_cache {
self.children.as_mut().unwrap().remove(index)
} else {
self.children.as_ref().unwrap().get(index).cloned().unwrap()
}
};
// get a list of instances of the specified NSMenuItem in this menu for menus in self.ns_menus.as_ref().unwrap().values() {
if let Some(ns_menu_items) = child.borrow_mut().ns_menu_items.remove(&self.id) { for menu in menus {
// remove each NSMenuItem from the NSMenu // check if we are removing this item from all ns_menus
unsafe { // which is usually when this is the item the user is actaully removing
for item in ns_menu_items { // or if we are removing from a specific menu (id)
for menus in self.ns_menus.values() { // which is when the actual item being removed is a submenu
for &ns_menu in menus { // and we are iterating through its children and removing
let () = msg_send![ns_menu, removeItem: item]; // each child ns menu item that are related to this submenu.
if id.map(|i| i == menu.0).unwrap_or(true) {
let mut child_ = child.borrow_mut();
if child_.item_type == MenuItemType::Submenu {
let menus = child_.ns_menus.as_ref().unwrap().get(&menu.0).cloned();
if let Some(menus) = menus {
for menu in menus {
// iterate through children and only remove the ns menu items
// related to this submenu
for item in child_.items() {
child_.remove_inner(item.as_ref(), false, Some(menu.0))?;
}
}
} }
child_.ns_menus.as_mut().unwrap().remove(&menu.0);
} }
let () = msg_send![self.ns_menu.1, removeItem: item]; if let Some(items) = child_.ns_menu_items.remove(&menu.0) {
for item in items {
let () = unsafe { msg_send![menu.1, removeItem: item] };
}
}
} }
} }
} }
if let Some(ns_menu_items) = child.borrow_mut().ns_menu_items.remove(&self.ns_menu.0) { if remove_from_cache {
unsafe { if let Some(ns_menu_items) = child
.borrow_mut()
.ns_menu_items
.remove(&self.ns_menu.as_ref().unwrap().0)
{
for item in ns_menu_items { for item in ns_menu_items {
let () = msg_send![self.ns_menu.1, removeItem: item]; let () =
unsafe { msg_send![self.ns_menu.as_ref().unwrap().1, removeItem: item] };
} }
} }
} }
// remove the item from our internal list of children
let children = self.children.as_mut().unwrap();
let index = children
.iter()
.position(|e| e.borrow().id == item.id())
.ok_or(crate::Error::NotAChildOfThisMenu)?;
children.remove(index);
Ok(()) Ok(())
} }
@ -547,27 +645,27 @@ impl MenuChild {
} }
pub fn show_context_menu_for_nsview(&self, view: id, position: Option<Position>) { pub fn show_context_menu_for_nsview(&self, view: id, position: Option<Position>) {
show_context_menu(self.ns_menu.1, view, position) show_context_menu(self.ns_menu.as_ref().unwrap().1, view, position)
} }
pub fn set_as_windows_menu_for_nsapp(&self) { pub fn set_as_windows_menu_for_nsapp(&self) {
unsafe { NSApp().setWindowsMenu_(self.ns_menu.1) } unsafe { NSApp().setWindowsMenu_(self.ns_menu.as_ref().unwrap().1) }
} }
pub fn set_as_help_menu_for_nsapp(&self) { pub fn set_as_help_menu_for_nsapp(&self) {
unsafe { msg_send![NSApp(), setHelpMenu: self.ns_menu.1] } unsafe { msg_send![NSApp(), setHelpMenu: self.ns_menu.as_ref().unwrap().1] }
} }
pub fn ns_menu(&self) -> *mut std::ffi::c_void { pub fn ns_menu(&self) -> *mut std::ffi::c_void {
self.ns_menu.1 as _ self.ns_menu.as_ref().unwrap().1 as _
} }
} }
/// NSMenuItem item creation methods /// NSMenuItem item creation methods
impl MenuChild { impl MenuChild {
pub fn create_ns_item_for_submenu(&mut self, menu_id: &MenuId) -> crate::Result<id> { pub fn create_ns_item_for_submenu(&mut self, menu_id: u32) -> crate::Result<id> {
let ns_menu_item: *mut Object; let ns_menu_item: id;
let ns_submenu: *mut Object; let ns_submenu: id;
unsafe { unsafe {
ns_menu_item = NSMenuItem::alloc(nil).autorelease(); ns_menu_item = NSMenuItem::alloc(nil).autorelease();
@ -583,25 +681,29 @@ impl MenuChild {
} }
} }
let id = COUNTER.next();
for item in self.children.as_ref().unwrap() { for item in self.children.as_ref().unwrap() {
let ns_item = item.borrow_mut().make_ns_item_for_menu(menu_id)?; let ns_item = item.borrow_mut().make_ns_item_for_menu(id)?;
unsafe { ns_submenu.addItem_(ns_item) }; unsafe { ns_submenu.addItem_(ns_item) };
} }
self.ns_menus self.ns_menus
.entry(menu_id.clone()) .as_mut()
.unwrap()
.entry(menu_id)
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(ns_submenu); .push(NsMenuRef(id, ns_submenu));
self.ns_menu_items self.ns_menu_items
.entry(menu_id.clone()) .entry(menu_id)
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(ns_menu_item); .push(ns_menu_item);
Ok(ns_menu_item) Ok(ns_menu_item)
} }
pub fn create_ns_item_for_menu_item(&mut self, menu_id: &MenuId) -> crate::Result<id> { pub fn create_ns_item_for_menu_item(&mut self, menu_id: u32) -> crate::Result<id> {
let ns_menu_item = create_ns_menu_item( let ns_menu_item = create_ns_menu_item(
&self.text, &self.text,
Some(sel!(fireMenuItemAction:)), Some(sel!(fireMenuItemAction:)),
@ -628,11 +730,8 @@ impl MenuChild {
Ok(ns_menu_item) Ok(ns_menu_item)
} }
pub fn create_ns_item_for_predefined_menu_item( pub fn create_ns_item_for_predefined_menu_item(&mut self, menu_id: u32) -> crate::Result<id> {
&mut self, let item_type = self.predefined_item_type.as_ref().unwrap();
menu_id: &MenuId,
) -> crate::Result<id> {
let item_type = &self.predefined_item_type;
let ns_menu_item = match item_type { let ns_menu_item = match item_type {
PredefinedMenuItemType::Separator => unsafe { PredefinedMenuItemType::Separator => unsafe {
NSMenuItem::separatorItem(nil).autorelease() NSMenuItem::separatorItem(nil).autorelease()
@ -640,7 +739,7 @@ impl MenuChild {
_ => create_ns_menu_item(&self.text, item_type.selector(), &self.accelerator)?, _ => create_ns_menu_item(&self.text, item_type.selector(), &self.accelerator)?,
}; };
if let PredefinedMenuItemType::About(_) = self.predefined_item_type { if let PredefinedMenuItemType::About(_) = item_type {
unsafe { unsafe {
let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item]; let _: () = msg_send![ns_menu_item, setTarget: ns_menu_item];
@ -654,7 +753,7 @@ impl MenuChild {
if !self.enabled { if !self.enabled {
let () = msg_send![ns_menu_item, setEnabled: NO]; let () = msg_send![ns_menu_item, setEnabled: NO];
} }
if let PredefinedMenuItemType::Services = self.predefined_item_type { if let PredefinedMenuItemType::Services = item_type {
// we have to assign an empty menu as the app's services menu, and macOS will populate it // we have to assign an empty menu as the app's services menu, and macOS will populate it
let services_menu = NSMenu::new(nil).autorelease(); let services_menu = NSMenu::new(nil).autorelease();
let () = msg_send![NSApp(), setServicesMenu: services_menu]; let () = msg_send![NSApp(), setServicesMenu: services_menu];
@ -670,7 +769,7 @@ impl MenuChild {
Ok(ns_menu_item) Ok(ns_menu_item)
} }
pub fn create_ns_item_for_check_menu_item(&mut self, menu_id: &MenuId) -> crate::Result<id> { pub fn create_ns_item_for_check_menu_item(&mut self, menu_id: u32) -> crate::Result<id> {
let ns_menu_item = create_ns_menu_item( let ns_menu_item = create_ns_menu_item(
&self.text, &self.text,
Some(sel!(fireMenuItemAction:)), Some(sel!(fireMenuItemAction:)),
@ -700,7 +799,7 @@ impl MenuChild {
Ok(ns_menu_item) Ok(ns_menu_item)
} }
pub fn create_ns_item_for_icon_menu_item(&mut self, menu_id: &MenuId) -> crate::Result<id> { pub fn create_ns_item_for_icon_menu_item(&mut self, menu_id: u32) -> crate::Result<id> {
let ns_menu_item = create_ns_menu_item( let ns_menu_item = create_ns_menu_item(
&self.text, &self.text,
Some(sel!(fireMenuItemAction:)), Some(sel!(fireMenuItemAction:)),
@ -733,7 +832,7 @@ impl MenuChild {
Ok(ns_menu_item) Ok(ns_menu_item)
} }
fn make_ns_item_for_menu(&mut self, menu_id: &MenuId) -> crate::Result<*mut Object> { fn make_ns_item_for_menu(&mut self, menu_id: u32) -> crate::Result<id> {
match self.item_type { match self.item_type {
MenuItemType::Submenu => self.create_ns_item_for_submenu(menu_id), MenuItemType::Submenu => self.create_ns_item_for_submenu(menu_id),
MenuItemType::MenuItem => self.create_ns_item_for_menu_item(menu_id), MenuItemType::MenuItem => self.create_ns_item_for_menu_item(menu_id),
@ -771,7 +870,7 @@ impl PredefinedMenuItemType {
} }
impl dyn IsMenuItem + '_ { impl dyn IsMenuItem + '_ {
fn make_ns_item_for_menu(&self, menu_id: &MenuId) -> crate::Result<*mut Object> { fn make_ns_item_for_menu(&self, menu_id: u32) -> crate::Result<id> {
match self.kind() { match self.kind() {
MenuItemKind::Submenu(i) => i.inner.borrow_mut().create_ns_item_for_submenu(menu_id), MenuItemKind::Submenu(i) => i.inner.borrow_mut().create_ns_item_for_submenu(menu_id),
MenuItemKind::MenuItem(i) => i.inner.borrow_mut().create_ns_item_for_menu_item(menu_id), MenuItemKind::MenuItem(i) => i.inner.borrow_mut().create_ns_item_for_menu_item(menu_id),
@ -835,7 +934,7 @@ extern "C" fn fire_menu_item_click(this: &Object, _: Sel, _item: id) {
let ptr: usize = *this.get_ivar(BLOCK_PTR); let ptr: usize = *this.get_ivar(BLOCK_PTR);
let item = ptr as *mut &mut MenuChild; let item = ptr as *mut &mut MenuChild;
if let PredefinedMenuItemType::About(about_meta) = &(*item).predefined_item_type { if let Some(PredefinedMenuItemType::About(about_meta)) = &(*item).predefined_item_type {
match about_meta { match about_meta {
Some(about_meta) => { Some(about_meta) => {
unsafe fn mkstr(s: &str) -> id { unsafe fn mkstr(s: &str) -> id {
@ -923,7 +1022,7 @@ fn create_ns_menu_item(
.map(|accel| accel.key_modifier_mask()) .map(|accel| accel.key_modifier_mask())
.unwrap_or_else(NSEventModifierFlags::empty); .unwrap_or_else(NSEventModifierFlags::empty);
let ns_menu_item: *mut Object = msg_send![make_menu_item_class(), alloc]; let ns_menu_item: id = msg_send![make_menu_item_class(), alloc];
ns_menu_item.initWithTitle_action_keyEquivalent_(title, selector, key_equivalent); ns_menu_item.initWithTitle_action_keyEquivalent_(title, selector, key_equivalent);
ns_menu_item.setKeyEquivalentModifierMask_(modifier_mask); ns_menu_item.setKeyEquivalentModifierMask_(modifier_mask);

View file

@ -30,9 +30,9 @@ use windows_sys::Win32::{
Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass}, Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass},
WindowsAndMessaging::{ WindowsAndMessaging::{
AppendMenuW, CreateAcceleratorTableW, CreateMenu, CreatePopupMenu, AppendMenuW, CreateAcceleratorTableW, CreateMenu, CreatePopupMenu,
DestroyAcceleratorTable, DrawMenuBar, EnableMenuItem, GetCursorPos, GetMenu, DestroyAcceleratorTable, DestroyMenu, DrawMenuBar, EnableMenuItem, GetCursorPos,
GetMenuItemInfoW, InsertMenuW, PostQuitMessage, RemoveMenu, SendMessageW, SetMenu, GetMenu, GetMenuItemInfoW, InsertMenuW, PostQuitMessage, RemoveMenu, SendMessageW,
SetMenuItemInfoW, ShowWindow, TrackPopupMenu, HACCEL, HMENU, MENUITEMINFOW, SetMenu, SetMenuItemInfoW, ShowWindow, TrackPopupMenu, HACCEL, HMENU, MENUITEMINFOW,
MFS_CHECKED, MFS_DISABLED, MF_BYCOMMAND, MF_BYPOSITION, MF_CHECKED, MF_DISABLED, MFS_CHECKED, MFS_DISABLED, MF_BYCOMMAND, MF_BYPOSITION, MF_CHECKED, MF_DISABLED,
MF_ENABLED, MF_GRAYED, MF_POPUP, MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_BITMAP, MF_ENABLED, MF_GRAYED, MF_POPUP, MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_BITMAP,
MIIM_STATE, MIIM_STRING, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, TPM_LEFTALIGN, WM_CLOSE, MIIM_STATE, MIIM_STRING, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, TPM_LEFTALIGN, WM_CLOSE,
@ -61,7 +61,7 @@ macro_rules! inner_menu_child_and_flags {
MenuItemKind::Predefined(i) => { MenuItemKind::Predefined(i) => {
let child = i.inner.clone(); let child = i.inner.clone();
let child_ = child.borrow(); let child_ = child.borrow();
match child_.predefined_item_type { match child_.predefined_item_type.as_ref().unwrap() {
PredefinedMenuItemType::None => return Ok(()), PredefinedMenuItemType::None => return Ok(()),
PredefinedMenuItemType::Separator => { PredefinedMenuItemType::Separator => {
flags |= MF_SEPARATOR; flags |= MF_SEPARATOR;
@ -101,6 +101,44 @@ pub(crate) struct Menu {
children: Vec<Rc<RefCell<MenuChild>>>, children: Vec<Rc<RefCell<MenuChild>>>,
} }
impl Drop for Menu {
fn drop(&mut self) {
for hwnd in self.hwnds.clone() {
let _ = self.remove_for_hwnd(hwnd);
}
fn remove_from_children_stores(id: &MenuId, children: &Vec<Rc<RefCell<MenuChild>>>) {
for child in children {
let mut child_ = child.borrow_mut();
child_.root_menu_haccel_stores.remove(id);
if child_.item_type == MenuItemType::Submenu {
remove_from_children_stores(id, child_.children.as_ref().unwrap());
}
}
}
remove_from_children_stores(&self.id, &self.children);
for child in &self.children {
let child_ = child.borrow();
let id = if child_.item_type == MenuItemType::Submenu {
child_.hmenu as _
} else {
child_.internal_id
};
unsafe {
RemoveMenu(self.hpopupmenu, id, MF_BYCOMMAND);
RemoveMenu(self.hmenu, id, MF_BYCOMMAND);
}
}
unsafe {
DestroyMenu(self.hmenu);
DestroyMenu(self.hpopupmenu);
}
}
}
impl Menu { impl Menu {
pub fn new(id: Option<MenuId>) -> Self { pub fn new(id: Option<MenuId>) -> Self {
Self { Self {
@ -124,9 +162,7 @@ impl Menu {
child child
.borrow_mut() .borrow_mut()
.root_menu_haccel_stores .root_menu_haccel_stores
.as_mut() .insert(self.id.clone(), self.haccel_store.clone());
.unwrap()
.push(self.haccel_store.clone());
} }
{ {
@ -371,7 +407,7 @@ pub(crate) 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>>>>, root_menu_haccel_stores: HashMap<MenuId, Rc<RefCell<AccelWrapper>>>,
// menu item fields // menu item fields
internal_id: u32, internal_id: u32,
@ -379,7 +415,7 @@ pub(crate) struct MenuChild {
accelerator: Option<Accelerator>, accelerator: Option<Accelerator>,
// predefined menu item fields // predefined menu item fields
predefined_item_type: PredefinedMenuItemType, predefined_item_type: Option<PredefinedMenuItemType>,
// check menu item fields // check menu item fields
checked: bool, checked: bool,
@ -393,6 +429,23 @@ pub(crate) struct MenuChild {
pub children: Option<Vec<Rc<RefCell<MenuChild>>>>, pub children: Option<Vec<Rc<RefCell<MenuChild>>>>,
} }
impl Drop for MenuChild {
fn drop(&mut self) {
if self.item_type == MenuItemType::Submenu {
unsafe {
DestroyMenu(self.hmenu);
DestroyMenu(self.hpopupmenu);
}
}
if self.accelerator.is_some() {
for store in self.root_menu_haccel_stores.values() {
AccelAction::remove(&mut store.borrow_mut(), self.internal_id)
}
}
}
}
/// Constructors /// Constructors
impl MenuChild { impl MenuChild {
pub fn new( pub fn new(
@ -410,8 +463,13 @@ impl MenuChild {
internal_id, internal_id,
id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), id: id.unwrap_or_else(|| MenuId(internal_id.to_string())),
accelerator, accelerator,
root_menu_haccel_stores: Some(Vec::new()), root_menu_haccel_stores: HashMap::new(),
..Default::default() predefined_item_type: None,
icon: None,
checked: false,
children: None,
hmenu: 0,
hpopupmenu: 0,
} }
} }
@ -427,8 +485,11 @@ impl MenuChild {
internal_id, internal_id,
id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), id: id.unwrap_or_else(|| MenuId(internal_id.to_string())),
hpopupmenu: unsafe { CreatePopupMenu() }, hpopupmenu: unsafe { CreatePopupMenu() },
root_menu_haccel_stores: Some(Vec::new()), root_menu_haccel_stores: HashMap::new(),
..Default::default() predefined_item_type: None,
icon: None,
checked: false,
accelerator: None,
} }
} }
@ -442,9 +503,13 @@ impl MenuChild {
internal_id, internal_id,
id: MenuId(internal_id.to_string()), id: MenuId(internal_id.to_string()),
accelerator: item_type.accelerator(), accelerator: item_type.accelerator(),
predefined_item_type: item_type, predefined_item_type: Some(item_type),
root_menu_haccel_stores: Some(Vec::new()), root_menu_haccel_stores: HashMap::new(),
..Default::default() icon: None,
checked: false,
children: None,
hmenu: 0,
hpopupmenu: 0,
} }
} }
@ -465,8 +530,12 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), id: id.unwrap_or_else(|| MenuId(internal_id.to_string())),
accelerator, accelerator,
checked, checked,
root_menu_haccel_stores: Some(Vec::new()), root_menu_haccel_stores: HashMap::new(),
..Default::default() predefined_item_type: None,
icon: None,
children: None,
hmenu: 0,
hpopupmenu: 0,
} }
} }
@ -487,8 +556,12 @@ impl MenuChild {
id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), id: id.unwrap_or_else(|| MenuId(internal_id.to_string())),
accelerator, accelerator,
icon, icon,
root_menu_haccel_stores: Some(Vec::new()), root_menu_haccel_stores: HashMap::new(),
..Default::default() predefined_item_type: None,
checked: false,
children: None,
hmenu: 0,
hpopupmenu: 0,
} }
} }
@ -508,8 +581,13 @@ impl MenuChild {
internal_id, internal_id,
id: id.unwrap_or_else(|| MenuId(internal_id.to_string())), id: id.unwrap_or_else(|| MenuId(internal_id.to_string())),
accelerator, accelerator,
root_menu_haccel_stores: Some(Vec::new()), root_menu_haccel_stores: HashMap::new(),
..Default::default() predefined_item_type: None,
icon: None,
checked: false,
children: None,
hmenu: 0,
hpopupmenu: 0,
} }
} }
} }
@ -556,16 +634,17 @@ impl MenuChild {
} }
pub fn set_text(&mut self, text: &str) { pub fn set_text(&mut self, text: &str) {
self.text = if let Some(accelerator) = self.accelerator { self.text = text.to_string();
format!("{text}\t{}", accelerator) let mut text = if let Some(accelerator) = self.accelerator {
encode_wide(format!("{text}\t{}", accelerator))
} else { } else {
text.to_string() encode_wide(text)
}; };
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(&self.text).as_mut_ptr(); info.dwTypeData = text.as_mut_ptr();
unsafe { SetMenuItemInfoW(*parent, self.internal_id(), false.into(), &info) }; unsafe { SetMenuItemInfoW(*parent, self.internal_id(), false.into(), &info) };
} }
@ -603,8 +682,7 @@ impl MenuChild {
self.accelerator = accelerator; self.accelerator = accelerator;
self.set_text(&self.text.clone()); self.set_text(&self.text.clone());
let haccel_stores = self.root_menu_haccel_stores.as_mut().unwrap(); for store in self.root_menu_haccel_stores.values() {
for store in haccel_stores {
let mut store = store.borrow_mut(); let mut store = store.borrow_mut();
if let Some(accelerator) = self.accelerator { if let Some(accelerator) = self.accelerator {
AccelAction::add(&mut store, self.internal_id, &accelerator)? AccelAction::add(&mut store, self.internal_id, &accelerator)?
@ -676,9 +754,7 @@ impl MenuChild {
child child
.borrow_mut() .borrow_mut()
.root_menu_haccel_stores .root_menu_haccel_stores
.as_mut() .extend(self.root_menu_haccel_stores.clone());
.unwrap()
.extend_from_slice(self.root_menu_haccel_stores.as_ref().unwrap());
} }
{ {
@ -695,7 +771,7 @@ impl MenuChild {
text.push('\t'); text.push('\t');
text.push_str(&accel_str); text.push_str(&accel_str);
for root_menu in self.root_menu_haccel_stores.as_mut().unwrap() { for root_menu in self.root_menu_haccel_stores.values() {
let mut haccel = root_menu.borrow_mut(); let mut haccel = root_menu.borrow_mut();
AccelAction::add(&mut haccel, child_.internal_id(), accelerator)?; AccelAction::add(&mut haccel, child_.internal_id(), accelerator)?;
} }
@ -885,30 +961,21 @@ impl AccelAction {
) -> crate::Result<()> { ) -> crate::Result<()> {
let accel = accelerator.to_accel(id as _)?; let accel = accelerator.to_accel(id as _)?;
haccel_store.1.insert(id, Accel(accel)); haccel_store.1.insert(id, Accel(accel));
Self::update_store(haccel_store); Self::update_store(haccel_store);
Ok(()) Ok(())
} }
fn remove(haccel_store: &mut RefMut<AccelWrapper>, id: u32) { fn remove(haccel_store: &mut RefMut<AccelWrapper>, id: u32) {
haccel_store.1.remove(&id); haccel_store.1.remove(&id);
Self::update_store(haccel_store) Self::update_store(haccel_store)
} }
fn update_store(haccel_store: &mut RefMut<AccelWrapper>) { fn update_store(haccel_store: &mut RefMut<AccelWrapper>) {
unsafe { unsafe {
DestroyAcceleratorTable(haccel_store.0); DestroyAcceleratorTable(haccel_store.0);
haccel_store.0 = CreateAcceleratorTableW( let len = haccel_store.1.len();
haccel_store let accels = haccel_store.1.values().map(|i| i.0).collect::<Vec<_>>();
.1 haccel_store.0 = CreateAcceleratorTableW(accels.as_ptr(), len as _);
.values()
.map(|i| i.0)
.collect::<Vec<_>>()
.as_ptr(),
haccel_store.1.len() as _,
);
} }
} }
} }
@ -962,35 +1029,45 @@ unsafe extern "system" fn menu_subclass_proc(
let checked = !item.checked; let checked = !item.checked;
item.set_checked(checked); item.set_checked(checked);
} }
MenuItemType::Predefined => match &item.predefined_item_type { MenuItemType::Predefined => {
PredefinedMenuItemType::Copy => execute_edit_command(EditCommand::Copy), if let Some(predefined_item_type) = &item.predefined_item_type {
PredefinedMenuItemType::Cut => execute_edit_command(EditCommand::Cut), match predefined_item_type {
PredefinedMenuItemType::Paste => execute_edit_command(EditCommand::Paste), PredefinedMenuItemType::Copy => {
PredefinedMenuItemType::SelectAll => { execute_edit_command(EditCommand::Copy)
execute_edit_command(EditCommand::SelectAll) }
} PredefinedMenuItemType::Cut => {
PredefinedMenuItemType::Separator => {} execute_edit_command(EditCommand::Cut)
PredefinedMenuItemType::Minimize => { }
ShowWindow(hwnd, SW_MINIMIZE); PredefinedMenuItemType::Paste => {
} execute_edit_command(EditCommand::Paste)
PredefinedMenuItemType::Maximize => { }
ShowWindow(hwnd, SW_MAXIMIZE); PredefinedMenuItemType::SelectAll => {
} execute_edit_command(EditCommand::SelectAll)
PredefinedMenuItemType::Hide => { }
ShowWindow(hwnd, SW_HIDE); PredefinedMenuItemType::Separator => {}
} PredefinedMenuItemType::Minimize => {
PredefinedMenuItemType::CloseWindow => { ShowWindow(hwnd, SW_MINIMIZE);
SendMessageW(hwnd, WM_CLOSE, 0, 0); }
} PredefinedMenuItemType::Maximize => {
PredefinedMenuItemType::Quit => { ShowWindow(hwnd, SW_MAXIMIZE);
PostQuitMessage(0); }
} PredefinedMenuItemType::Hide => {
PredefinedMenuItemType::About(Some(ref metadata)) => { ShowWindow(hwnd, SW_HIDE);
show_about_dialog(hwnd, metadata) }
} PredefinedMenuItemType::CloseWindow => {
SendMessageW(hwnd, WM_CLOSE, 0, 0);
}
PredefinedMenuItemType::Quit => {
PostQuitMessage(0);
}
PredefinedMenuItemType::About(Some(ref metadata)) => {
show_about_dialog(hwnd, metadata)
}
_ => {} _ => {}
}, }
}
}
_ => {} _ => {}
} }
} }