fix(linux): add submenus, text items, native items at runtime

This commit is contained in:
amrbashir 2022-06-10 15:11:02 +02:00
parent 943beda6df
commit 7520e19645
No known key found for this signature in database
GPG key ID: BBD7A47A2003FF33

View file

@ -20,8 +20,8 @@ struct MenuEntry {
// multiple times, and thus can't be used in multiple windows, each entry // multiple times, and thus can't be used in multiple windows, each entry
// keeps a vector of a [`gtk::MenuItem`] or a tuple of [`gtk::MenuItem`] and [`gtk::Menu`] if its a menu // keeps a vector of a [`gtk::MenuItem`] or a tuple of [`gtk::MenuItem`] and [`gtk::Menu`] if its a menu
// and push to it every time [`Menu::init_for_gtk_window`] is called. // and push to it every time [`Menu::init_for_gtk_window`] is called.
native_items: Option<Rc<RefCell<Vec<gtk::MenuItem>>>>, native_items: Option<Vec<gtk::MenuItem>>,
native_menus: Option<Rc<RefCell<Vec<(gtk::MenuItem, gtk::Menu)>>>>, native_menus: Option<Vec<(gtk::MenuItem, gtk::Menu)>>,
entries: Option<Vec<Rc<RefCell<MenuEntry>>>>, entries: Option<Vec<Rc<RefCell<MenuEntry>>>>,
} }
@ -45,7 +45,7 @@ struct InnerMenu {
// keeps a hashmap of window pointer as the key and a tuple of [`gtk::MenuBar`] and [`gtk::Box`] as the value // keeps a hashmap of window pointer as the key and a tuple of [`gtk::MenuBar`] and [`gtk::Box`] as the value
// and push to it every time `Menu::init_for_gtk_window` is called. // and push to it every time `Menu::init_for_gtk_window` is called.
native_menus: HashMap<isize, (Option<gtk::MenuBar>, Rc<gtk::Box>)>, native_menus: HashMap<isize, (Option<gtk::MenuBar>, Rc<gtk::Box>)>,
accel_group: gtk::AccelGroup, accel_group: Rc<gtk::AccelGroup>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -56,21 +56,38 @@ impl Menu {
Self(Rc::new(RefCell::new(InnerMenu { Self(Rc::new(RefCell::new(InnerMenu {
entries: Vec::new(), entries: Vec::new(),
native_menus: HashMap::new(), native_menus: HashMap::new(),
accel_group: gtk::AccelGroup::new(), accel_group: Rc::new(gtk::AccelGroup::new()),
}))) })))
} }
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu { pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
let label = label.as_ref().to_string();
let entry = Rc::new(RefCell::new(MenuEntry { let entry = Rc::new(RefCell::new(MenuEntry {
label: label.as_ref().to_string(), label: label.clone(),
enabled, enabled,
entries: Some(Vec::new()), entries: Some(Vec::new()),
r#type: MenuEntryType::Submenu, r#type: MenuEntryType::Submenu,
native_menus: Some(Rc::new(RefCell::new(Vec::new()))), native_menus: Some(Vec::new()),
..Default::default() ..Default::default()
})); }));
self.0.borrow_mut().entries.push(entry.clone());
Submenu(entry) let mut inner = self.0.borrow_mut();
for (_, (menu_bar, _)) in inner.native_menus.iter() {
if let Some(menu_bar) = menu_bar {
let (item, submenu) = create_gtk_submenu(&label, enabled);
menu_bar.append(&item);
entry
.borrow_mut()
.native_menus
.as_mut()
.unwrap()
.push((item, submenu));
}
}
inner.entries.push(entry.clone());
Submenu(entry, Rc::clone(&inner.accel_group))
} }
pub fn init_for_gtk_window<W>(&self, window: &W) -> Rc<gtk::Box> pub fn init_for_gtk_window<W>(&self, window: &W) -> Rc<gtk::Box>
@ -87,6 +104,7 @@ impl Menu {
let menu_bar = gtk::MenuBar::new(); let menu_bar = gtk::MenuBar::new();
let vbox = gtk::Box::new(Orientation::Vertical, 0); let vbox = gtk::Box::new(Orientation::Vertical, 0);
window.add(&vbox); window.add(&vbox);
vbox.show();
inner inner
.native_menus .native_menus
.insert(window.as_ptr() as _, (Some(menu_bar), Rc::new(vbox))); .insert(window.as_ptr() as _, (Some(menu_bar), Rc::new(vbox)));
@ -112,11 +130,11 @@ impl Menu {
&inner.entries, &inner.entries,
&inner.accel_group, &inner.accel_group,
); );
window.add_accel_group(&inner.accel_group); window.add_accel_group(&*inner.accel_group);
// Show the menubar on the window // Show the menubar on the window
vbox.pack_start(menu_bar.as_ref().unwrap(), false, false, 0); vbox.pack_start(menu_bar.as_ref().unwrap(), false, false, 0);
vbox.show_all(); menu_bar.as_ref().unwrap().show();
Rc::clone(vbox) Rc::clone(vbox)
} }
@ -132,7 +150,7 @@ impl Menu {
// Remove the [`gtk::Menubar`] from the widget tree // Remove the [`gtk::Menubar`] from the widget tree
unsafe { menu_bar.destroy() }; unsafe { menu_bar.destroy() };
// Detach the accelerators from the window // Detach the accelerators from the window
window.remove_accel_group(&inner.accel_group); window.remove_accel_group(&*inner.accel_group);
// Remove the removed [`gtk::Menubar`] from our cache // Remove the removed [`gtk::Menubar`] from our cache
let vbox = Rc::clone(vbox); let vbox = Rc::clone(vbox);
inner inner
@ -171,7 +189,7 @@ impl Menu {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Submenu(Rc<RefCell<MenuEntry>>); pub struct Submenu(Rc<RefCell<MenuEntry>>, Rc<gtk::AccelGroup>);
impl Submenu { impl Submenu {
pub fn label(&self) -> String { pub fn label(&self) -> String {
@ -181,7 +199,7 @@ impl Submenu {
pub fn set_label(&mut self, label: impl AsRef<str>) { pub fn set_label(&mut self, label: impl AsRef<str>) {
let label = label.as_ref().to_string(); let label = label.as_ref().to_string();
let mut entry = self.0.borrow_mut(); let mut entry = self.0.borrow_mut();
for (item, _) in entry.native_menus.as_ref().unwrap().borrow().iter() { for (item, _) in entry.native_menus.as_ref().unwrap() {
item.set_label(&to_gtk_menemenoic(&label)); item.set_label(&to_gtk_menemenoic(&label));
} }
entry.label = label; entry.label = label;
@ -194,27 +212,37 @@ impl Submenu {
pub fn set_enabled(&mut self, enabled: bool) { pub fn set_enabled(&mut self, enabled: bool) {
let mut entry = self.0.borrow_mut(); let mut entry = self.0.borrow_mut();
entry.enabled = true; entry.enabled = true;
for (item, _) in entry.native_menus.as_ref().unwrap().borrow().iter() { for (item, _) in entry.native_menus.as_ref().unwrap() {
item.set_sensitive(enabled); item.set_sensitive(enabled);
} }
} }
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu { pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
let label = label.as_ref().to_string();
let entry = Rc::new(RefCell::new(MenuEntry { let entry = Rc::new(RefCell::new(MenuEntry {
label: label.as_ref().to_string(), label: label.clone(),
enabled, enabled,
entries: Some(Vec::new()), entries: Some(Vec::new()),
r#type: MenuEntryType::Submenu, r#type: MenuEntryType::Submenu,
native_menus: Some(Rc::new(RefCell::new(Vec::new()))), native_menus: Some(Vec::new()),
..Default::default() ..Default::default()
})); }));
self.0
let mut inner = self.0.borrow_mut();
for (_, menu) in inner.native_menus.as_ref().unwrap() {
let (item, submenu) = create_gtk_submenu(&label, enabled);
menu.append(&item);
entry
.borrow_mut() .borrow_mut()
.entries .native_menus
.as_mut() .as_mut()
.unwrap() .unwrap()
.push(entry.clone()); .push((item, submenu));
Submenu(entry) }
inner.entries.as_mut().unwrap().push(entry.clone());
Submenu(entry, Rc::clone(&self.1))
} }
pub fn add_text_item( pub fn add_text_item(
@ -223,31 +251,50 @@ impl Submenu {
enabled: bool, enabled: bool,
accelerator: Option<&str>, accelerator: Option<&str>,
) -> TextMenuItem { ) -> TextMenuItem {
let label = label.as_ref().to_string();
let id = COUNTER.next();
let entry = Rc::new(RefCell::new(MenuEntry { let entry = Rc::new(RefCell::new(MenuEntry {
label: label.as_ref().to_string(), label: label.clone(),
enabled, enabled,
r#type: MenuEntryType::Text, r#type: MenuEntryType::Text,
item_id: Some(COUNTER.next()), item_id: Some(id),
accelerator: accelerator.map(|s| s.to_string()), accelerator: accelerator.map(|s| s.to_string()),
native_items: Some(Rc::new(RefCell::new(Vec::new()))), native_items: Some(Vec::new()),
..Default::default() ..Default::default()
})); }));
self.0
.borrow_mut() let mut inner = self.0.borrow_mut();
.entries
.as_mut() for (_, menu) in inner.native_menus.as_ref().unwrap() {
.unwrap() let item = create_gtk_text_menu_item(
.push(entry.clone()); &label,
enabled,
&accelerator.map(|s| s.to_string()),
id,
&*self.1,
);
menu.append(&item);
entry.borrow_mut().native_items.as_mut().unwrap().push(item);
}
inner.entries.as_mut().unwrap().push(entry.clone());
TextMenuItem(entry) TextMenuItem(entry)
} }
pub fn add_native_item(&mut self, item: NativeMenuItem) { pub fn add_native_item(&mut self, item: NativeMenuItem) {
let mut inner = self.0.borrow_mut();
for (_, menu) in inner.native_menus.as_ref().unwrap() {
item.add_to_gtk_menu(menu);
}
let entry = Rc::new(RefCell::new(MenuEntry { let entry = Rc::new(RefCell::new(MenuEntry {
r#type: MenuEntryType::Native, r#type: MenuEntryType::Native,
native_menu_item: Some(item), native_menu_item: Some(item),
..Default::default() ..Default::default()
})); }));
self.0.borrow_mut().entries.as_mut().unwrap().push(entry); inner.entries.as_mut().unwrap().push(entry);
} }
} }
@ -262,7 +309,7 @@ impl TextMenuItem {
pub fn set_label(&mut self, label: impl AsRef<str>) { pub fn set_label(&mut self, label: impl AsRef<str>) {
let label = label.as_ref().to_string(); let label = label.as_ref().to_string();
let mut entry = self.0.borrow_mut(); let mut entry = self.0.borrow_mut();
for item in entry.native_items.as_ref().unwrap().borrow().iter() { for item in entry.native_items.as_ref().unwrap() {
item.set_label(&to_gtk_menemenoic(&label)); item.set_label(&to_gtk_menemenoic(&label));
} }
entry.label = label; entry.label = label;
@ -274,7 +321,7 @@ impl TextMenuItem {
pub fn set_enabled(&mut self, enabled: bool) { pub fn set_enabled(&mut self, enabled: bool) {
let mut entry = self.0.borrow_mut(); let mut entry = self.0.borrow_mut();
for item in entry.native_items.as_ref().unwrap().borrow().iter() { for item in entry.native_items.as_ref().unwrap() {
item.set_sensitive(enabled); item.set_sensitive(enabled);
} }
entry.enabled = enabled; entry.enabled = enabled;
@ -285,37 +332,61 @@ impl TextMenuItem {
} }
} }
fn add_entries_to_menu<M>( fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(
gtk_menu: &M, gtk_menu: &M,
entries: &Vec<Rc<RefCell<MenuEntry>>>, entries: &Vec<Rc<RefCell<MenuEntry>>>,
accel_group: &gtk::AccelGroup, accel_group: &gtk::AccelGroup,
) where ) {
M: IsA<gtk::MenuShell>,
{
for entry in entries { for entry in entries {
let mut entry = entry.borrow_mut(); let mut entry = entry.borrow_mut();
match entry.r#type { match entry.r#type {
MenuEntryType::Submenu => { MenuEntryType::Submenu => {
let gtk_item = gtk::MenuItem::with_mnemonic(&to_gtk_menemenoic(&entry.label)); let (item, submenu) = create_gtk_submenu(&entry.label, entry.enabled);
gtk_menu.append(&gtk_item); gtk_menu.append(&item);
gtk_item.set_sensitive(entry.enabled); add_entries_to_menu(&submenu, entry.entries.as_ref().unwrap(), accel_group);
let gtk_menu = gtk::Menu::new(); entry.native_menus.as_mut().unwrap().push((item, submenu));
gtk_item.set_submenu(Some(&gtk_menu));
add_entries_to_menu(&gtk_menu, entry.entries.as_ref().unwrap(), accel_group);
entry
.native_menus
.as_mut()
.unwrap()
.borrow_mut()
.push((gtk_item, gtk_menu));
} }
MenuEntryType::Text => { MenuEntryType::Text => {
let gtk_item = gtk::MenuItem::with_mnemonic(&to_gtk_menemenoic(&entry.label)); let item = create_gtk_text_menu_item(
gtk_menu.append(&gtk_item); &entry.label,
gtk_item.set_sensitive(entry.enabled); entry.enabled,
if let Some(accelerator) = &entry.accelerator { &entry.accelerator,
entry.item_id.unwrap(),
accel_group,
);
gtk_menu.append(&item);
entry.native_items.as_mut().unwrap().push(item);
}
MenuEntryType::Native => entry
.native_menu_item
.as_ref()
.unwrap()
.add_to_gtk_menu(gtk_menu),
}
}
}
fn create_gtk_submenu(label: &str, enabled: bool) -> (gtk::MenuItem, gtk::Menu) {
let item = gtk::MenuItem::with_mnemonic(&to_gtk_menemenoic(label));
item.set_sensitive(enabled);
let menu = gtk::Menu::new();
item.set_submenu(Some(&menu));
item.show();
(item, menu)
}
fn create_gtk_text_menu_item(
label: &str,
enabled: bool,
accelerator: &Option<String>,
id: u64,
accel_group: &gtk::AccelGroup,
) -> gtk::MenuItem {
let item = gtk::MenuItem::with_mnemonic(&to_gtk_menemenoic(label));
item.set_sensitive(enabled);
if let Some(accelerator) = accelerator {
let (key, modifiers) = gtk::accelerator_parse(&to_gtk_accelerator(accelerator)); let (key, modifiers) = gtk::accelerator_parse(&to_gtk_accelerator(accelerator));
gtk_item.add_accelerator( item.add_accelerator(
"activate", "activate",
accel_group, accel_group,
key, key,
@ -323,117 +394,116 @@ fn add_entries_to_menu<M>(
gtk::AccelFlags::VISIBLE, gtk::AccelFlags::VISIBLE,
); );
} }
item.connect_activate(move |_| {
let id = entry.item_id.unwrap();
gtk_item.connect_activate(move |_| {
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id }); let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
}); });
entry item.show();
.native_items item
.as_mut() }
.unwrap()
.borrow_mut() impl NativeMenuItem {
.push(gtk_item); fn add_to_gtk_menu<M: IsA<gtk::MenuShell>>(&self, gtk_menu: &M) {
} match self {
MenuEntryType::Native => match entry.native_menu_item.as_ref().unwrap() {
NativeMenuItem::Copy => { NativeMenuItem::Copy => {
let gtk_item = gtk::MenuItem::with_mnemonic("_Copy"); let item = gtk::MenuItem::with_mnemonic("_Copy");
let (key, modifiers) = gtk::accelerator_parse("<Ctrl>X"); let (key, modifiers) = gtk::accelerator_parse("<Ctrl>X");
gtk_item item.child()
.child()
.unwrap() .unwrap()
.downcast::<gtk::AccelLabel>() .downcast::<gtk::AccelLabel>()
.unwrap() .unwrap()
.set_accel(key, modifiers); .set_accel(key, modifiers);
gtk_item.connect_activate(move |_| { item.connect_activate(move |_| {
// TODO: wayland // TODO: wayland
if let Ok(xdo) = libxdo::XDo::new(None) { if let Ok(xdo) = libxdo::XDo::new(None) {
let _ = xdo.send_keysequence("ctrl+c", 0); let _ = xdo.send_keysequence("ctrl+c", 0);
} }
}); });
gtk_menu.append(&gtk_item); item.show();
gtk_menu.append(&item);
} }
NativeMenuItem::Cut => { NativeMenuItem::Cut => {
let gtk_item = gtk::MenuItem::with_mnemonic("Cu_t"); let item = gtk::MenuItem::with_mnemonic("Cu_t");
let (key, modifiers) = gtk::accelerator_parse("<Ctrl>X"); let (key, modifiers) = gtk::accelerator_parse("<Ctrl>X");
gtk_item item.child()
.child()
.unwrap() .unwrap()
.downcast::<gtk::AccelLabel>() .downcast::<gtk::AccelLabel>()
.unwrap() .unwrap()
.set_accel(key, modifiers); .set_accel(key, modifiers);
gtk_item.connect_activate(move |_| { item.connect_activate(move |_| {
// TODO: wayland // TODO: wayland
if let Ok(xdo) = libxdo::XDo::new(None) { if let Ok(xdo) = libxdo::XDo::new(None) {
let _ = xdo.send_keysequence("ctrl+x", 0); let _ = xdo.send_keysequence("ctrl+x", 0);
} }
}); });
gtk_menu.append(&gtk_item); item.show();
gtk_menu.append(&item);
} }
NativeMenuItem::Paste => { NativeMenuItem::Paste => {
let gtk_item = gtk::MenuItem::with_mnemonic("_Paste"); let item = gtk::MenuItem::with_mnemonic("_Paste");
let (key, modifiers) = gtk::accelerator_parse("<Ctrl>V"); let (key, modifiers) = gtk::accelerator_parse("<Ctrl>V");
gtk_item item.child()
.child()
.unwrap() .unwrap()
.downcast::<gtk::AccelLabel>() .downcast::<gtk::AccelLabel>()
.unwrap() .unwrap()
.set_accel(key, modifiers); .set_accel(key, modifiers);
gtk_item.connect_activate(move |_| { item.connect_activate(move |_| {
// TODO: wayland // TODO: wayland
if let Ok(xdo) = libxdo::XDo::new(None) { if let Ok(xdo) = libxdo::XDo::new(None) {
let _ = xdo.send_keysequence("ctrl+v", 0); let _ = xdo.send_keysequence("ctrl+v", 0);
} }
}); });
gtk_menu.append(&gtk_item); item.show();
gtk_menu.append(&item);
} }
NativeMenuItem::SelectAll => { NativeMenuItem::SelectAll => {
let gtk_item = gtk::MenuItem::with_mnemonic("Select _All"); let item = gtk::MenuItem::with_mnemonic("Select _All");
let (key, modifiers) = gtk::accelerator_parse("<Ctrl>A"); let (key, modifiers) = gtk::accelerator_parse("<Ctrl>A");
gtk_item item.child()
.child()
.unwrap() .unwrap()
.downcast::<gtk::AccelLabel>() .downcast::<gtk::AccelLabel>()
.unwrap() .unwrap()
.set_accel(key, modifiers); .set_accel(key, modifiers);
gtk_item.connect_activate(move |_| { item.connect_activate(move |_| {
// TODO: wayland // TODO: wayland
if let Ok(xdo) = libxdo::XDo::new(None) { if let Ok(xdo) = libxdo::XDo::new(None) {
let _ = xdo.send_keysequence("ctrl+a", 0); let _ = xdo.send_keysequence("ctrl+a", 0);
} }
}); });
gtk_menu.append(&gtk_item); item.show();
gtk_menu.append(&item);
} }
NativeMenuItem::Separator => { NativeMenuItem::Separator => {
gtk_menu.append(&gtk::SeparatorMenuItem::new()); gtk_menu.append(&gtk::SeparatorMenuItem::new());
} }
NativeMenuItem::Minimize => { NativeMenuItem::Minimize => {
let gtk_item = gtk::MenuItem::with_mnemonic("_Minimize"); let item = gtk::MenuItem::with_mnemonic("_Minimize");
gtk_item.connect_activate(move |m| { item.connect_activate(move |m| {
if let Some(window) = m.window() { if let Some(window) = m.window() {
window.iconify() window.iconify()
} }
}); });
gtk_menu.append(&gtk_item); item.show();
gtk_menu.append(&item);
} }
NativeMenuItem::CloseWindow => { NativeMenuItem::CloseWindow => {
let gtk_item = gtk::MenuItem::with_mnemonic("C_lose Window"); let item = gtk::MenuItem::with_mnemonic("C_lose Window");
gtk_item.connect_activate(move |m| { item.connect_activate(move |m| {
if let Some(window) = m.window() { if let Some(window) = m.window() {
window.destroy() window.destroy()
} }
}); });
gtk_menu.append(&gtk_item); item.show();
gtk_menu.append(&item);
} }
NativeMenuItem::Quit => { NativeMenuItem::Quit => {
let gtk_item = gtk::MenuItem::with_mnemonic("_Quit"); let item = gtk::MenuItem::with_mnemonic("_Quit");
gtk_item.connect_activate(move |_| { item.connect_activate(move |_| {
std::process::exit(0); std::process::exit(0);
}); });
gtk_menu.append(&gtk_item); item.show();
gtk_menu.append(&item);
} }
_ => {} _ => {}
},
} }
} }
} }