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<Type: IsA<Object>> {
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
256pub trait ObjectImplExt: ObjectImpl {
257    // rustdoc-stripper-ignore-next
258    /// Chain up to the parent class' implementation of `glib::Object::constructed()`.
259    #[inline]
260    fn parent_constructed(&self) {
261        unsafe {
262            let data = Self::type_data();
263            let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
264
265            if let Some(ref func) = (*parent_class).constructed {
266                func(self.obj().unsafe_cast_ref::<Object>().to_glib_none().0);
267            }
268        }
269    }
270
271    // rustdoc-stripper-ignore-next
272    /// Chain up to the parent class' implementation of `glib::Object::notify()`.
273    #[inline]
274    fn parent_notify(&self, pspec: &ParamSpec) {
275        unsafe {
276            let data = Self::type_data();
277            let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
278
279            if let Some(ref func) = (*parent_class).notify {
280                func(
281                    self.obj().unsafe_cast_ref::<Object>().to_glib_none().0,
282                    pspec.to_glib_none().0,
283                );
284            }
285        }
286    }
287
288    // rustdoc-stripper-ignore-next
289    /// Chain up to the parent class' implementation of `glib::Object::dispatch_properties_changed()`.
290    #[inline]
291    fn parent_dispatch_properties_changed(&self, pspecs: &[ParamSpec]) {
292        unsafe {
293            let data = Self::type_data();
294            let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass;
295
296            if let Some(ref func) = (*parent_class).dispatch_properties_changed {
297                func(
298                    self.obj().unsafe_cast_ref::<Object>().to_glib_none().0,
299                    pspecs.len() as _,
300                    pspecs.as_ptr() as *mut _,
301                );
302            }
303        }
304    }
305
306    // rustdoc-stripper-ignore-next
307    /// Chain up to parent class signal handler.
308    fn signal_chain_from_overridden(
309        &self,
310        token: &super::SignalClassHandlerToken,
311        values: &[Value],
312    ) -> Option<Value> {
313        unsafe {
314            super::types::signal_chain_from_overridden(self.obj().as_ptr() as *mut _, token, values)
315        }
316    }
317}
318
319impl<T: ObjectImpl> ObjectImplExt for T {}
320
321#[cfg(test)]
322mod test {
323    use std::cell::RefCell;
324
325    use super::*;
326    // We rename the current crate as glib, since the macros in glib-macros
327    // generate the glib namespace through the crate_ident_new utility,
328    // and that returns `glib` (and not `crate`) when called inside the glib crate
329    use crate as glib;
330
331    mod imp {
332        use std::sync::OnceLock;
333
334        use super::*;
335
336        // A dummy `Object` to test setting an `Object` property and returning an `Object` in signals
337        #[derive(Default)]
338        pub struct ChildObject;
339
340        #[glib::object_subclass]
341        impl ObjectSubclass for ChildObject {
342            const NAME: &'static str = "ChildObject";
343            type Type = super::ChildObject;
344        }
345
346        impl ObjectImpl for ChildObject {}
347
348        pub struct SimpleObject {
349            name: RefCell<Option<String>>,
350            construct_name: RefCell<Option<String>>,
351            constructed: RefCell<bool>,
352            answer: RefCell<i32>,
353            array: RefCell<Vec<String>>,
354        }
355
356        impl Default for SimpleObject {
357            fn default() -> Self {
358                SimpleObject {
359                    name: Default::default(),
360                    construct_name: Default::default(),
361                    constructed: Default::default(),
362                    answer: RefCell::new(42i32),
363                    array: RefCell::new(vec!["default0".to_string(), "default1".to_string()]),
364                }
365            }
366        }
367
368        #[glib::object_subclass]
369        impl ObjectSubclass for SimpleObject {
370            const NAME: &'static str = "SimpleObject";
371            type Type = super::SimpleObject;
372            type Interfaces = (super::Dummy,);
373        }
374
375        impl ObjectImpl for SimpleObject {
376            fn properties() -> &'static [ParamSpec] {
377                static PROPERTIES: OnceLock<Vec<ParamSpec>> = OnceLock::new();
378                PROPERTIES.get_or_init(|| {
379                    vec![
380                        crate::ParamSpecString::builder("name").build(),
381                        crate::ParamSpecString::builder("construct-name")
382                            .construct_only()
383                            .build(),
384                        crate::ParamSpecBoolean::builder("constructed")
385                            .read_only()
386                            .build(),
387                        crate::ParamSpecObject::builder::<super::ChildObject>("child").build(),
388                        crate::ParamSpecInt::builder("answer")
389                            .default_value(42i32)
390                            .build(),
391                        crate::ParamSpecValueArray::builder("array").build(),
392                    ]
393                })
394            }
395
396            fn signals() -> &'static [super::Signal] {
397                static SIGNALS: OnceLock<Vec<super::Signal>> = OnceLock::new();
398                SIGNALS.get_or_init(|| {
399                    vec![
400                        super::Signal::builder("name-changed")
401                            .param_types([String::static_type()])
402                            .build(),
403                        super::Signal::builder("change-name")
404                            .param_types([String::static_type()])
405                            .return_type::<String>()
406                            .action()
407                            .class_handler(|args| {
408                                let obj = args[0]
409                                    .get::<super::SimpleObject>()
410                                    .expect("Failed to get Object from args[0]");
411                                let new_name = args[1]
412                                    .get::<String>()
413                                    .expect("Failed to get Object from args[1]");
414                                let imp = obj.imp();
415
416                                let old_name = imp.name.replace(Some(new_name));
417
418                                obj.emit_by_name::<()>("name-changed", &[&*imp.name.borrow()]);
419
420                                Some(old_name.to_value())
421                            })
422                            .build(),
423                        super::Signal::builder("create-string")
424                            .return_type::<String>()
425                            .accumulator(|_hint, acc, val| {
426                                // join all strings from signal handlers by newline
427                                let mut acc = acc
428                                    .get_owned::<Option<String>>()
429                                    .unwrap()
430                                    .map(|mut acc| {
431                                        acc.push('\n');
432                                        acc
433                                    })
434                                    .unwrap_or_default();
435                                acc.push_str(val.get::<&str>().unwrap());
436                                std::ops::ControlFlow::Continue(acc.to_value())
437                            })
438                            .build(),
439                        super::Signal::builder("create-child-object")
440                            .return_type::<super::ChildObject>()
441                            .build(),
442                        super::Signal::builder("return-string")
443                            .return_type::<String>()
444                            .action()
445                            .class_handler(|args| {
446                                let _obj = args[0]
447                                    .get::<super::SimpleObject>()
448                                    .expect("Failed to get Object from args[0]");
449                                Some("base".to_value())
450                            })
451                            .build(),
452                    ]
453                })
454            }
455
456            fn set_property(&self, _id: usize, value: &Value, pspec: &crate::ParamSpec) {
457                match pspec.name() {
458                    "name" => {
459                        let name = value
460                            .get()
461                            .expect("type conformity checked by 'Object::set_property'");
462                        self.name.replace(name);
463                        self.obj()
464                            .emit_by_name::<()>("name-changed", &[&*self.name.borrow()]);
465                    }
466                    "construct-name" => {
467                        let name = value
468                            .get()
469                            .expect("type conformity checked by 'Object::set_property'");
470                        self.construct_name.replace(name);
471                    }
472                    "child" => {
473                        // not stored, only used to test `set_property` with `Objects`
474                    }
475                    "answer" => {
476                        let answer = value
477                            .get()
478                            .expect("type conformity checked by 'Object::set_property'");
479                        self.answer.replace(answer);
480                    }
481                    "array" => {
482                        let value = value
483                            .get::<crate::ValueArray>()
484                            .expect("type conformity checked by 'Object::set_property'");
485                        let mut array = self.array.borrow_mut();
486                        array.clear();
487                        array.extend(value.iter().map(|v| v.get().unwrap()));
488                    }
489                    _ => unimplemented!(),
490                }
491            }
492
493            fn property(&self, _id: usize, pspec: &crate::ParamSpec) -> Value {
494                match pspec.name() {
495                    "name" => self.name.borrow().to_value(),
496                    "construct-name" => self.construct_name.borrow().to_value(),
497                    "constructed" => self.constructed.borrow().to_value(),
498                    "answer" => self.answer.borrow().to_value(),
499                    "array" => crate::ValueArray::new(self.array.borrow().iter()).to_value(),
500                    _ => unimplemented!(),
501                }
502            }
503
504            fn constructed(&self) {
505                self.parent_constructed();
506
507                debug_assert_eq!(self as *const _, self.obj().imp() as *const _);
508
509                *self.constructed.borrow_mut() = true;
510            }
511        }
512
513        #[derive(Default)]
514        pub struct SimpleSubObject;
515
516        #[glib::object_subclass]
517        impl ObjectSubclass for SimpleSubObject {
518            const NAME: &'static str = "SimpleSubObject";
519            type Type = super::SimpleSubObject;
520            type ParentType = super::SimpleObject;
521
522            fn class_init(class: &mut Self::Class) {
523                class.override_signal_class_handler("return-string", |token, args| {
524                    let obj = args[0]
525                        .get::<super::SimpleSubObject>()
526                        .expect("Failed to get Object from args[0]");
527
528                    let res = obj.imp().signal_chain_from_overridden(token, args);
529                    assert_eq!(res.unwrap().get::<&str>().unwrap(), "base");
530
531                    Some("sub".to_value())
532                });
533            }
534        }
535
536        impl ObjectImpl for SimpleSubObject {}
537
538        impl SimpleObjectImpl for SimpleSubObject {}
539
540        #[derive(Clone, Copy)]
541        #[repr(C)]
542        pub struct DummyInterface {
543            parent: gobject_ffi::GTypeInterface,
544        }
545
546        unsafe impl InterfaceStruct for DummyInterface {
547            type Type = Dummy;
548        }
549
550        pub enum Dummy {}
551
552        #[glib::object_interface]
553        impl ObjectInterface for Dummy {
554            const NAME: &'static str = "Dummy";
555            type Interface = DummyInterface;
556        }
557    }
558
559    wrapper! {
560        pub struct ChildObject(ObjectSubclass<imp::ChildObject>);
561    }
562
563    wrapper! {
564        pub struct SimpleObject(ObjectSubclass<imp::SimpleObject>);
565    }
566
567    pub trait SimpleObjectImpl: ObjectImpl {}
568
569    unsafe impl<Obj: SimpleObjectImpl> IsSubclassable<Obj> for SimpleObject {}
570
571    wrapper! {
572        pub struct SimpleSubObject(ObjectSubclass<imp::SimpleSubObject>) @extends SimpleObject;
573    }
574
575    wrapper! {
576        pub struct Dummy(ObjectInterface<imp::Dummy>);
577    }
578
579    unsafe impl<T: ObjectSubclass> IsImplementable<T> for Dummy {}
580
581    #[test]
582    fn test_create() {
583        let type_ = SimpleObject::static_type();
584        let obj = Object::with_type(type_);
585
586        assert!(obj.type_().is_a(Dummy::static_type()));
587
588        // Assert that the object representation is equivalent to the underlying C GObject pointer
589        assert_eq!(
590            mem::size_of::<SimpleObject>(),
591            mem::size_of::<ffi::gpointer>()
592        );
593        assert_eq!(obj.as_ptr() as ffi::gpointer, unsafe {
594            *(&obj as *const _ as *const ffi::gpointer)
595        });
596
597        assert!(obj.property::<bool>("constructed"));
598
599        let weak = obj.downgrade();
600        drop(obj);
601        assert!(weak.upgrade().is_none());
602    }
603
604    #[test]
605    fn test_sub_create() {
606        let obj = Object::builder::<SimpleSubObject>().build();
607        assert!(obj.type_().is_a(SimpleObject::static_type()));
608        assert_eq!(obj.type_(), SimpleSubObject::static_type());
609        assert!(obj.property::<bool>("constructed"));
610    }
611
612    #[test]
613    fn test_properties() {
614        let type_ = SimpleObject::static_type();
615        let obj = Object::with_type(type_);
616
617        assert!(obj.type_().is_a(Dummy::static_type()));
618
619        let properties = obj.list_properties();
620        assert_eq!(properties.len(), 6);
621        assert_eq!(properties[0].name(), "name");
622        assert_eq!(properties[1].name(), "construct-name");
623        assert_eq!(properties[2].name(), "constructed");
624        assert_eq!(properties[3].name(), "child");
625        assert_eq!(properties[4].name(), "answer");
626        assert_eq!(properties[5].name(), "array");
627    }
628
629    #[test]
630    fn test_create_child_object() {
631        let obj: ChildObject = Object::new();
632
633        assert_eq!(&obj, obj.imp().obj().as_ref());
634    }
635
636    #[test]
637    fn test_builder() {
638        let obj = Object::builder::<SimpleObject>()
639            .property("construct-name", "meh")
640            .property("name", "initial")
641            .build();
642
643        assert_eq!(
644            obj.property::<String>("construct-name"),
645            String::from("meh")
646        );
647
648        assert_eq!(obj.property::<String>("name"), String::from("initial"));
649    }
650
651    #[test]
652    fn test_set_property() {
653        let obj = Object::builder::<SimpleObject>()
654            .property("construct-name", "meh")
655            .property("name", "initial")
656            .build();
657
658        assert_eq!(
659            obj.property::<String>("construct-name"),
660            String::from("meh")
661        );
662
663        assert_eq!(
664            obj.property::<String>("construct-name"),
665            String::from("meh")
666        );
667
668        assert_eq!(obj.property::<String>("name"), String::from("initial"));
669        obj.set_property("name", "test");
670        assert_eq!(obj.property::<String>("name"), String::from("test"));
671
672        let child = Object::with_type(ChildObject::static_type());
673        obj.set_property("child", &child);
674    }
675
676    #[test]
677    fn builder_property_if() {
678        use crate::ValueArray;
679
680        let array = ["val0", "val1"];
681        let obj = Object::builder::<SimpleObject>()
682            .property_if("name", "some name", true)
683            .property_if("answer", 21i32, 6 != 9)
684            .property_if("array", ValueArray::new(["val0", "val1"]), array.len() == 2)
685            .build();
686
687        assert_eq!(obj.property::<String>("name").as_str(), "some name");
688        assert_eq!(
689            obj.property::<Option<String>>("name").as_deref(),
690            Some("some name")
691        );
692        assert_eq!(obj.property::<i32>("answer"), 21);
693        assert!(obj
694            .property::<ValueArray>("array")
695            .iter()
696            .map(|val| val.get::<&str>().unwrap())
697            .eq(array));
698
699        let obj = Object::builder::<SimpleObject>()
700            .property_if("name", "some name", false)
701            .property_if("answer", 21i32, 6 == 9)
702            .property_if("array", ValueArray::new(array), array.len() == 4)
703            .build();
704
705        assert!(obj.property::<Option<String>>("name").is_none());
706        assert_eq!(obj.property::<i32>("answer"), 42);
707        assert!(obj
708            .property::<ValueArray>("array")
709            .iter()
710            .map(|val| val.get::<&str>().unwrap())
711            .eq(["default0", "default1"]));
712    }
713
714    #[test]
715    fn builder_property_if_some() {
716        use crate::ValueArray;
717
718        let array = ["val0", "val1"];
719        let obj = Object::builder::<SimpleObject>()
720            .property_if_some("name", Some("some name"))
721            .property_if_some("answer", Some(21i32))
722            .property_if_some("array", Some(ValueArray::new(array)))
723            .build();
724
725        assert_eq!(obj.property::<String>("name").as_str(), "some name");
726        assert_eq!(
727            obj.property::<Option<String>>("name").as_deref(),
728            Some("some name")
729        );
730        assert_eq!(obj.property::<i32>("answer"), 21);
731        assert!(obj
732            .property::<ValueArray>("array")
733            .iter()
734            .map(|val| val.get::<&str>().unwrap())
735            .eq(array));
736
737        let obj = Object::builder::<SimpleObject>()
738            .property_if_some("name", Option::<&str>::None)
739            .property_if_some("answer", Option::<i32>::None)
740            .property_if_some("array", Option::<ValueArray>::None)
741            .build();
742
743        assert!(obj.property::<Option<String>>("name").is_none());
744        assert_eq!(obj.property::<i32>("answer"), 42);
745        assert!(obj
746            .property::<ValueArray>("array")
747            .iter()
748            .map(|val| val.get::<&str>().unwrap())
749            .eq(["default0", "default1"]));
750    }
751
752    #[test]
753    fn builder_property_from_iter() {
754        use crate::ValueArray;
755
756        let array = ["val0", "val1"];
757        let obj = Object::builder::<SimpleObject>()
758            .property_from_iter::<ValueArray>("array", &array)
759            .build();
760
761        assert!(obj
762            .property::<ValueArray>("array")
763            .iter()
764            .map(|val| val.get::<&str>().unwrap())
765            .eq(array));
766
767        let obj = Object::builder::<SimpleObject>()
768            .property_from_iter::<ValueArray>("array", Vec::<&str>::new())
769            .build();
770
771        assert!(obj.property::<ValueArray>("array").is_empty());
772    }
773
774    #[test]
775    fn builder_property_if_not_empty() {
776        use crate::ValueArray;
777
778        let array = ["val0", "val1"];
779        let obj = Object::builder::<SimpleObject>()
780            .property_if_not_empty::<ValueArray>("array", &array)
781            .build();
782
783        assert!(obj
784            .property::<ValueArray>("array")
785            .iter()
786            .map(|val| val.get::<&str>().unwrap())
787            .eq(array));
788
789        let empty_vec = Vec::<String>::new();
790        let obj = Object::builder::<SimpleObject>()
791            .property_if_not_empty::<ValueArray>("array", &empty_vec)
792            .build();
793
794        assert!(obj
795            .property::<ValueArray>("array")
796            .iter()
797            .map(|val| val.get::<&str>().unwrap())
798            .eq(["default0", "default1"]));
799    }
800
801    #[test]
802    #[should_panic = "property 'construct-name' of type 'SimpleObject' is not writable"]
803    fn test_set_property_non_writable() {
804        let obj = Object::builder::<SimpleObject>()
805            .property("construct-name", "meh")
806            .property("name", "initial")
807            .build();
808
809        obj.set_property("construct-name", "test");
810    }
811
812    #[test]
813    #[should_panic = "property 'test' of type 'SimpleObject' not found"]
814    fn test_set_property_not_found() {
815        let obj = Object::builder::<SimpleObject>()
816            .property("construct-name", "meh")
817            .property("name", "initial")
818            .build();
819
820        obj.set_property("test", true);
821    }
822
823    #[test]
824    #[should_panic = "property 'constructed' of type 'SimpleObject' is not writable"]
825    fn test_set_property_not_writable() {
826        let obj = Object::builder::<SimpleObject>()
827            .property("construct-name", "meh")
828            .property("name", "initial")
829            .build();
830
831        obj.set_property("constructed", false);
832    }
833
834    #[test]
835    #[should_panic = "property 'name' of type 'SimpleObject' can't be set from the given type (expected: 'gchararray', got: 'gboolean')"]
836    fn test_set_property_wrong_type() {
837        let obj = Object::builder::<SimpleObject>()
838            .property("construct-name", "meh")
839            .property("name", "initial")
840            .build();
841
842        obj.set_property("name", false);
843    }
844
845    #[test]
846    #[should_panic = "property 'child' of type 'SimpleObject' can't be set from the given type (expected: 'ChildObject', got: 'SimpleObject')"]
847    fn test_set_property_wrong_type_2() {
848        let obj = Object::builder::<SimpleObject>()
849            .property("construct-name", "meh")
850            .property("name", "initial")
851            .build();
852
853        let other_obj = Object::with_type(SimpleObject::static_type());
854
855        obj.set_property("child", &other_obj);
856    }
857
858    #[test]
859    #[should_panic = "Can't set construct property 'construct-name' for type 'SimpleObject' twice"]
860    fn test_construct_property_set_twice() {
861        let _obj = Object::builder::<SimpleObject>()
862            .property("construct-name", "meh")
863            .property("construct-name", "meh2")
864            .build();
865    }
866
867    #[test]
868    fn test_signals() {
869        use std::sync::{
870            atomic::{AtomicBool, Ordering},
871            Arc,
872        };
873
874        let obj = Object::builder::<SimpleObject>()
875            .property("name", "old-name")
876            .build();
877
878        let name_changed_triggered = Arc::new(AtomicBool::new(false));
879        let name_changed_clone = name_changed_triggered.clone();
880        obj.connect("name-changed", false, move |args| {
881            let _obj = args[0].get::<Object>().expect("Failed to get args[0]");
882            let name = args[1].get::<&str>().expect("Failed to get args[1]");
883
884            assert_eq!(name, "new-name");
885            name_changed_clone.store(true, Ordering::Relaxed);
886
887            None
888        });
889
890        assert_eq!(obj.property::<String>("name"), String::from("old-name"));
891        assert!(!name_changed_triggered.load(Ordering::Relaxed));
892
893        assert_eq!(
894            obj.emit_by_name::<String>("change-name", &[&"new-name"]),
895            "old-name"
896        );
897        assert!(name_changed_triggered.load(Ordering::Relaxed));
898
899        assert_eq!(obj.emit_by_name::<String>("return-string", &[]), "base");
900    }
901
902    #[test]
903    fn test_signal_return_expected_type() {
904        let obj = Object::with_type(SimpleObject::static_type());
905
906        obj.connect("create-string", false, move |_args| {
907            Some("return value 1".to_value())
908        });
909
910        obj.connect("create-string", false, move |_args| {
911            Some("return value 2".to_value())
912        });
913
914        let signal_id = imp::SimpleObject::signals()[2].signal_id();
915
916        let value = obj.emit::<String>(signal_id, &[]);
917        assert_eq!(value, "return value 1\nreturn value 2");
918    }
919
920    #[test]
921    fn test_signal_override() {
922        let obj = Object::builder::<SimpleSubObject>().build();
923
924        assert_eq!(obj.emit_by_name::<String>("return-string", &[]), "sub");
925    }
926
927    #[test]
928    fn test_callback_validity() {
929        use std::sync::{
930            atomic::{AtomicBool, Ordering},
931            Arc,
932        };
933
934        let obj = Object::builder::<SimpleObject>()
935            .property("name", "old-name")
936            .build();
937
938        let name_changed_triggered = Arc::new(AtomicBool::new(false));
939        let name_changed_clone = name_changed_triggered.clone();
940
941        obj.connect_notify(Some("name"), move |_, _| {
942            name_changed_clone.store(true, Ordering::Relaxed);
943        });
944        obj.notify("name");
945        assert!(name_changed_triggered.load(Ordering::Relaxed));
946    }
947
948    // Note: can't test type mismatch in signals since panics across FFI boundaries
949    // are UB. See https://github.com/gtk-rs/glib/issues/518
950
951    #[test]
952    fn test_signal_return_expected_object_type() {
953        let obj = Object::with_type(SimpleObject::static_type());
954
955        obj.connect("create-child-object", false, move |_args| {
956            Some(Object::with_type(ChildObject::static_type()).to_value())
957        });
958        let value: glib::Object = obj.emit_by_name("create-child-object", &[]);
959        assert!(value.type_().is_a(ChildObject::static_type()));
960    }
961}