use crate::id::Id;
use crate::menus::menuitem::{ItemKind, MenuItem};
#[cfg(feature = "xrc")]
use crate::window::WindowHandle;
use crate::{CommandEventData, Event, EventType};
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::rc::Rc;
use wxdragon_sys as ffi;
pub struct Menu {
ptr: *mut ffi::wxd_Menu_t,
owned: bool,
_nosend_nosync: PhantomData<Rc<()>>,
}
impl Drop for Menu {
fn drop(&mut self) {
self.destroy_menu();
}
}
impl From<*mut ffi::wxd_Menu_t> for Menu {
fn from(ptr: *mut ffi::wxd_Menu_t) -> Self {
assert!(!ptr.is_null(), "invalid null pointer passed to Menu::from");
Menu {
ptr,
owned: true,
_nosend_nosync: PhantomData,
}
}
}
impl From<*const ffi::wxd_Menu_t> for Menu {
fn from(ptr: *const ffi::wxd_Menu_t) -> Self {
assert!(!ptr.is_null(), "invalid null pointer passed to Menu::from");
let ptr = ptr as *mut ffi::wxd_Menu_t;
Menu {
ptr,
owned: false,
_nosend_nosync: PhantomData,
}
}
}
impl TryFrom<Menu> for *const ffi::wxd_Menu_t {
type Error = std::io::Error;
fn try_from(value: Menu) -> Result<Self, Self::Error> {
use std::io::{Error, ErrorKind::InvalidData, ErrorKind::InvalidInput};
if value.ptr.is_null() {
return Err(Error::new(InvalidInput, "Menu pointer is null"));
}
if value.is_owned() {
return Err(Error::new(
InvalidData,
"Menu owns the pointer, use into_raw_mut or mutable version",
));
}
let ptr = value.ptr as *const _;
std::mem::forget(value);
Ok(ptr)
}
}
impl TryFrom<Menu> for *mut ffi::wxd_Menu_t {
type Error = std::io::Error;
fn try_from(value: Menu) -> Result<Self, Self::Error> {
use std::io::{Error, ErrorKind::InvalidData, ErrorKind::InvalidInput};
if value.ptr.is_null() {
return Err(Error::new(InvalidInput, "Menu pointer is null"));
}
if !value.is_owned() {
return Err(Error::new(InvalidData, "Menu does not own the pointer, use const version"));
}
let ptr = value.ptr;
std::mem::forget(value);
Ok(ptr)
}
}
impl Menu {
pub fn builder() -> MenuBuilder {
MenuBuilder::new()
}
pub fn is_owned(&self) -> bool {
self.owned
}
pub fn get_item_count(&self) -> usize {
unsafe { ffi::wxd_Menu_GetMenuItemCount(self.ptr) }
}
pub fn get_title(&self) -> Option<String> {
let size = unsafe { ffi::wxd_Menu_GetTitle(self.ptr, std::ptr::null_mut(), 0) };
if size < 0 {
return None;
}
let mut buffer = vec![0; size as usize + 1]; unsafe { ffi::wxd_Menu_GetTitle(self.ptr, buffer.as_mut_ptr(), buffer.len()) };
Some(unsafe { CStr::from_ptr(buffer.as_ptr()).to_string_lossy().to_string() })
}
pub fn set_title(&mut self, title: &str) {
let title_c = CString::new(title).unwrap_or_default();
unsafe { ffi::wxd_Menu_SetTitle(self.as_mut_ptr(), title_c.as_ptr()) };
}
pub fn destroy_menu(&mut self) {
if self.owned && !self.ptr.is_null() {
log::debug!("Menu '{:?}' destroyed", self.get_title());
unsafe { ffi::wxd_Menu_Destroy(self.ptr) };
self.ptr = std::ptr::null_mut();
}
}
pub fn as_const_ptr(&self) -> *const ffi::wxd_Menu_t {
self.ptr as *const _
}
pub fn as_mut_ptr(&mut self) -> *mut ffi::wxd_Menu_t {
self.ptr
}
pub fn into_raw_mut(self) -> *mut ffi::wxd_Menu_t {
self.try_into().expect("into_raw_mut must only be used on owning wrappers")
}
pub fn into_raw_const(self) -> *const ffi::wxd_Menu_t {
self.try_into()
.expect("into_raw_const must only be used on non-owning wrappers")
}
pub fn append(&self, id: Id, item: &str, help_string: &str, kind: ItemKind) -> Option<MenuItem> {
self.append_raw(id, item, help_string, kind)
}
pub fn append_submenu(&self, submenu: Menu, title: &str, help_string: &str) -> Option<MenuItem> {
let title = CString::new(title).unwrap_or_default();
let help_c = CString::new(help_string).unwrap_or_default();
let item_ptr = unsafe { ffi::wxd_Menu_AppendSubMenu(self.ptr, submenu.into_raw_mut(), title.as_ptr(), help_c.as_ptr()) };
if item_ptr.is_null() {
return None;
}
Some(MenuItem::from(item_ptr))
}
pub fn enable_item(&self, id: Id, enable: bool) -> bool {
unsafe { ffi::wxd_Menu_ItemEnable(self.ptr, id, enable) }
}
pub fn is_item_enabled(&self, id: Id) -> bool {
unsafe { ffi::wxd_Menu_IsItemEnabled(self.ptr, id) }
}
pub fn check_item(&self, id: Id, check: bool) {
unsafe { ffi::wxd_Menu_CheckItem(self.ptr, id, check) }
}
pub fn is_item_checked(&self, id: Id) -> bool {
unsafe { ffi::wxd_Menu_IsItemChecked(self.ptr, id) }
}
pub fn find_item(&self, id: Id) -> Option<MenuItem> {
let ptr = unsafe { ffi::wxd_Menu_FindItem(self.ptr, id) };
if ptr.is_null() { None } else { Some(MenuItem::from_ptr(ptr)) }
}
pub fn append_separator(&self) {
self.append_separator_raw();
}
pub fn insert(&self, pos: usize, id: Id, item: &str, help_string: &str, kind: ItemKind) -> Option<MenuItem> {
let item_c = CString::new(item).unwrap_or_default();
let help_c = CString::new(help_string).unwrap_or_default();
let item_ptr = unsafe { ffi::wxd_Menu_Insert(self.ptr, pos, id, item_c.as_ptr(), help_c.as_ptr(), kind.into()) };
if item_ptr.is_null() {
None
} else {
Some(MenuItem::from_ptr(item_ptr))
}
}
pub fn insert_submenu(&self, pos: usize, submenu: Menu, title: &str, help_string: &str) -> Option<MenuItem> {
let title = CString::new(title).unwrap_or_default();
let help_c = CString::new(help_string).unwrap_or_default();
let item_ptr =
unsafe { ffi::wxd_Menu_InsertSubMenu(self.ptr, pos, submenu.into_raw_mut(), title.as_ptr(), help_c.as_ptr()) };
if item_ptr.is_null() {
return None;
}
Some(MenuItem::from(item_ptr))
}
pub fn insert_separator(&self, pos: usize) -> Option<MenuItem> {
let item_ptr = unsafe { ffi::wxd_Menu_InsertSeparator(self.ptr, pos) };
if item_ptr.is_null() {
None
} else {
Some(MenuItem::from_ptr(item_ptr))
}
}
pub fn prepend(&self, id: Id, item: &str, help_string: &str, kind: ItemKind) -> Option<MenuItem> {
let item_c = CString::new(item).unwrap_or_default();
let help_c = CString::new(help_string).unwrap_or_default();
let item_ptr = unsafe { ffi::wxd_Menu_Prepend(self.ptr, id, item_c.as_ptr(), help_c.as_ptr(), kind.into()) };
if item_ptr.is_null() {
None
} else {
Some(MenuItem::from_ptr(item_ptr))
}
}
pub fn prepend_submenu(&self, submenu: Menu, title: &str, help_string: &str) -> Option<MenuItem> {
let title = CString::new(title).unwrap_or_default();
let help_c = CString::new(help_string).unwrap_or_default();
let item_ptr = unsafe { ffi::wxd_Menu_PrependSubMenu(self.ptr, submenu.into_raw_mut(), title.as_ptr(), help_c.as_ptr()) };
if item_ptr.is_null() {
return None;
}
Some(MenuItem::from(item_ptr))
}
pub fn prepend_separator(&self) -> Option<MenuItem> {
let item_ptr = unsafe { ffi::wxd_Menu_PrependSeparator(self.ptr) };
if item_ptr.is_null() {
None
} else {
Some(MenuItem::from_ptr(item_ptr))
}
}
pub fn remove(&self, id: Id) -> Option<MenuItem> {
let ptr = unsafe { ffi::wxd_Menu_Remove(self.ptr, id) };
if ptr.is_null() { None } else { Some(MenuItem::from(ptr)) }
}
pub fn remove_item(&self, item: &MenuItem) -> Option<MenuItem> {
let raw_item = item.as_const_ptr() as *mut ffi::wxd_MenuItem_t;
let ptr = unsafe { ffi::wxd_Menu_RemoveItem(self.ptr, raw_item) };
if ptr.is_null() { None } else { Some(MenuItem::from(ptr)) }
}
pub fn delete(&self, id: Id) -> bool {
unsafe { ffi::wxd_Menu_Delete(self.ptr, id) }
}
pub fn delete_item(&self, item: &MenuItem) -> bool {
unsafe { ffi::wxd_Menu_DeleteItem(self.ptr, item.as_const_ptr() as *mut _) }
}
pub fn find_item_by_position(&self, pos: usize) -> Option<MenuItem> {
let ptr = unsafe { ffi::wxd_Menu_FindItemByPosition(self.ptr, pos) };
if ptr.is_null() { None } else { Some(MenuItem::from_ptr(ptr)) }
}
pub fn get_help_string(&self, id: Id) -> String {
let len = unsafe { ffi::wxd_Menu_GetHelpString(self.ptr, id, std::ptr::null_mut(), 0) };
if len < 0 {
return String::new();
}
let mut buffer = vec![0u8; len as usize + 1];
unsafe { ffi::wxd_Menu_GetHelpString(self.ptr, id, buffer.as_mut_ptr() as *mut _, buffer.len()) };
unsafe { CStr::from_ptr(buffer.as_ptr() as *const _).to_string_lossy().into_owned() }
}
pub fn set_help_string(&self, id: Id, help_string: &str) {
let c_help = CString::new(help_string).unwrap_or_default();
unsafe { ffi::wxd_Menu_SetHelpString(self.ptr, id, c_help.as_ptr()) }
}
pub fn update_ui(&self, source: Option<&impl crate::event::WxEvtHandler>) {
let source_ptr = source
.map(|s| unsafe { s.get_event_handler_ptr() })
.unwrap_or(std::ptr::null_mut());
unsafe { ffi::wxd_Menu_UpdateUI(self.ptr, source_ptr) }
}
pub fn insert_break(&self) {
unsafe { ffi::wxd_Menu_Break(self.ptr) }
}
pub fn get_menu_items(&self) -> Vec<MenuItem> {
let count = self.get_item_count();
let mut items = Vec::with_capacity(count);
for i in 0..count {
if let Some(item) = self.find_item_by_position(i) {
items.push(item);
}
}
items
}
#[cfg(feature = "xrc")]
pub fn get_item_by_name(&self, parent_handle: WindowHandle, item_name: &str) -> Option<MenuItem> {
MenuItem::from_xrc_name(parent_handle, item_name)
}
fn append_raw(&self, id: Id, item: &str, help_string: &str, kind: ItemKind) -> Option<MenuItem> {
let item_c = CString::new(item).unwrap_or_default();
let help_c = CString::new(help_string).unwrap_or_default();
let item_ptr = unsafe { ffi::wxd_Menu_Append(self.ptr, id, item_c.as_ptr(), help_c.as_ptr(), kind.into()) };
if item_ptr.is_null() {
None
} else {
Some(MenuItem::from_ptr(item_ptr))
}
}
fn append_separator_raw(&self) {
unsafe { ffi::wxd_Menu_AppendSeparator(self.ptr) };
}
}
enum MenuAction {
AppendItem {
id: Id,
item: String,
help: String,
kind: ItemKind,
},
AppendSeparator,
}
#[derive(Default)]
pub struct MenuBuilder {
actions: Vec<MenuAction>,
title: String,
_marker: PhantomData<()>,
}
impl MenuBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn with_title(mut self, title: &str) -> Self {
self.title = title.to_string();
self
}
pub fn append_item(mut self, id: Id, item: &str, help: &str) -> Self {
self.actions.push(MenuAction::AppendItem {
id,
item: item.to_string(),
help: help.to_string(),
kind: ItemKind::Normal,
});
self
}
pub fn append_check_item(mut self, id: Id, item: &str, help: &str) -> Self {
self.actions.push(MenuAction::AppendItem {
id,
item: item.to_string(),
help: help.to_string(),
kind: ItemKind::Check,
});
self
}
pub fn append_radio_item(mut self, id: Id, item: &str, help: &str) -> Self {
self.actions.push(MenuAction::AppendItem {
id,
item: item.to_string(),
help: help.to_string(),
kind: ItemKind::Radio,
});
self
}
pub fn append_separator(mut self) -> Self {
self.actions.push(MenuAction::AppendSeparator);
self
}
pub fn build(self) -> Menu {
let title_c = CString::new(self.title).unwrap();
let style = 0i64;
let ptr = unsafe { ffi::wxd_Menu_Create(title_c.as_ptr(), style as ffi::wxd_Style_t) };
if ptr.is_null() {
panic!("Failed to create Menu");
}
let menu = Menu {
ptr,
owned: true,
_nosend_nosync: PhantomData,
};
for action in self.actions {
match action {
MenuAction::AppendItem { id, item, help, kind } => {
let _ = menu.append_raw(id, &item, &help, kind);
}
MenuAction::AppendSeparator => {
menu.append_separator_raw();
}
}
}
menu
}
}
#[cfg(feature = "xrc")]
impl crate::xrc::XrcSupport for Menu {
unsafe fn from_xrc_ptr(ptr: *mut wxdragon_sys::wxd_Window_t) -> Self {
let ptr = ptr as *mut wxdragon_sys::wxd_Menu_t;
Self {
ptr,
owned: false,
_nosend_nosync: PhantomData,
}
}
}
impl crate::window::WxWidget for Menu {
fn handle_ptr(&self) -> *mut wxdragon_sys::wxd_Window_t {
self.ptr as *mut wxdragon_sys::wxd_Window_t
}
fn get_id(&self) -> i32 {
-1 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MenuEvent {
Selected,
}
#[derive(Debug)]
pub struct MenuEventData {
pub event: CommandEventData,
}
impl MenuEventData {
pub fn new(event: Event) -> Self {
Self {
event: CommandEventData::new(event),
}
}
pub fn get_id(&self) -> i32 {
self.event.get_id()
}
}
impl crate::event::WxEvtHandler for Menu {
unsafe fn get_event_handler_ptr(&self) -> *mut wxdragon_sys::wxd_EvtHandler_t {
self.ptr as *mut ffi::wxd_EvtHandler_t
}
}
crate::implement_widget_local_event_handlers!(
Menu,
MenuEvent,
MenuEventData,
Selected => selected, EventType::MENU
);