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