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