diff --git a/glib-macros/src/class_handler.rs b/glib-macros/src/class_handler.rs new file mode 100644 index 000000000000..35bd2cc35909 --- /dev/null +++ b/glib-macros/src/class_handler.rs @@ -0,0 +1,58 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::utils::crate_ident_new; +use proc_macro::TokenStream; +use proc_macro2::{Ident, Span}; +use quote::quote_spanned; +use syn::spanned::Spanned; + +pub(crate) fn class_handler_inner(input: TokenStream, has_token: bool) -> TokenStream { + let crate_ident = crate_ident_new(); + let closure = syn::parse_macro_input!(input as syn::ExprClosure); + let closure_ident = Ident::new("____closure", Span::mixed_site()); + let token_ident = has_token.then(|| Ident::new("____token", Span::mixed_site())); + let values_ident = Ident::new("____values", Span::mixed_site()); + let offset = if has_token { 1 } else { 0 }; + let arg_names = closure + .inputs + .iter() + .skip(offset) + .enumerate() + .map(|(index, _)| Ident::new(&format!("____arg{}", index), Span::mixed_site())); + let arg_names = if let Some(token) = token_ident.as_ref().cloned() { + std::iter::once(token).chain(arg_names).collect::>() + } else { + arg_names.collect::>() + }; + let arg_values = closure + .inputs + .iter() + .skip(offset) + .enumerate() + .map(|(index, pat)| { + let err_msg = format!("Wrong type for argument {}: {{:?}}", index); + let name = &arg_names[index + offset]; + quote_spanned! { pat.span() => + let #name = #crate_ident::Value::get(&#values_ident[#index]) + .unwrap_or_else(|e| ::std::panic!(#err_msg, e)); + } + }); + let args_len = closure.inputs.len().saturating_sub(offset); + let token_arg = token_ident.map(|t| { + quote_spanned! { t.span() => + #t: #crate_ident::subclass::SignalClassOverrideToken, + } + }); + let output = quote_spanned! { closure.span() => { + let #closure_ident = #closure; + move + |#token_arg #values_ident: &[#crate_ident::Value]| -> ::std::option::Option<#crate_ident::Value> { + assert_eq!(#values_ident.len(), #args_len); + #(#arg_values)* + #crate_ident::closure::ToClosureReturnValue::to_closure_return_value( + &#closure_ident(#(#arg_names),*) + ) + } + } }; + output.into() +} diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 1a7f88e881ab..a8ddc0815908 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -1,6 +1,7 @@ // Take a look at the license at the top of the repository in the LICENSE file. mod boxed_derive; +mod class_handler; mod clone; mod closure; mod downgrade_derive; @@ -418,6 +419,190 @@ pub fn closure_local(item: TokenStream) -> TokenStream { closure::closure_inner(item, "new_local") } +/// Macro for creating signal class handlers. +/// +/// This is only meant to be used with [`SignalBuilder::class_handler`] for automatic conversion of +/// its types to and from [`Value`]s. This macro takes a closure expression with any number of +/// arguments, and returns a closure that returns `Option` and takes a single `&[Value]` +/// argument. +/// +/// Similar to [`closure!`], the returned function takes [`Value`] objects as inputs and output and +/// automatically converts the inputs to Rust types when invoking its callback, and then will +/// convert the output back to a `Value`. All inputs must implement the [`FromValue`] trait, and +/// outputs must either implement the [`ToValue`] trait or be the unit type `()`. Type-checking of +/// inputs is done at run-time; if the argument types do not match the type of the signal handler +/// then the closure will panic. Note that when passing input types derived from [`Object`] or +/// [`Interface`], you must take care to upcast to the exact object or interface type that is being +/// received. +/// +/// [`SignalBuilder::class_handler`]: ../glib/subclass/signal/struct.SignalBuilder.html#method.class_handler +/// [`Value`]: ../glib/value/struct.Value.html +/// [`FromValue`]: ../glib/value/trait.FromValue.html +/// [`ToValue`]: ../glib/value/trait.ToValue.html +/// [`Interface`]: ../glib/object/struct.Interface.html +/// [`Object`]: ../glib/object/struct.Object.html +/// ```no_run +/// # use glib; +/// use glib::prelude::*; +/// use glib::subclass::prelude::*; +/// use glib::class_handler; +/// # use glib::once_cell; +/// # +/// # glib::wrapper! { +/// # pub struct MyObject(ObjectSubclass); +/// # } +/// # mod imp { +/// # use super::*; +/// # +/// # #[derive(Default)] +/// # pub struct MyObject; +/// # +/// # #[glib::object_subclass] +/// # impl ObjectSubclass for MyObject { +/// # const NAME: &'static str = "MyObject"; +/// # type Type = super::MyObject; +/// # } +/// +/// impl ObjectImpl for MyObject { +/// fn signals() -> &'static [glib::subclass::Signal] { +/// use once_cell::sync::Lazy; +/// use glib::subclass::Signal; +/// static SIGNALS: Lazy> = Lazy::new(|| { +/// vec![ +/// Signal::builder( +/// "my-signal", +/// &[u32::static_type().into()], +/// String::static_type().into(), +/// ) +/// .class_handler(class_handler!( +/// |_this: &super::MyObject, num: u32| -> String { +/// format!("{}", num) +/// })) +/// .build(), +/// ] +/// }); +/// SIGNALS.as_ref() +/// } +/// } +/// # } +/// # fn main() {} +/// ``` +#[proc_macro] +#[proc_macro_error] +pub fn class_handler(item: TokenStream) -> TokenStream { + class_handler::class_handler_inner(item, false) +} + +/// Macro for creating overridden signal class handlers. +/// +/// This is only meant to be used with [`ObjectClassSubclassExt::override_signal_class_handler`] +/// for automatic conversion of its types to and from [`Value`]s. This macro takes a closure +/// expression with any number of arguments and returns a closure that returns `Option` and +/// takes two arguments: a [`SignalClassOverrideToken`] as the first and `&[Value]` as the second. +/// If any arguments are provided to the input closure, the `SignalClassOverrideToken` will always +/// be passed as the first argument. +/// +/// Similar to [`closure!`], the returned function takes [`Value`] objects as inputs and output and +/// automatically converts the inputs to Rust types when invoking its callback, and then will +/// convert the output back to a `Value`. All inputs must implement the [`FromValue`] trait, and +/// outputs must either implement the [`ToValue`] trait or be the unit type `()`. Type-checking of +/// inputs is done at run-time; if the argument types do not match the type of the signal handler +/// then the closure will panic. Note that when passing input types derived from [`Object`] or +/// [`Interface`], you must take care to upcast to the exact object or interface type that is being +/// received. +/// +/// [`ObjectClassSubclassExt::override_signal_class_handler`]: ../glib/subclass/object/trait.ObjectClassSubclassExt.html#method.override_signal_class_handler +/// [`SignalClassOverrideToken`]: ../glib/subclass/object/struct.SignalClassOverrideToken.html +/// [`Value`]: ../glib/value/struct.Value.html +/// [`FromValue`]: ../glib/value/trait.FromValue.html +/// [`ToValue`]: ../glib/value/trait.ToValue.html +/// [`Interface`]: ../glib/object/struct.Interface.html +/// [`Object`]: ../glib/object/struct.Object.html +/// ```no_run +/// # use glib; +/// use glib::prelude::*; +/// use glib::subclass::prelude::*; +/// use glib::subclass::SignalClassOverrideToken; +/// use glib::{class_handler, override_handler}; +/// # use glib::once_cell; +/// # +/// # glib::wrapper! { +/// # pub struct MyBase(ObjectSubclass); +/// # } +/// # unsafe impl IsSubclassable for MyBase {} +/// # glib::wrapper! { +/// # pub struct MyDerived(ObjectSubclass) @extends MyBase; +/// # } +/// # mod imp { +/// # use super::*; +/// # +/// # #[derive(Default)] +/// # pub struct MyBase; +/// # +/// # #[glib::object_subclass] +/// # impl ObjectSubclass for MyBase { +/// # const NAME: &'static str = "MyBase"; +/// # type Type = super::MyBase; +/// # } +/// +/// impl ObjectImpl for MyBase { +/// fn signals() -> &'static [glib::subclass::Signal] { +/// use once_cell::sync::Lazy; +/// use glib::subclass::Signal; +/// static SIGNALS: Lazy> = Lazy::new(|| { +/// vec![ +/// Signal::builder( +/// "my-signal", +/// &[String::static_type().into(), u32::static_type().into()], +/// String::static_type().into(), +/// ) +/// .class_handler(class_handler!( +/// |this: &super::MyBase, myarg1: Option<&str>, myarg2: u32| -> String { +/// format!("len: {}", myarg1.unwrap_or_default().len() + myarg2 as usize) +/// })) +/// .build(), +/// ] +/// }); +/// SIGNALS.as_ref() +/// } +/// } +/// # +/// # pub trait MyBaseImpl: ObjectImpl {} +/// # +/// # #[derive(Default)] +/// # pub struct MyDerived; +/// +/// #[glib::object_subclass] +/// impl ObjectSubclass for MyDerived { +/// const NAME: &'static str = "MyDerived"; +/// type Type = super::MyDerived; +/// type ParentType = super::MyBase; +/// fn class_init(class: &mut Self::Class) { +/// class.override_signal_class_handler( +/// "my-signal", +/// override_handler!(|token: SignalClassOverrideToken, +/// this: &super::MyDerived, +/// myarg1: Option<&str>, +/// myarg2: u32| +/// -> String { +/// let s = format!("str: {} num: {}", myarg1.unwrap_or("nothing"), myarg2); +/// let ret: String = token.chain(&[&this, &Some(&s), &myarg2]); +/// format!("ret: {}", ret) +/// }) +/// ); +/// } +/// } +/// # impl ObjectImpl for MyDerived {} +/// # impl MyBaseImpl for MyDerived {} +/// # } +/// # fn main() {} +/// ``` +#[proc_macro] +#[proc_macro_error] +pub fn override_handler(item: TokenStream) -> TokenStream { + class_handler::class_handler_inner(item, true) +} + /// Derive macro for register a rust enum in the glib type system and derive the /// the [`glib::Value`] traits. /// diff --git a/glib/src/lib.rs b/glib/src/lib.rs index 1038bb3dcf1d..e6df9b4f22f8 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -17,8 +17,8 @@ pub use bitflags; pub use once_cell; pub use glib_macros::{ - clone, closure, closure_local, flags, object_interface, object_subclass, Boxed, Downgrade, - Enum, ErrorDomain, SharedBoxed, Variant, + class_handler, clone, closure, closure_local, flags, object_interface, object_subclass, + override_handler, Boxed, Downgrade, Enum, ErrorDomain, SharedBoxed, Variant, }; #[doc(hidden)] diff --git a/glib/src/subclass/object.rs b/glib/src/subclass/object.rs index fa0dab4e249d..6c9fd52eecda 100644 --- a/glib/src/subclass/object.rs +++ b/glib/src/subclass/object.rs @@ -252,7 +252,10 @@ pub unsafe trait ObjectClassSubclassExt: Sized + 'static { // rustdoc-stripper-ignore-next /// Overrides the class handler for a signal. The signal `name` must be installed on the parent /// class or this method will panic. The parent class handler can be invoked by calling one of - /// the `chain` methods on the [`SignalClassOverrideToken`]. + /// the `chain` methods on the [`SignalClassOverrideToken`]. This can be used with the + /// [`override_handler`](crate::override_handler) macro to perform automatic conversion to and + /// from the [`Value`](crate::Value) arguments and return value. See the documentation of that + /// macro for more information. fn override_signal_class_handler(&mut self, name: &str, class_handler: F) where F: Fn(SignalClassOverrideToken, &[Value]) -> Option + Send + Sync + 'static, @@ -485,6 +488,18 @@ mod test { ChildObject::type_().into(), ) .build(), + super::Signal::builder( + "print-hex", + &[u32::static_type().into()], + String::static_type().into(), + ) + .run_first() + .class_handler(glib::class_handler!(|_: &super::SimpleObject, + n: u32| + -> String { + format!("0x{:x}", n) + })) + .build(), ] }); @@ -574,6 +589,22 @@ mod test { }) ); + derive_simple!( + DerivedObject2, + super::DerivedObject2, + class, + class.override_signal_class_handler( + "change-name", + glib::override_handler!(|token: SignalClassOverrideToken, + this: &super::DerivedObject2, + name: Option<&str>| + -> Option { + let name = name.map(|n| format!("{}-closure", n)); + token.chain(&[&this, &name]) + }) + ) + ); + #[derive(Clone, Copy)] #[repr(C)] pub struct DummyInterface { @@ -600,6 +631,10 @@ mod test { pub struct DerivedObject(ObjectSubclass) @extends SimpleObject; } + wrapper! { + pub struct DerivedObject2(ObjectSubclass) @extends SimpleObject; + } + wrapper! { pub struct Dummy(ObjectInterface); } @@ -812,6 +847,21 @@ mod test { assert!(value.type_().is_a(ChildObject::static_type())); } + #[test] + fn test_signal_class_handler() { + let obj = Object::with_type(SimpleObject::static_type(), &[]).expect("Object::new failed"); + + let value = obj.emit_by_name::("print-hex", &[&55u32]); + assert_eq!(value, "0x37"); + obj.connect_closure( + "print-hex", + false, + glib::closure_local!(|_: &SimpleObject, n: u32| -> String { format!("0x{:08x}", n) }), + ); + let value = obj.emit_by_name::("print-hex", &[&56u32]); + assert_eq!(value, "0x00000038"); + } + #[test] fn test_signal_override_chain_values() { let obj = Object::with_type(DerivedObject::static_type(), &[]).expect("Object::new failed"); @@ -832,4 +882,26 @@ mod test { ); assert_eq!(*current_name.borrow(), "new-name"); } + + #[test] + fn test_signal_override_chain() { + let obj = + Object::with_type(DerivedObject2::static_type(), &[]).expect("Object::new failed"); + obj.emit_by_name::>("change-name", &[&"old-name"]); + + let current_name = Rc::new(RefCell::new(String::new())); + let current_name_clone = current_name.clone(); + + obj.connect_local("name-changed", false, move |args| { + let name = args[1].get::().expect("Failed to get args[1]"); + current_name_clone.replace(name); + None + }); + assert_eq!( + obj.emit_by_name::>("change-name", &[&"new-name"]) + .unwrap(), + "old-name-closure" + ); + assert_eq!(*current_name.borrow(), "new-name-closure"); + } } diff --git a/glib/src/subclass/signal.rs b/glib/src/subclass/signal.rs index a3ad326c4717..da787381a678 100644 --- a/glib/src/subclass/signal.rs +++ b/glib/src/subclass/signal.rs @@ -465,6 +465,10 @@ impl<'a> SignalBuilder<'a> { // rustdoc-stripper-ignore-next /// Class handler for this signal. + /// + /// This can be used with the [`class_handler`](crate::class_handler) macro to perform + /// automatic conversion to and from the [`Value`](crate::Value) arguments and return value. + /// See the documentation of that macro for more information. pub fn class_handler(mut self, func: F) -> Self where F: Fn(&[Value]) -> Option + Send + Sync + 'static,