Skip to main content

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