glib/gobject/
signal_group.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::mem::transmute;
4
5use crate::{
6    ffi, gobject_ffi,
7    prelude::*,
8    signal::{connect_raw, SignalHandlerId},
9    translate::*,
10    Object, RustClosure, SignalGroup, Value,
11};
12
13impl SignalGroup {
14    #[doc(alias = "g_signal_group_new")]
15    pub fn new<T: IsA<Object>>() -> Self {
16        Self::with_type(T::static_type())
17    }
18    /// Connects `closure` to the signal `detailed_signal` on [`target`][struct@crate::SignalGroup#target].
19    ///
20    /// You cannot connect a signal handler after [`target`][struct@crate::SignalGroup#target] has been set.
21    /// ## `detailed_signal`
22    /// a string of the form `signal-name` with optional `::signal-detail`
23    /// ## `closure`
24    /// the closure to connect.
25    /// ## `after`
26    /// whether the handler should be called before or after the
27    ///  default handler of the signal.
28    // rustdoc-stripper-ignore-next-stop
29    /// Connects `closure` to the signal `detailed_signal` on [`target`][struct@crate::SignalGroup#target].
30    ///
31    /// You cannot connect a signal handler after [`target`][struct@crate::SignalGroup#target] has been set.
32    /// ## `detailed_signal`
33    /// a string of the form `signal-name` with optional `::signal-detail`
34    /// ## `closure`
35    /// the closure to connect.
36    /// ## `after`
37    /// whether the handler should be called before or after the
38    ///  default handler of the signal.
39    #[doc(alias = "g_signal_group_connect_closure")]
40    pub fn connect_closure(&self, signal_name: &str, after: bool, closure: RustClosure) {
41        unsafe {
42            gobject_ffi::g_signal_group_connect_closure(
43                self.to_glib_none().0,
44                signal_name.to_glib_none().0,
45                closure.as_ref().to_glib_none().0,
46                after.into_glib(),
47            );
48        }
49    }
50
51    /// Connects `c_handler` to the signal `detailed_signal`
52    /// on the target instance of `self`.
53    ///
54    /// You cannot connect a signal handler after [`target`][struct@crate::SignalGroup#target] has been set.
55    /// ## `detailed_signal`
56    /// a string of the form "signal-name::detail"
57    /// ## `c_handler`
58    /// the `GCallback` to connect
59    // rustdoc-stripper-ignore-next-stop
60    /// Connects `c_handler` to the signal `detailed_signal`
61    /// on the target instance of `self`.
62    ///
63    /// You cannot connect a signal handler after [`target`][struct@crate::SignalGroup#target] has been set.
64    /// ## `detailed_signal`
65    /// a string of the form "signal-name::detail"
66    /// ## `c_handler`
67    /// the `GCallback` to connect
68    #[doc(alias = "g_signal_group_connect")]
69    #[inline]
70    pub fn connect<F>(&self, signal_name: &str, after: bool, callback: F)
71    where
72        F: Fn(&[Value]) -> Option<Value> + Send + Sync + 'static,
73    {
74        self.connect_closure(signal_name, after, RustClosure::new(callback));
75    }
76
77    // rustdoc-stripper-ignore-next
78    /// Like [`Self::connect`] but doesn't require a `Send+Sync` closure. Signal emission will
79    /// panic if the signal on the current target is emitted from a different thread from the
80    /// thread that connected the signal.
81    #[inline]
82    pub fn connect_local<F>(&self, signal_name: &str, after: bool, callback: F)
83    where
84        F: Fn(&[Value]) -> Option<Value> + 'static,
85    {
86        self.connect_closure(signal_name, after, RustClosure::new_local(callback));
87    }
88
89    #[inline]
90    pub fn connect_notify<F>(&self, name: Option<&str>, callback: F)
91    where
92        F: Fn(&crate::Object, &crate::ParamSpec) + Send + Sync + 'static,
93    {
94        let signal_name = if let Some(name) = name {
95            format!("notify::{name}")
96        } else {
97            "notify".into()
98        };
99
100        let closure = crate::RustClosure::new(move |values| {
101            let obj = values[0].get().unwrap();
102            let pspec = values[1].get().unwrap();
103            callback(obj, pspec);
104
105            None
106        });
107
108        self.connect_closure(&signal_name, false, closure);
109    }
110
111    #[inline]
112    pub fn connect_notify_local<F>(&self, name: Option<&str>, callback: F)
113    where
114        F: Fn(&crate::Object, &crate::ParamSpec) + 'static,
115    {
116        let signal_name = if let Some(name) = name {
117            format!("notify::{name}")
118        } else {
119            "notify".into()
120        };
121
122        let closure = crate::RustClosure::new_local(move |values| {
123            let obj = values[0].get().unwrap();
124            let pspec = values[1].get().unwrap();
125            callback(obj, pspec);
126
127            None
128        });
129
130        self.connect_closure(&signal_name, false, closure);
131    }
132
133    unsafe fn connect_bind_unsafe<F: Fn(&Self, &Object)>(&self, f: F) -> SignalHandlerId {
134        unsafe extern "C" fn bind_trampoline<F: Fn(&SignalGroup, &Object)>(
135            this: *mut crate::gobject_ffi::GSignalGroup,
136            instance: *mut crate::gobject_ffi::GObject,
137            f: ffi::gpointer,
138        ) {
139            let f: &F = &*(f as *const F);
140            f(&from_glib_borrow(this), &from_glib_borrow(instance))
141        }
142        let f: Box<F> = Box::new(f);
143        connect_raw(
144            self.as_ptr() as *mut _,
145            b"bind\0".as_ptr() as *const _,
146            Some(transmute::<*const (), unsafe extern "C" fn()>(
147                bind_trampoline::<F> as *const (),
148            )),
149            Box::into_raw(f),
150        )
151    }
152
153    unsafe fn connect_unbind_unsafe<F: Fn(&Self)>(&self, f: F) -> SignalHandlerId {
154        unsafe extern "C" fn unbind_trampoline<F: Fn(&SignalGroup)>(
155            this: *mut crate::gobject_ffi::GSignalGroup,
156            f: ffi::gpointer,
157        ) {
158            let f: &F = &*(f as *const F);
159            f(&from_glib_borrow(this))
160        }
161        let f: Box<F> = Box::new(f);
162        connect_raw(
163            self.as_ptr() as *mut _,
164            b"unbind\0".as_ptr() as *const _,
165            Some(transmute::<*const (), unsafe extern "C" fn()>(
166                unbind_trampoline::<F> as *const (),
167            )),
168            Box::into_raw(f),
169        )
170    }
171
172    /// This signal is emitted when [`target`][struct@crate::SignalGroup#target] is set to a new value
173    /// other than [`None`]. It is similar to [`notify`][struct@crate::Object#notify] on `target` except it
174    /// will not emit when [`target`][struct@crate::SignalGroup#target] is [`None`] and also allows for
175    /// receiving the [`Object`][crate::Object] without a data-race.
176    /// ## `instance`
177    /// a [`Object`][crate::Object] containing the new value for [`target`][struct@crate::SignalGroup#target]
178    // rustdoc-stripper-ignore-next-stop
179    /// This signal is emitted when [`target`][struct@crate::SignalGroup#target] is set to a new value
180    /// other than [`None`]. It is similar to [`notify`][struct@crate::Object#notify] on `target` except it
181    /// will not emit when [`target`][struct@crate::SignalGroup#target] is [`None`] and also allows for
182    /// receiving the [`Object`][crate::Object] without a data-race.
183    /// ## `instance`
184    /// a [`Object`][crate::Object] containing the new value for [`target`][struct@crate::SignalGroup#target]
185    #[doc(alias = "bind")]
186    pub fn connect_bind<F: Fn(&Self, &Object) + Send + Sync + 'static>(
187        &self,
188        f: F,
189    ) -> SignalHandlerId {
190        unsafe { self.connect_bind_unsafe(f) }
191    }
192
193    // rustdoc-stripper-ignore-next
194    /// Like [`Self::connect_bind`] but doesn't require a `Send+Sync` closure. Signal emission will
195    /// panic if the signal is emitted from a different thread from the thread that connected the
196    /// signal.
197    pub fn connect_bind_local<F: Fn(&Self, &Object) + 'static>(&self, f: F) -> SignalHandlerId {
198        let f = crate::thread_guard::ThreadGuard::new(f);
199
200        unsafe {
201            self.connect_bind_unsafe(move |s, o| {
202                (f.get_ref())(s, o);
203            })
204        }
205    }
206
207    /// This signal is emitted when the target instance of `self_` is set to a
208    /// new [`Object`][crate::Object].
209    ///
210    /// This signal will only be emitted if the previous target of `self_` is
211    /// non-[`None`].
212    // rustdoc-stripper-ignore-next-stop
213    /// This signal is emitted when the target instance of `self_` is set to a
214    /// new [`Object`][crate::Object].
215    ///
216    /// This signal will only be emitted if the previous target of `self_` is
217    /// non-[`None`].
218    #[doc(alias = "unbind")]
219    pub fn connect_unbind<F: Fn(&Self) + Send + Sync + 'static>(&self, f: F) -> SignalHandlerId {
220        unsafe { self.connect_unbind_unsafe(f) }
221    }
222
223    // rustdoc-stripper-ignore-next
224    /// Like [`Self::connect_unbind`] but doesn't require a `Send+Sync` closure. Signal emission
225    /// will panic if the signal is emitted from a different thread from the thread that connected
226    /// the signal.
227    pub fn connect_unbind_local<F: Fn(&Self) + 'static>(&self, f: F) -> SignalHandlerId {
228        let f = crate::thread_guard::ThreadGuard::new(f);
229
230        unsafe {
231            self.connect_unbind_unsafe(move |s| {
232                (f.get_ref())(s);
233            })
234        }
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use std::{cell::RefCell, rc::Rc, sync::OnceLock};
241
242    use super::*;
243    use crate as glib;
244
245    mod imp {
246        use super::*;
247        use crate::subclass::{prelude::*, Signal};
248
249        #[derive(Default)]
250        pub struct SignalObject {}
251
252        #[glib::object_subclass]
253        impl ObjectSubclass for SignalObject {
254            const NAME: &'static str = "SignalObject";
255            type Type = super::SignalObject;
256        }
257
258        impl ObjectImpl for SignalObject {
259            fn signals() -> &'static [Signal] {
260                static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
261                SIGNALS.get_or_init(|| {
262                    vec![
263                        Signal::builder("sig-with-args")
264                            .param_types([u32::static_type(), String::static_type()])
265                            .build(),
266                        Signal::builder("sig-with-ret")
267                            .return_type::<String>()
268                            .build(),
269                    ]
270                })
271            }
272        }
273    }
274
275    wrapper! {
276        pub struct SignalObject(ObjectSubclass<imp::SignalObject>);
277    }
278
279    #[test]
280    fn group_emit() {
281        let group = SignalGroup::new::<SignalObject>();
282
283        let obj = Object::new::<SignalObject>();
284        let store = Rc::new(RefCell::new(String::new()));
285        group.connect_closure(
286            "sig-with-args",
287            false,
288            glib::closure_local!(
289                #[watch]
290                obj,
291                #[strong]
292                store,
293                move |o: &SignalObject, a: u32, b: &str| {
294                    assert_eq!(o, obj);
295                    store.replace(format!("a {a} b {b}"));
296                }
297            ),
298        );
299        group.connect_closure(
300            "sig-with-ret",
301            false,
302            glib::closure_local!(
303                #[watch]
304                obj,
305                move |o: &SignalObject| -> &'static crate::GStr {
306                    assert_eq!(o, obj);
307                    crate::gstr!("Hello")
308                }
309            ),
310        );
311        group.set_target(Some(&obj));
312        obj.emit_by_name::<()>("sig-with-args", &[&5u32, &"World"]);
313        assert_eq!(*store.borrow(), "a 5 b World");
314        let ret = obj.emit_by_name::<crate::GString>("sig-with-ret", &[]);
315        assert_eq!(ret, "Hello");
316        group.set_target(Object::NONE);
317        let ret = obj.emit_by_name::<Option<String>>("sig-with-ret", &[]);
318        assert_eq!(ret, None);
319    }
320}