Skip to content

Commit

Permalink
glib-macros: Add class_handler and override_handler macros
Browse files Browse the repository at this point in the history
  • Loading branch information
jf2048 committed Mar 19, 2022
1 parent 114c2c4 commit 2ba8dc3
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 3 deletions.
58 changes: 58 additions & 0 deletions 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::<Vec<_>>()
} else {
arg_names.collect::<Vec<_>>()
};
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()
}
185 changes: 185 additions & 0 deletions 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;
Expand Down Expand Up @@ -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<Value>` 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<imp::MyObject>);
/// # }
/// # 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<Vec<glib::subclass::Signal>> = 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<Value>` 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<imp::MyBase>);
/// # }
/// # unsafe impl<T: imp::MyBaseImpl> IsSubclassable<T> for MyBase {}
/// # glib::wrapper! {
/// # pub struct MyDerived(ObjectSubclass<imp::MyDerived>) @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<Vec<glib::subclass::Signal>> = 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.
///
Expand Down
4 changes: 2 additions & 2 deletions glib/src/lib.rs
Expand Up @@ -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)]
Expand Down
74 changes: 73 additions & 1 deletion glib/src/subclass/object.rs
Expand Up @@ -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<F>(&mut self, name: &str, class_handler: F)
where
F: Fn(SignalClassOverrideToken, &[Value]) -> Option<Value> + Send + Sync + 'static,
Expand Down Expand Up @@ -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(),
]
});

Expand Down Expand Up @@ -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<String> {
let name = name.map(|n| format!("{}-closure", n));
token.chain(&[&this, &name])
})
)
);

#[derive(Clone, Copy)]
#[repr(C)]
pub struct DummyInterface {
Expand All @@ -600,6 +631,10 @@ mod test {
pub struct DerivedObject(ObjectSubclass<imp::DerivedObject>) @extends SimpleObject;
}

wrapper! {
pub struct DerivedObject2(ObjectSubclass<imp::DerivedObject2>) @extends SimpleObject;
}

wrapper! {
pub struct Dummy(ObjectInterface<imp::DummyInterface>);
}
Expand Down Expand Up @@ -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::<String>("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::<String>("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");
Expand All @@ -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::<Option<String>>("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::<String>().expect("Failed to get args[1]");
current_name_clone.replace(name);
None
});
assert_eq!(
obj.emit_by_name::<Option<String>>("change-name", &[&"new-name"])
.unwrap(),
"old-name-closure"
);
assert_eq!(*current_name.borrow(), "new-name-closure");
}
}
4 changes: 4 additions & 0 deletions glib/src/subclass/signal.rs
Expand Up @@ -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<F>(mut self, func: F) -> Self
where
F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static,
Expand Down

0 comments on commit 2ba8dc3

Please sign in to comment.