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