diff --git a/examples/Cargo.toml b/examples/Cargo.toml index c703b6bf524f..3126f24439c6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -51,3 +51,7 @@ path = "object_subclass/main.rs" [[bin]] name = "virtual_methods" path = "virtual_methods/main.rs" + +[[bin]] +name = "gio_action_impl" +path = "gio_action_impl/main.rs" diff --git a/examples/gio_action_impl/action.rs b/examples/gio_action_impl/action.rs new file mode 100644 index 000000000000..ffac897b6bb5 --- /dev/null +++ b/examples/gio_action_impl/action.rs @@ -0,0 +1,91 @@ +use gio::{prelude::*, subclass::prelude::*}; +use glib; + +mod imp { + use super::*; + use std::cell::OnceCell; + + #[derive(glib::Properties, Default)] + #[properties(wrapper_type = super::RenamedAction)] + pub struct RenamedAction { + #[property(get, construct_only)] + pub new_name: OnceCell, + + #[property(get, construct_only)] + pub action: OnceCell, + } + + #[glib::object_subclass] + impl ObjectSubclass for RenamedAction { + const NAME: &str = "ExampleRenamedAction"; + type Type = super::RenamedAction; + type Interfaces = (gio::Action,); + } + + #[glib::derived_properties] + impl ObjectImpl for RenamedAction { + fn properties() -> &'static [glib::ParamSpec] { + Self::derived_properties() + } + + fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + let _ = self.delegate_set_property(id, value, pspec) || { + self.derived_set_property(id, value, pspec); + true + }; + } + + fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value { + self.delegate_get_property(id, pspec) + .unwrap_or_else(|| self.derived_property(id, pspec)) + } + } + + impl ActionImpl for RenamedAction { + fn name(&self) -> glib::GString { + self.obj().new_name() + } + + fn parameter_type(&self) -> Option { + self.obj().action().parameter_type() + } + + fn state_type(&self) -> Option { + self.obj().action().state_type() + } + + fn state_hint(&self) -> Option { + self.obj().action().state_hint() + } + + fn is_enabled(&self) -> bool { + self.obj().action().is_enabled() + } + + fn state(&self) -> Option { + self.obj().action().state() + } + + fn change_state(&self, value: glib::Variant) { + self.obj().action().change_state(&value); + } + + fn activate(&self, parameter: Option) { + self.obj().action().activate(parameter.as_ref()); + } + } +} + +glib::wrapper! { + pub struct RenamedAction(ObjectSubclass) + @implements gio::Action; +} + +impl RenamedAction { + pub fn new(name: &str, action: &impl IsA) -> Self { + glib::Object::builder() + .property("new-name", name) + .property("action", action) + .build() + } +} diff --git a/examples/gio_action_impl/main.rs b/examples/gio_action_impl/main.rs new file mode 100644 index 000000000000..ad9b72f95c65 --- /dev/null +++ b/examples/gio_action_impl/main.rs @@ -0,0 +1,22 @@ +mod action; + +use gio::prelude::*; + +fn main() { + let action = gio::SimpleAction::new("bark", Some(glib::VariantTy::STRING)); + action.connect_activate(|_, p| { + let target = p.unwrap().str().unwrap(); + println!("Woof, {}!", target); + }); + + let renamed_action = action::RenamedAction::new("meow", &action); + + let group = gio::SimpleActionGroup::new(); + group.add_action(&action); + group.add_action(&renamed_action); + + println!("actions = {:?}", group.list_actions()); + + group.activate_action("bark", Some(&"postman".to_variant())); + group.activate_action("meow", Some(&"milkman".to_variant())); +} diff --git a/gio/src/subclass/action.rs b/gio/src/subclass/action.rs new file mode 100644 index 000000000000..6f368530cfeb --- /dev/null +++ b/gio/src/subclass/action.rs @@ -0,0 +1,427 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::sync::OnceLock; + +use glib::{ + ffi::{gboolean, GVariant, GVariantType}, + prelude::*, + subclass::prelude::*, + translate::*, + GString, Variant, VariantType, +}; + +use crate::{ffi, Action}; + +pub trait ActionImpl: ObjectImpl + ObjectSubclass> { + fn name(&self) -> GString { + self.parent_name() + } + + fn parameter_type(&self) -> Option { + self.parent_parameter_type() + } + + fn state_type(&self) -> Option { + self.parent_state_type() + } + + fn state_hint(&self) -> Option { + self.parent_state_hint() + } + + fn is_enabled(&self) -> bool { + self.parent_enabled() + } + + fn state(&self) -> Option { + self.parent_state() + } + + fn change_state(&self, value: Variant) { + self.parent_change_state(value); + } + + fn activate(&self, parameter: Option) { + self.parent_activate(parameter); + } + + fn delegate_get_property( + &self, + prop_id: usize, + _pspec: &glib::ParamSpec, + ) -> Option { + let type_: glib::Type = self.obj().type_(); + let property = ActionProperty::from_type(type_, prop_id)?; + Some(match property { + ActionProperty::Name => self.name().to_value(), + ActionProperty::ParameterType => self.parameter_type().to_value(), + ActionProperty::StateType => self.state_type().to_value(), + ActionProperty::Enabled => self.is_enabled().to_value(), + ActionProperty::State => self.state().to_value(), + }) + } + + fn delegate_set_property( + &self, + _prop_id: usize, + _value: &glib::Value, + _pspec: &glib::ParamSpec, + ) -> bool { + false + } +} + +pub trait ActionImplExt: ActionImpl { + fn parent_name(&self) -> GString { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_name + .expect("no parent \"get_name\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_parameter_type(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_parameter_type + .expect("no parent \"get_parameter_type\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_state_type(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_state_type + .expect("no parent \"get_state_type\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_state_hint(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_state_hint + .expect("no parent \"get_state_hint\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_enabled(&self) -> bool { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_enabled + .expect("no parent \"get_enabled\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + ret != 0 + } + } + + fn parent_state(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_state + .expect("no parent \"get_state\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_change_state(&self, value: Variant) { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .change_state + .expect("no parent \"change_state\" implementation"); + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + value.to_glib_none().0, + ); + } + } + + fn parent_activate(&self, parameter: Option) { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .activate + .expect("no parent \"activate\" implementation"); + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + parameter.to_glib_none().0, + ); + } + } +} + +impl ActionImplExt for T {} + +fn property_id_quark() -> glib::Quark { + static QUARK: OnceLock = OnceLock::new(); + *QUARK.get_or_init(|| glib::Quark::from_str("gtk-rs-subclass-action-first-prop")) +} + +fn set_first_prop(type_: glib::Type, first_prop: usize) { + let data = Box::new(first_prop); + unsafe { + glib::gobject_ffi::g_type_set_qdata( + type_.into_glib(), + property_id_quark().into_glib(), + Box::into_raw(data) as *mut _, + ); + } +} + +fn get_first_prop(type_: glib::Type) -> Option { + unsafe { + let ptr = + glib::gobject_ffi::g_type_get_qdata(type_.into_glib(), property_id_quark().into_glib()) + as *mut usize; + if ptr.is_null() { + None + } else { + Some(*ptr) + } + } +} + +#[repr(C)] +enum ActionProperty { + Name = 0, + ParameterType, + StateType, + Enabled, + State, +} + +impl ActionProperty { + fn from_id(first_prop: usize, id: usize) -> Option { + match id.checked_sub(first_prop)? { + 0 => Some(Self::Name), + 1 => Some(Self::ParameterType), + 2 => Some(Self::StateType), + 3 => Some(Self::Enabled), + 4 => Some(Self::State), + _ => None, + } + } + + fn from_type(mut type_: glib::Type, id: usize) -> Option { + loop { + if let Some(first_prop) = get_first_prop(type_) { + break ActionProperty::from_id(first_prop, id); + } + type_ = type_.parent()?; + } + } +} + +unsafe impl IsImplementable for Action { + fn interface_init(iface: &mut glib::Interface) { + let instance_type = iface.instance_type(); + let iface = iface.as_mut(); + + iface.get_name = Some(action_get_name::); + iface.get_parameter_type = Some(action_get_parameter_type::); + iface.get_state_type = Some(action_get_state_type::); + iface.get_state_hint = Some(action_get_state_hint::); + iface.get_enabled = Some(action_get_enabled::); + iface.get_state = Some(action_get_state::); + iface.change_state = Some(action_change_state::); + iface.activate = Some(action_activate::); + + unsafe { + let class_ref = glib::object::Class::::from_type(instance_type).unwrap(); + let object_class = + class_ref.as_ref() as *const _ as *mut glib::gobject_ffi::GObjectClass; + + let mut first_prop = std::mem::MaybeUninit::uninit(); + let properties = glib::gobject_ffi::g_object_class_list_properties( + object_class, + first_prop.as_mut_ptr(), + ); + glib::ffi::g_free(properties as *mut _); + let first_prop = first_prop.assume_init() + 1; + + set_first_prop(instance_type, first_prop as usize); + + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::Name as u32, + b"name\0".as_ptr() as *const _, + ); + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::ParameterType as u32, + b"parameter-type\0".as_ptr() as *const _, + ); + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::StateType as u32, + b"state-type\0".as_ptr() as *const _, + ); + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::Enabled as u32, + b"enabled\0".as_ptr() as *const _, + ); + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::State as u32, + b"state\0".as_ptr() as *const _, + ); + } + } +} + +unsafe extern "C" fn action_get_name( + actionptr: *mut ffi::GAction, +) -> *const libc::c_char { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + let instance = imp.obj(); + let name_quark = { + static QUARK: OnceLock = OnceLock::new(); + *QUARK.get_or_init(|| glib::Quark::from_str("gtk-rs-subclass-action-get-name")) + }; + if let Some(old_name_ptr) = instance.qdata::(name_quark) { + old_name_ptr.as_ref().as_ptr() + } else { + instance.set_qdata(name_quark, imp.name()); + instance + .qdata::(name_quark) + .unwrap() + .as_ref() + .as_ptr() + } +} + +unsafe extern "C" fn action_get_parameter_type( + actionptr: *mut ffi::GAction, +) -> *const GVariantType { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + if let Some(parameter_type) = imp.parameter_type() { + let instance = imp.obj(); + let parameter_type_quark = { + static QUARK: OnceLock = OnceLock::new(); + *QUARK + .get_or_init(|| glib::Quark::from_str("gtk-rs-subclass-action-get-parameter-type")) + }; + if let Some(old_parameter_type_ptr) = + instance.qdata::(parameter_type_quark) + { + old_parameter_type_ptr.as_ref().to_glib_none().0 + } else { + let parameter_type_ptr = parameter_type.to_glib_none().0; + instance.set_qdata(parameter_type_quark, parameter_type); + parameter_type_ptr + } + } else { + std::ptr::null() + } +} + +unsafe extern "C" fn action_get_state_type( + actionptr: *mut ffi::GAction, +) -> *const GVariantType { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + if let Some(state_type) = imp.state_type() { + let instance = imp.obj(); + let state_type_quark = { + static QUARK: OnceLock = OnceLock::new(); + *QUARK.get_or_init(|| glib::Quark::from_str("gtk-rs-subclass-action-get-state-type")) + }; + let state_type_ptr = state_type.to_glib_none().0; + instance.set_qdata(state_type_quark, state_type); + state_type_ptr + } else { + std::ptr::null() + } +} + +unsafe extern "C" fn action_get_state_hint( + actionptr: *mut ffi::GAction, +) -> *mut GVariant { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + imp.state_hint().to_glib_full() +} + +unsafe extern "C" fn action_get_enabled(actionptr: *mut ffi::GAction) -> gboolean { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + imp.is_enabled() as gboolean +} + +unsafe extern "C" fn action_get_state( + actionptr: *mut ffi::GAction, +) -> *mut GVariant { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + imp.state().to_glib_full() +} + +unsafe extern "C" fn action_change_state( + actionptr: *mut ffi::GAction, + value: *mut GVariant, +) { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + let value: Variant = from_glib_none(value); + + imp.change_state(value); +} + +unsafe extern "C" fn action_activate( + actionptr: *mut ffi::GAction, + parameterptr: *mut GVariant, +) { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + let parameter: Option = from_glib_none(parameterptr); + + imp.activate(parameter); +} diff --git a/gio/src/subclass/mod.rs b/gio/src/subclass/mod.rs index 791d1752a8c8..a532bc74ca14 100644 --- a/gio/src/subclass/mod.rs +++ b/gio/src/subclass/mod.rs @@ -1,5 +1,6 @@ // Take a look at the license at the top of the repository in the LICENSE file. +mod action; mod action_group; mod action_map; mod application; @@ -19,6 +20,7 @@ pub mod prelude { pub use glib::subclass::prelude::*; pub use super::{ + action::{ActionImpl, ActionImplExt}, action_group::{ActionGroupImpl, ActionGroupImplExt}, action_map::{ActionMapImpl, ActionMapImplExt}, application::{ApplicationImpl, ApplicationImplExt},