glib/subclass/
object.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3// rustdoc-stripper-ignore-next
4//! Module that contains all types needed for creating a direct subclass of `GObject`
5//! or implementing virtual methods of it.
6
7use std::{mem, ptr};
8
9use crate::{
10    ffi, gobject_ffi,
11    prelude::*,
12    subclass::{prelude::*, Signal},
13    translate::*,
14    Object, ParamSpec, Slice, Value,
15};
16
17// rustdoc-stripper-ignore-next
18/// Trait for implementors of `glib::Object` subclasses.
19///
20/// This allows overriding the virtual methods of `glib::Object`. Except for
21/// `finalize` as implementing `Drop` would allow the same behavior.
22pub trait ObjectImpl: ObjectSubclass + ObjectImplExt {
23    // rustdoc-stripper-ignore-next
24    /// Properties installed for this type.
25    fn properties() -> &'static [ParamSpec] {
26        &[]
27    }
28
29    // rustdoc-stripper-ignore-next
30    /// Signals installed for this type.
31    fn signals() -> &'static [Signal] {
32        &[]
33    }
34
35    // rustdoc-stripper-ignore-next
36    /// Property setter.
37    ///
38    /// This is called whenever the property of this specific subclass with the
39    /// given index is set. The new value is passed as `glib::Value`.
40    ///
41    /// `value` is guaranteed to be of the correct type for the given property.
42    fn set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) {
43        unimplemented!()
44    }
45
46    // rustdoc-stripper-ignore-next
47    /// Property getter.
48    ///
49    /// This is called whenever the property value of the specific subclass with the
50    /// given index should be returned.
51    ///
52    /// The returned `Value` must be of the correct type for the given property.
53    #[doc(alias = "get_property")]
54    fn property(&self, _id: usize, _pspec: &ParamSpec) -> Value {
55        unimplemented!()
56    }
57
58    // rustdoc-stripper-ignore-next
59    /// Constructed.
60    ///
61    /// This is called once construction of the instance is finished.
62    ///
63    /// Should chain up to the parent class' implementation.
64    fn constructed(&self) {
65        self.parent_constructed();
66    }
67
68    // rustdoc-stripper-ignore-next
69    /// Disposes of the object.
70    ///
71    /// When `dispose()` ends, the object should not hold any reference to any other member object.
72    /// The object is also expected to be able to answer client method invocations (with possibly an
73    /// error code but no memory violation) until it is dropped. `dispose()` can be executed more
74    /// than once.
75    fn dispose(&self) {}
76
77    // rustdoc-stripper-ignore-next
78    /// Function to be called when property change is notified for with
79    /// `self.notify("property")`.
80    fn notify(&self, pspec: &ParamSpec) {
81        self.parent_notify(pspec)
82    }
83
84    fn dispatch_properties_changed(&self, pspecs: &[ParamSpec]) {
85        self.parent_dispatch_properties_changed(pspecs)
86    }
87}
88
89#[doc(alias = "get_property")]
90unsafe extern "C" fn property<T: ObjectImpl>(
91    obj: *mut gobject_ffi::GObject,
92    id: u32,
93    value: *mut gobject_ffi::GValue,
94    pspec: *mut gobject_ffi::GParamSpec,
95) {
96    let instance = &*(obj as *mut T::Instance);
97    let imp = instance.imp();
98
99    let v = imp.property(id as usize, &from_glib_borrow(pspec));
100
101    // We first unset the value we get passed in, in case it contained
102    // any previous data. Then we directly overwrite it with our new
103    // value, and pass ownership of the contained data to the C GValue
104    // by forgetting it on the Rust side.
105    //
106    // Without this, by using the GValue API, we would have to create
107    // a copy of the value when setting it on the destination just to
108    // immediately free the original value afterwards.
109    gobject_ffi::g_value_unset(value);
110    let v = mem::ManuallyDrop::new(v);
111    ptr::write(value, ptr::read(v.to_glib_none().0));
112}
113
114unsafe extern "C" fn set_property<T: ObjectImpl>(
115    obj: *mut gobject_ffi::GObject,
116    id: u32,
117    value: *mut gobject_ffi::GValue,
118    pspec: *mut gobject_ffi::GParamSpec,
119) {
120    let instance = &*(obj as *mut T::Instance);
121    let imp = instance.imp();
122    imp.set_property(
123        id as usize,
124        &*(value as *mut Value),
125        &from_glib_borrow(pspec),
126    );
127}
128
129unsafe extern "C" fn constructed<T: ObjectImpl>(obj: *mut gobject_ffi::GObject) {
130    let instance = &*(obj as *mut T::Instance);
131    let imp = instance.imp();
132
133    imp.constructed();
134}
135
136unsafe extern "C" fn notify<T: ObjectImpl>(
137    obj: *mut gobject_ffi::GObject,
138    pspec: *mut gobject_ffi::GParamSpec,
139) {
140    let instance = &*(obj as *mut T::Instance);
141    let imp = instance.imp();
142    imp.notify(&from_glib_borrow(pspec));
143}
144
145unsafe extern "C" fn dispatch_properties_changed<T: ObjectImpl>(
146    obj: *mut gobject_ffi::GObject,
147    n_pspecs: u32,
148    pspecs: *mut *mut gobject_ffi::GParamSpec,
149) {
150    let instance = &*(obj as *mut T::Instance);
151    let imp = instance.imp();
152    imp.dispatch_properties_changed(Slice::from_glib_borrow_num(pspecs, n_pspecs as _));
153}
154
155unsafe extern "C" fn dispose<T: ObjectImpl>(obj: *mut gobject_ffi::GObject) {
156    let instance = &*(obj as *mut T::Instance);
157    let imp = instance.imp();
158
159    imp.dispose();
160
161    // Chain up to the parent's dispose.
162    let data = T::type_data();
163    let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
164    if let Some(ref func) = (*parent_class).dispose {
165        func(obj);
166    }
167}
168
169// rustdoc-stripper-ignore-next
170/// Trait containing only the property related functions of [`ObjectImpl`].
171/// Implemented by the [`Properties`](crate::Properties) macro.
172/// When implementing `ObjectImpl` you may want to delegate the function calls to this trait.
173pub trait DerivedObjectProperties: ObjectSubclass {
174    // rustdoc-stripper-ignore-next
175    /// Properties installed for this type.
176    fn derived_properties() -> &'static [ParamSpec] {
177        &[]
178    }
179
180    // rustdoc-stripper-ignore-next
181    /// Similar to [`ObjectImpl`](trait.ObjectImpl.html) but auto-generated by the [`Properties`](crate::Properties) macro
182    /// to allow handling more complex use-cases.
183    fn derived_set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) {
184        unimplemented!()
185    }
186
187    // rustdoc-stripper-ignore-next
188    /// Similar to [`ObjectImpl`](trait.ObjectImpl.html) but auto-generated by the [`Properties`](crate::Properties) macro
189    /// to allow handling more complex use-cases.
190    fn derived_property(&self, _id: usize, _pspec: &ParamSpec) -> Value {
191        unimplemented!()
192    }
193}
194
195// rustdoc-stripper-ignore-next
196/// Extension trait for `glib::Object`'s class struct.
197///
198/// This contains various class methods and allows subclasses to override signal class handlers.
199pub unsafe trait ObjectClassSubclassExt: Sized + 'static {
200    fn override_signal_class_handler<F>(&mut self, name: &str, class_handler: F)
201    where
202        F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option<Value> + Send + Sync + 'static,
203    {
204        unsafe {
205            super::types::signal_override_class_handler(
206                name,
207                *(self as *mut _ as *mut ffi::GType),
208                class_handler,
209            );
210        }
211    }
212}
213
214unsafe impl ObjectClassSubclassExt for crate::Class<Object> {}
215
216unsafe impl<T: ObjectImpl> IsSubclassable<T> for Object {
217    fn class_init(class: &mut crate::Class<Self>) {
218        let klass = class.as_mut();
219        klass.set_property = Some(set_property::<T>);
220        klass.get_property = Some(property::<T>);
221        klass.constructed = Some(constructed::<T>);
222        klass.notify = Some(notify::<T>);
223        klass.dispatch_properties_changed = Some(dispatch_properties_changed::<T>);
224        klass.dispose = Some(dispose::<T>);
225
226        let pspecs = <T as ObjectImpl>::properties();
227        if !pspecs.is_empty() {
228            unsafe {
229                let mut pspecs_ptrs = Vec::with_capacity(pspecs.len() + 1);
230
231                pspecs_ptrs.push(ptr::null_mut());
232
233                for pspec in pspecs {
234                    pspecs_ptrs.push(pspec.to_glib_none().0);
235                }
236
237                gobject_ffi::g_object_class_install_properties(
238                    klass,
239                    pspecs_ptrs.len() as u32,
240                    pspecs_ptrs.as_mut_ptr(),
241                );
242            }
243        }
244
245        let type_ = T::type_();
246        let signals = <T as ObjectImpl>::signals();
247        for signal in signals {
248            signal.register(type_);
249        }
250    }
251
252    #[inline]
253    fn instance_init(_instance: &mut super::InitializingObject<T>) {}
254}
255
256mod sealed {
257    pub trait Sealed {}
258    impl<T: super::ObjectImplExt> Sealed for T {}
259}
260
261pub trait ObjectImplExt: sealed::Sealed + ObjectSubclass {
262    // rustdoc-stripper-ignore-next
263    /// Chain up to the parent class' implementation of `glib::Object::constructed()`.
264    #[inline]
265    fn parent_constructed(&self) {
266        unsafe {
267            let data = Self::type_data();
268            let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
269
270            if let Some(ref func) = (*parent_class).constructed {
271                func(self.obj().unsafe_cast_ref::<Object>().to_glib_none().0);
272            }
273        }
274    }
275
276    // rustdoc-stripper-ignore-next
277    /// Chain up to the parent class' implementation of `glib::Object::notify()`.
278    #[inline]
279    fn parent_notify(&self, pspec: &ParamSpec) {
280        unsafe {
281            let data = Self::type_data();
282            let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
283
284            if let Some(ref func) = (*parent_class).notify {
285                func(
286                    self.obj().unsafe_cast_ref::<Object>().to_glib_none().0,
287                    pspec.to_glib_none().0,
288                );
289            }
290        }
291    }
292
293    // rustdoc-stripper-ignore-next
294    /// Chain up to the parent class' implementation of `glib::Object::dispatch_properties_changed()`.
295    #[inline]
296    fn parent_dispatch_properties_changed(&self, pspecs: &[ParamSpec]) {
297        unsafe {
298            let data = Self::type_data();
299            let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
300
301            if let Some(ref func) = (*parent_class).dispatch_properties_changed {
302                func(
303                    self.obj().unsafe_cast_ref::<Object>().to_glib_none().0,
304                    pspecs.len() as _,
305                    pspecs.as_ptr() as *mut _,
306                );
307            }
308        }
309    }
310
311    // rustdoc-stripper-ignore-next
312    /// Chain up to parent class signal handler.
313    fn signal_chain_from_overridden(
314        &self,
315        token: &super::SignalClassHandlerToken,
316        values: &[Value],
317    ) -> Option<Value> {
318        unsafe {
319            super::types::signal_chain_from_overridden(self.obj().as_ptr() as *mut _, token, values)
320        }
321    }
322}
323
324impl<T: ObjectImpl> ObjectImplExt for T {}
325
326#[cfg(test)]
327mod test {
328    use std::cell::RefCell;
329
330    use super::*;
331    // We rename the current crate as glib, since the macros in glib-macros
332    // generate the glib namespace through the crate_ident_new utility,
333    // and that returns `glib` (and not `crate`) when called inside the glib crate
334    use crate as glib;
335
336    mod imp {
337        use std::sync::OnceLock;
338
339        use super::*;
340
341        // A dummy `Object` to test setting an `Object` property and returning an `Object` in signals
342        #[derive(Default)]
343        pub struct ChildObject;
344
345        #[glib::object_subclass]
346        impl ObjectSubclass for ChildObject {
347            const NAME: &'static str = "ChildObject";
348            type Type = super::ChildObject;
349        }
350
351        impl ObjectImpl for ChildObject {}
352
353        pub struct SimpleObject {
354            name: RefCell<Option<String>>,
355            construct_name: RefCell<Option<String>>,
356            constructed: RefCell<bool>,
357            answer: RefCell<i32>,
358            array: RefCell<Vec<String>>,
359        }
360
361        impl Default for SimpleObject {
362            fn default() -> Self {
363                SimpleObject {
364                    name: Default::default(),
365                    construct_name: Default::default(),
366                    constructed: Default::default(),
367                    answer: RefCell::new(42i32),
368                    array: RefCell::new(vec!["default0".to_string(), "default1".to_string()]),
369                }
370            }
371        }
372
373        #[glib::object_subclass]
374        impl ObjectSubclass for SimpleObject {
375            const NAME: &'static str = "SimpleObject";
376            type Type = super::SimpleObject;
377            type Interfaces = (super::Dummy,);
378        }
379
380        impl ObjectImpl for SimpleObject {
381            fn properties() -> &'static [ParamSpec] {
382                static PROPERTIES: OnceLock<Vec<ParamSpec>> = OnceLock::new();
383                PROPERTIES.get_or_init(|| {
384                    vec![
385                        crate::ParamSpecString::builder("name").build(),
386                        crate::ParamSpecString::builder("construct-name")
387                            .construct_only()
388                            .build(),
389                        crate::ParamSpecBoolean::builder("constructed")
390                            .read_only()
391                            .build(),
392                        crate::ParamSpecObject::builder::<super::ChildObject>("child").build(),
393                        crate::ParamSpecInt::builder("answer")
394                            .default_value(42i32)
395                            .build(),
396                        crate::ParamSpecValueArray::builder("array").build(),
397                    ]
398                })
399            }
400
401            fn signals() -> &'static [super::Signal] {
402                static SIGNALS: OnceLock<Vec<super::Signal>> = OnceLock::new();
403                SIGNALS.get_or_init(|| {
404                    vec![
405                        super::Signal::builder("name-changed")
406                            .param_types([String::static_type()])
407                            .build(),
408                        super::Signal::builder("change-name")
409                            .param_types([String::static_type()])
410                            .return_type::<String>()
411                            .action()
412                            .class_handler(|_, args| {
413                                let obj = args[0]
414                                    .get::<super::SimpleObject>()
415                                    .expect("Failed to get Object from args[0]");
416                                let new_name = args[1]
417                                    .get::<String>()
418                                    .expect("Failed to get Object from args[1]");
419                                let imp = obj.imp();
420
421                                let old_name = imp.name.replace(Some(new_name));
422
423                                obj.emit_by_name::<()>("name-changed", &[&*imp.name.borrow()]);
424
425                                Some(old_name.to_value())
426                            })
427                            .build(),
428                        super::Signal::builder("create-string")
429                            .return_type::<String>()
430                            .build(),
431                        super::Signal::builder("create-child-object")
432                            .return_type::<super::ChildObject>()
433                            .build(),
434                    ]
435                })
436            }
437
438            fn set_property(&self, _id: usize, value: &Value, pspec: &crate::ParamSpec) {
439                match pspec.name() {
440                    "name" => {
441                        let name = value
442                            .get()
443                            .expect("type conformity checked by 'Object::set_property'");
444                        self.name.replace(name);
445                        self.obj()
446                            .emit_by_name::<()>("name-changed", &[&*self.name.borrow()]);
447                    }
448                    "construct-name" => {
449                        let name = value
450                            .get()
451                            .expect("type conformity checked by 'Object::set_property'");
452                        self.construct_name.replace(name);
453                    }
454                    "child" => {
455                        // not stored, only used to test `set_property` with `Objects`
456                    }
457                    "answer" => {
458                        let answer = value
459                            .get()
460                            .expect("type conformity checked by 'Object::set_property'");
461                        self.answer.replace(answer);
462                    }
463                    "array" => {
464                        let value = value
465                            .get::<crate::ValueArray>()
466                            .expect("type conformity checked by 'Object::set_property'");
467                        let mut array = self.array.borrow_mut();
468                        array.clear();
469                        array.extend(value.iter().map(|v| v.get().unwrap()));
470                    }
471                    _ => unimplemented!(),
472                }
473            }
474
475            fn property(&self, _id: usize, pspec: &crate::ParamSpec) -> Value {
476                match pspec.name() {
477                    "name" => self.name.borrow().to_value(),
478                    "construct-name" => self.construct_name.borrow().to_value(),
479                    "constructed" => self.constructed.borrow().to_value(),
480                    "answer" => self.answer.borrow().to_value(),
481                    "array" => crate::ValueArray::new(self.array.borrow().iter()).to_value(),
482                    _ => unimplemented!(),
483                }
484            }
485
486            fn constructed(&self) {
487                self.parent_constructed();
488
489                debug_assert_eq!(self as *const _, self.obj().imp() as *const _);
490
491                *self.constructed.borrow_mut() = true;
492            }
493        }
494
495        #[derive(Clone, Copy)]
496        #[repr(C)]
497        pub struct DummyInterface {
498            parent: gobject_ffi::GTypeInterface,
499        }
500
501        unsafe impl InterfaceStruct for DummyInterface {
502            type Type = Dummy;
503        }
504
505        pub enum Dummy {}
506
507        #[glib::object_interface]
508        impl ObjectInterface for Dummy {
509            const NAME: &'static str = "Dummy";
510            type Interface = DummyInterface;
511        }
512    }
513
514    wrapper! {
515        pub struct ChildObject(ObjectSubclass<imp::ChildObject>);
516    }
517
518    wrapper! {
519        pub struct SimpleObject(ObjectSubclass<imp::SimpleObject>);
520    }
521
522    wrapper! {
523        pub struct Dummy(ObjectInterface<imp::Dummy>);
524    }
525
526    unsafe impl<T: ObjectSubclass> IsImplementable<T> for Dummy {}
527
528    #[test]
529    fn test_create() {
530        let type_ = SimpleObject::static_type();
531        let obj = Object::with_type(type_);
532
533        assert!(obj.type_().is_a(Dummy::static_type()));
534
535        // Assert that the object representation is equivalent to the underlying C GObject pointer
536        assert_eq!(
537            mem::size_of::<SimpleObject>(),
538            mem::size_of::<ffi::gpointer>()
539        );
540        assert_eq!(obj.as_ptr() as ffi::gpointer, unsafe {
541            *(&obj as *const _ as *const ffi::gpointer)
542        });
543
544        assert!(obj.property::<bool>("constructed"));
545
546        let weak = obj.downgrade();
547        drop(obj);
548        assert!(weak.upgrade().is_none());
549    }
550
551    #[test]
552    fn test_properties() {
553        let type_ = SimpleObject::static_type();
554        let obj = Object::with_type(type_);
555
556        assert!(obj.type_().is_a(Dummy::static_type()));
557
558        let properties = obj.list_properties();
559        assert_eq!(properties.len(), 6);
560        assert_eq!(properties[0].name(), "name");
561        assert_eq!(properties[1].name(), "construct-name");
562        assert_eq!(properties[2].name(), "constructed");
563        assert_eq!(properties[3].name(), "child");
564        assert_eq!(properties[4].name(), "answer");
565        assert_eq!(properties[5].name(), "array");
566    }
567
568    #[test]
569    fn test_create_child_object() {
570        let obj: ChildObject = Object::new();
571
572        assert_eq!(&obj, obj.imp().obj().as_ref());
573    }
574
575    #[test]
576    fn test_builder() {
577        let obj = Object::builder::<SimpleObject>()
578            .property("construct-name", "meh")
579            .property("name", "initial")
580            .build();
581
582        assert_eq!(
583            obj.property::<String>("construct-name"),
584            String::from("meh")
585        );
586
587        assert_eq!(obj.property::<String>("name"), String::from("initial"));
588    }
589
590    #[test]
591    fn test_set_property() {
592        let obj = Object::builder::<SimpleObject>()
593            .property("construct-name", "meh")
594            .property("name", "initial")
595            .build();
596
597        assert_eq!(
598            obj.property::<String>("construct-name"),
599            String::from("meh")
600        );
601
602        assert_eq!(
603            obj.property::<String>("construct-name"),
604            String::from("meh")
605        );
606
607        assert_eq!(obj.property::<String>("name"), String::from("initial"));
608        obj.set_property("name", "test");
609        assert_eq!(obj.property::<String>("name"), String::from("test"));
610
611        let child = Object::with_type(ChildObject::static_type());
612        obj.set_property("child", &child);
613    }
614
615    #[test]
616    fn builder_property_if() {
617        use crate::ValueArray;
618
619        let array = ["val0", "val1"];
620        let obj = Object::builder::<SimpleObject>()
621            .property_if("name", "some name", true)
622            .property_if("answer", 21i32, 6 != 9)
623            .property_if("array", ValueArray::new(["val0", "val1"]), array.len() == 2)
624            .build();
625
626        assert_eq!(obj.property::<String>("name").as_str(), "some name");
627        assert_eq!(
628            obj.property::<Option<String>>("name").as_deref(),
629            Some("some name")
630        );
631        assert_eq!(obj.property::<i32>("answer"), 21);
632        assert!(obj
633            .property::<ValueArray>("array")
634            .iter()
635            .map(|val| val.get::<&str>().unwrap())
636            .eq(array));
637
638        let obj = Object::builder::<SimpleObject>()
639            .property_if("name", "some name", false)
640            .property_if("answer", 21i32, 6 == 9)
641            .property_if("array", ValueArray::new(array), array.len() == 4)
642            .build();
643
644        assert!(obj.property::<Option<String>>("name").is_none());
645        assert_eq!(obj.property::<i32>("answer"), 42);
646        assert!(obj
647            .property::<ValueArray>("array")
648            .iter()
649            .map(|val| val.get::<&str>().unwrap())
650            .eq(["default0", "default1"]));
651    }
652
653    #[test]
654    fn builder_property_if_some() {
655        use crate::ValueArray;
656
657        let array = ["val0", "val1"];
658        let obj = Object::builder::<SimpleObject>()
659            .property_if_some("name", Some("some name"))
660            .property_if_some("answer", Some(21i32))
661            .property_if_some("array", Some(ValueArray::new(array)))
662            .build();
663
664        assert_eq!(obj.property::<String>("name").as_str(), "some name");
665        assert_eq!(
666            obj.property::<Option<String>>("name").as_deref(),
667            Some("some name")
668        );
669        assert_eq!(obj.property::<i32>("answer"), 21);
670        assert!(obj
671            .property::<ValueArray>("array")
672            .iter()
673            .map(|val| val.get::<&str>().unwrap())
674            .eq(array));
675
676        let obj = Object::builder::<SimpleObject>()
677            .property_if_some("name", Option::<&str>::None)
678            .property_if_some("answer", Option::<i32>::None)
679            .property_if_some("array", Option::<ValueArray>::None)
680            .build();
681
682        assert!(obj.property::<Option<String>>("name").is_none());
683        assert_eq!(obj.property::<i32>("answer"), 42);
684        assert!(obj
685            .property::<ValueArray>("array")
686            .iter()
687            .map(|val| val.get::<&str>().unwrap())
688            .eq(["default0", "default1"]));
689    }
690
691    #[test]
692    fn builder_property_from_iter() {
693        use crate::ValueArray;
694
695        let array = ["val0", "val1"];
696        let obj = Object::builder::<SimpleObject>()
697            .property_from_iter::<ValueArray>("array", &array)
698            .build();
699
700        assert!(obj
701            .property::<ValueArray>("array")
702            .iter()
703            .map(|val| val.get::<&str>().unwrap())
704            .eq(array));
705
706        let obj = Object::builder::<SimpleObject>()
707            .property_from_iter::<ValueArray>("array", Vec::<&str>::new())
708            .build();
709
710        assert!(obj.property::<ValueArray>("array").is_empty());
711    }
712
713    #[test]
714    fn builder_property_if_not_empty() {
715        use crate::ValueArray;
716
717        let array = ["val0", "val1"];
718        let obj = Object::builder::<SimpleObject>()
719            .property_if_not_empty::<ValueArray>("array", &array)
720            .build();
721
722        assert!(obj
723            .property::<ValueArray>("array")
724            .iter()
725            .map(|val| val.get::<&str>().unwrap())
726            .eq(array));
727
728        let empty_vec = Vec::<String>::new();
729        let obj = Object::builder::<SimpleObject>()
730            .property_if_not_empty::<ValueArray>("array", &empty_vec)
731            .build();
732
733        assert!(obj
734            .property::<ValueArray>("array")
735            .iter()
736            .map(|val| val.get::<&str>().unwrap())
737            .eq(["default0", "default1"]));
738    }
739
740    #[test]
741    #[should_panic = "property 'construct-name' of type 'SimpleObject' is not writable"]
742    fn test_set_property_non_writable() {
743        let obj = Object::builder::<SimpleObject>()
744            .property("construct-name", "meh")
745            .property("name", "initial")
746            .build();
747
748        obj.set_property("construct-name", "test");
749    }
750
751    #[test]
752    #[should_panic = "property 'test' of type 'SimpleObject' not found"]
753    fn test_set_property_not_found() {
754        let obj = Object::builder::<SimpleObject>()
755            .property("construct-name", "meh")
756            .property("name", "initial")
757            .build();
758
759        obj.set_property("test", true);
760    }
761
762    #[test]
763    #[should_panic = "property 'constructed' of type 'SimpleObject' is not writable"]
764    fn test_set_property_not_writable() {
765        let obj = Object::builder::<SimpleObject>()
766            .property("construct-name", "meh")
767            .property("name", "initial")
768            .build();
769
770        obj.set_property("constructed", false);
771    }
772
773    #[test]
774    #[should_panic = "property 'name' of type 'SimpleObject' can't be set from the given type (expected: 'gchararray', got: 'gboolean')"]
775    fn test_set_property_wrong_type() {
776        let obj = Object::builder::<SimpleObject>()
777            .property("construct-name", "meh")
778            .property("name", "initial")
779            .build();
780
781        obj.set_property("name", false);
782    }
783
784    #[test]
785    #[should_panic = "property 'child' of type 'SimpleObject' can't be set from the given type (expected: 'ChildObject', got: 'SimpleObject')"]
786    fn test_set_property_wrong_type_2() {
787        let obj = Object::builder::<SimpleObject>()
788            .property("construct-name", "meh")
789            .property("name", "initial")
790            .build();
791
792        let other_obj = Object::with_type(SimpleObject::static_type());
793
794        obj.set_property("child", &other_obj);
795    }
796
797    #[test]
798    #[should_panic = "Can't set construct property 'construct-name' for type 'SimpleObject' twice"]
799    fn test_construct_property_set_twice() {
800        let _obj = Object::builder::<SimpleObject>()
801            .property("construct-name", "meh")
802            .property("construct-name", "meh2")
803            .build();
804    }
805
806    #[test]
807    fn test_signals() {
808        use std::sync::{
809            atomic::{AtomicBool, Ordering},
810            Arc,
811        };
812
813        let obj = Object::builder::<SimpleObject>()
814            .property("name", "old-name")
815            .build();
816
817        let name_changed_triggered = Arc::new(AtomicBool::new(false));
818        let name_changed_clone = name_changed_triggered.clone();
819        obj.connect("name-changed", false, move |args| {
820            let _obj = args[0].get::<Object>().expect("Failed to get args[0]");
821            let name = args[1].get::<&str>().expect("Failed to get args[1]");
822
823            assert_eq!(name, "new-name");
824            name_changed_clone.store(true, Ordering::Relaxed);
825
826            None
827        });
828
829        assert_eq!(obj.property::<String>("name"), String::from("old-name"));
830        assert!(!name_changed_triggered.load(Ordering::Relaxed));
831
832        assert_eq!(
833            obj.emit_by_name::<String>("change-name", &[&"new-name"]),
834            "old-name"
835        );
836        assert!(name_changed_triggered.load(Ordering::Relaxed));
837    }
838
839    #[test]
840    fn test_signal_return_expected_type() {
841        let obj = Object::with_type(SimpleObject::static_type());
842
843        obj.connect("create-string", false, move |_args| {
844            Some("return value".to_value())
845        });
846
847        let signal_id = imp::SimpleObject::signals()[2].signal_id();
848
849        let value = obj.emit::<String>(signal_id, &[]);
850        assert_eq!(value, "return value");
851    }
852
853    #[test]
854    fn test_callback_validity() {
855        use std::sync::{
856            atomic::{AtomicBool, Ordering},
857            Arc,
858        };
859
860        let obj = Object::builder::<SimpleObject>()
861            .property("name", "old-name")
862            .build();
863
864        let name_changed_triggered = Arc::new(AtomicBool::new(false));
865        let name_changed_clone = name_changed_triggered.clone();
866
867        obj.connect_notify(Some("name"), move |_, _| {
868            name_changed_clone.store(true, Ordering::Relaxed);
869        });
870        obj.notify("name");
871        assert!(name_changed_triggered.load(Ordering::Relaxed));
872    }
873
874    // Note: can't test type mismatch in signals since panics across FFI boundaries
875    // are UB. See https://github.com/gtk-rs/glib/issues/518
876
877    #[test]
878    fn test_signal_return_expected_object_type() {
879        let obj = Object::with_type(SimpleObject::static_type());
880
881        obj.connect("create-child-object", false, move |_args| {
882            Some(Object::with_type(ChildObject::static_type()).to_value())
883        });
884        let value: glib::Object = obj.emit_by_name("create-child-object", &[]);
885        assert!(value.type_().is_a(ChildObject::static_type()));
886    }
887}