glib/subclass/
object.rs

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