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 | |
7 | use std::{mem, ptr}; |
8 | |
9 | use super::{prelude::*, Signal}; |
10 | use crate::{prelude::*, translate::*, Cast, Object, ParamSpec, Slice, Value}; |
11 | |
12 | // rustdoc-stripper-ignore-next |
13 | /// Trait for implementors of `glib::Object` subclasses. |
14 | /// |
15 | /// This allows overriding the virtual methods of `glib::Object`. Except for |
16 | /// `finalize` as implementing `Drop` would allow the same behavior. |
17 | pub trait ObjectImpl: ObjectSubclass + ObjectImplExt { |
18 | // rustdoc-stripper-ignore-next |
19 | /// Properties installed for this type. |
20 | fn properties() -> &'static [ParamSpec] { |
21 | &[] |
22 | } |
23 | |
24 | // rustdoc-stripper-ignore-next |
25 | /// Signals installed for this type. |
26 | fn signals() -> &'static [Signal] { |
27 | &[] |
28 | } |
29 | |
30 | // rustdoc-stripper-ignore-next |
31 | /// Property setter. |
32 | /// |
33 | /// This is called whenever the property of this specific subclass with the |
34 | /// given index is set. The new value is passed as `glib::Value`. |
35 | /// |
36 | /// `value` is guaranteed to be of the correct type for the given property. |
37 | fn set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) { |
38 | unimplemented!() |
39 | } |
40 | |
41 | // rustdoc-stripper-ignore-next |
42 | /// Property getter. |
43 | /// |
44 | /// This is called whenever the property value of the specific subclass with the |
45 | /// given index should be returned. |
46 | /// |
47 | /// The returned `Value` must be of the correct type for the given property. |
48 | #[doc (alias = "get_property" )] |
49 | fn property(&self, _id: usize, _pspec: &ParamSpec) -> Value { |
50 | unimplemented!() |
51 | } |
52 | |
53 | // rustdoc-stripper-ignore-next |
54 | /// Constructed. |
55 | /// |
56 | /// This is called once construction of the instance is finished. |
57 | /// |
58 | /// Should chain up to the parent class' implementation. |
59 | fn constructed(&self) { |
60 | self.parent_constructed(); |
61 | } |
62 | |
63 | // rustdoc-stripper-ignore-next |
64 | /// Disposes of the object. |
65 | /// |
66 | /// When `dispose()` ends, the object should not hold any reference to any other member object. |
67 | /// The object is also expected to be able to answer client method invocations (with possibly an |
68 | /// error code but no memory violation) until it is dropped. `dispose()` can be executed more |
69 | /// than once. |
70 | fn dispose(&self) {} |
71 | |
72 | // rustdoc-stripper-ignore-next |
73 | /// Function to be called when property change is notified for with |
74 | /// `self.notify("property")`. |
75 | fn notify(&self, pspec: &ParamSpec) { |
76 | self.parent_notify(pspec) |
77 | } |
78 | |
79 | fn dispatch_properties_changed(&self, pspecs: &[ParamSpec]) { |
80 | self.parent_dispatch_properties_changed(pspecs) |
81 | } |
82 | } |
83 | |
84 | #[doc (alias = "get_property" )] |
85 | unsafe extern "C" fn property<T: ObjectImpl>( |
86 | obj: *mut gobject_ffi::GObject, |
87 | id: u32, |
88 | value: *mut gobject_ffi::GValue, |
89 | pspec: *mut gobject_ffi::GParamSpec, |
90 | ) { |
91 | let instance: &::Instance = &*(obj as *mut T::Instance); |
92 | let imp: &T = instance.imp(); |
93 | |
94 | let v: Value = imp.property(id as usize, &from_glib_borrow(ptr:pspec)); |
95 | |
96 | // We first unset the value we get passed in, in case it contained |
97 | // any previous data. Then we directly overwrite it with our new |
98 | // value, and pass ownership of the contained data to the C GValue |
99 | // by forgetting it on the Rust side. |
100 | // |
101 | // Without this, by using the GValue API, we would have to create |
102 | // a copy of the value when setting it on the destination just to |
103 | // immediately free the original value afterwards. |
104 | gobject_ffi::g_value_unset(value); |
105 | let v: ManuallyDrop = mem::ManuallyDrop::new(v); |
106 | ptr::write(dst:value, src:ptr::read(src:v.to_glib_none().0)); |
107 | } |
108 | |
109 | unsafe extern "C" fn set_property<T: ObjectImpl>( |
110 | obj: *mut gobject_ffi::GObject, |
111 | id: u32, |
112 | value: *mut gobject_ffi::GValue, |
113 | pspec: *mut gobject_ffi::GParamSpec, |
114 | ) { |
115 | let instance: &::Instance = &*(obj as *mut T::Instance); |
116 | let imp: &T = instance.imp(); |
117 | imp.set_property( |
118 | id as usize, |
119 | &*(value as *mut Value), |
120 | &from_glib_borrow(ptr:pspec), |
121 | ); |
122 | } |
123 | |
124 | unsafe extern "C" fn constructed<T: ObjectImpl>(obj: *mut gobject_ffi::GObject) { |
125 | let instance: &::Instance = &*(obj as *mut T::Instance); |
126 | let imp: &T = instance.imp(); |
127 | |
128 | imp.constructed(); |
129 | } |
130 | |
131 | unsafe extern "C" fn notify<T: ObjectImpl>( |
132 | obj: *mut gobject_ffi::GObject, |
133 | pspec: *mut gobject_ffi::GParamSpec, |
134 | ) { |
135 | let instance: &::Instance = &*(obj as *mut T::Instance); |
136 | let imp: &T = instance.imp(); |
137 | imp.notify(&from_glib_borrow(ptr:pspec)); |
138 | } |
139 | |
140 | unsafe extern "C" fn dispatch_properties_changed<T: ObjectImpl>( |
141 | obj: *mut gobject_ffi::GObject, |
142 | n_pspecs: u32, |
143 | pspecs: *mut *mut gobject_ffi::GParamSpec, |
144 | ) { |
145 | let instance: &::Instance = &*(obj as *mut T::Instance); |
146 | let imp: &T = instance.imp(); |
147 | imp.dispatch_properties_changed(pspecs:Slice::from_glib_borrow_num(ptr:pspecs, len:n_pspecs as _)); |
148 | } |
149 | |
150 | unsafe extern "C" fn dispose<T: ObjectImpl>(obj: *mut gobject_ffi::GObject) { |
151 | let instance: &::Instance = &*(obj as *mut T::Instance); |
152 | let imp: &T = instance.imp(); |
153 | |
154 | imp.dispose(); |
155 | |
156 | // Chain up to the parent's dispose. |
157 | let data: NonNull = T::type_data(); |
158 | let parent_class: *mut GObjectClass = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass; |
159 | if let Some(ref func: &unsafe fn(*mut GObject)) = (*parent_class).dispose { |
160 | func(obj); |
161 | } |
162 | } |
163 | |
164 | // rustdoc-stripper-ignore-next |
165 | /// Trait containing only the property related functions of `ObjectImpl`. |
166 | /// Implemented by the `Props` macro. |
167 | /// When implementing `ObjectImpl` you may want to delegate the function calls to this trait. |
168 | pub trait DerivedObjectProperties: ObjectSubclass { |
169 | // rustdoc-stripper-ignore-next |
170 | /// Properties installed for this type. |
171 | fn derived_properties() -> &'static [ParamSpec] { |
172 | &[] |
173 | } |
174 | |
175 | // rustdoc-stripper-ignore-next |
176 | /// Similar to [`ObjectImpl`](trait.ObjectImpl.html) but auto-generated by the [`Properties`] macro |
177 | /// to allow handling more complex use-cases. |
178 | fn derived_set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) { |
179 | unimplemented!() |
180 | } |
181 | |
182 | // rustdoc-stripper-ignore-next |
183 | /// Similar to [`ObjectImpl`](trait.ObjectImpl.html) but auto-generated by the [`Properties`] macro |
184 | /// to allow handling more complex use-cases. |
185 | fn derived_property(&self, _id: usize, _pspec: &ParamSpec) -> Value { |
186 | unimplemented!() |
187 | } |
188 | } |
189 | |
190 | // rustdoc-stripper-ignore-next |
191 | /// Extension trait for `glib::Object`'s class struct. |
192 | /// |
193 | /// This contains various class methods and allows subclasses to override signal class handlers. |
194 | pub unsafe trait ObjectClassSubclassExt: Sized + 'static { |
195 | fn override_signal_class_handler<F>(&mut self, name: &str, class_handler: F) |
196 | where |
197 | F: Fn(&super::SignalClassHandlerToken, &[Value]) -> Option<Value> + Send + Sync + 'static, |
198 | { |
199 | unsafe { |
200 | super::types::signal_override_class_handler( |
201 | name, |
202 | *(self as *mut _ as *mut ffi::GType), |
203 | class_handler, |
204 | ); |
205 | } |
206 | } |
207 | } |
208 | |
209 | unsafe impl ObjectClassSubclassExt for crate::Class<Object> {} |
210 | |
211 | unsafe impl<T: ObjectImpl> IsSubclassable<T> for Object { |
212 | fn class_init(class: &mut crate::Class<Self>) { |
213 | let klass = class.as_mut(); |
214 | klass.set_property = Some(set_property::<T>); |
215 | klass.get_property = Some(property::<T>); |
216 | klass.constructed = Some(constructed::<T>); |
217 | klass.notify = Some(notify::<T>); |
218 | klass.dispatch_properties_changed = Some(dispatch_properties_changed::<T>); |
219 | klass.dispose = Some(dispose::<T>); |
220 | |
221 | let pspecs = <T as ObjectImpl>::properties(); |
222 | if !pspecs.is_empty() { |
223 | unsafe { |
224 | let mut pspecs_ptrs = Vec::with_capacity(pspecs.len() + 1); |
225 | |
226 | pspecs_ptrs.push(ptr::null_mut()); |
227 | |
228 | for pspec in pspecs { |
229 | pspecs_ptrs.push(pspec.to_glib_none().0); |
230 | } |
231 | |
232 | gobject_ffi::g_object_class_install_properties( |
233 | klass, |
234 | pspecs_ptrs.len() as u32, |
235 | pspecs_ptrs.as_mut_ptr(), |
236 | ); |
237 | } |
238 | } |
239 | |
240 | let type_ = T::type_(); |
241 | let signals = <T as ObjectImpl>::signals(); |
242 | for signal in signals { |
243 | signal.register(type_); |
244 | } |
245 | } |
246 | |
247 | #[inline ] |
248 | fn instance_init(_instance: &mut super::InitializingObject<T>) {} |
249 | } |
250 | |
251 | mod sealed { |
252 | pub trait Sealed {} |
253 | impl<T: super::ObjectImplExt> Sealed for T {} |
254 | } |
255 | |
256 | pub trait ObjectImplExt: sealed::Sealed + ObjectSubclass { |
257 | // rustdoc-stripper-ignore-next |
258 | /// Chain up to the parent class' implementation of `glib::Object::constructed()`. |
259 | #[inline ] |
260 | fn parent_constructed(&self) { |
261 | unsafe { |
262 | let data = Self::type_data(); |
263 | let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass; |
264 | |
265 | if let Some(ref func) = (*parent_class).constructed { |
266 | func(self.obj().unsafe_cast_ref::<Object>().to_glib_none().0); |
267 | } |
268 | } |
269 | } |
270 | |
271 | // rustdoc-stripper-ignore-next |
272 | /// Chain up to the parent class' implementation of `glib::Object::notify()`. |
273 | #[inline ] |
274 | fn parent_notify(&self, pspec: &ParamSpec) { |
275 | unsafe { |
276 | let data = Self::type_data(); |
277 | let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass; |
278 | |
279 | if let Some(ref func) = (*parent_class).notify { |
280 | func( |
281 | self.obj().unsafe_cast_ref::<Object>().to_glib_none().0, |
282 | pspec.to_glib_none().0, |
283 | ); |
284 | } |
285 | } |
286 | } |
287 | |
288 | // rustdoc-stripper-ignore-next |
289 | /// Chain up to the parent class' implementation of `glib::Object::dispatch_properties_changed()`. |
290 | #[inline ] |
291 | fn parent_dispatch_properties_changed(&self, pspecs: &[ParamSpec]) { |
292 | unsafe { |
293 | let data = Self::type_data(); |
294 | let parent_class = data.as_ref().parent_class() as *mut gobject_ffi::GObjectClass; |
295 | |
296 | if let Some(ref func) = (*parent_class).dispatch_properties_changed { |
297 | func( |
298 | self.obj().unsafe_cast_ref::<Object>().to_glib_none().0, |
299 | pspecs.len() as _, |
300 | pspecs.as_ptr() as *mut _, |
301 | ); |
302 | } |
303 | } |
304 | } |
305 | |
306 | // rustdoc-stripper-ignore-next |
307 | /// Chain up to parent class signal handler. |
308 | fn signal_chain_from_overridden( |
309 | &self, |
310 | token: &super::SignalClassHandlerToken, |
311 | values: &[Value], |
312 | ) -> Option<Value> { |
313 | unsafe { |
314 | super::types::signal_chain_from_overridden(self.obj().as_ptr() as *mut _, token, values) |
315 | } |
316 | } |
317 | } |
318 | |
319 | impl<T: ObjectImpl> ObjectImplExt for T {} |
320 | |
321 | #[cfg (test)] |
322 | mod test { |
323 | use std::cell::RefCell; |
324 | |
325 | use super::*; |
326 | // We rename the current crate as glib, since the macros in glib-macros |
327 | // generate the glib namespace through the crate_ident_new utility, |
328 | // and that returns `glib` (and not `crate`) when called inside the glib crate |
329 | use crate as glib; |
330 | |
331 | mod imp { |
332 | use super::*; |
333 | |
334 | // A dummy `Object` to test setting an `Object` property and returning an `Object` in signals |
335 | #[derive (Default)] |
336 | pub struct ChildObject; |
337 | |
338 | #[glib::object_subclass] |
339 | impl ObjectSubclass for ChildObject { |
340 | const NAME: &'static str = "ChildObject" ; |
341 | type Type = super::ChildObject; |
342 | } |
343 | |
344 | impl ObjectImpl for ChildObject {} |
345 | |
346 | #[derive (Default)] |
347 | pub struct SimpleObject { |
348 | name: RefCell<Option<String>>, |
349 | construct_name: RefCell<Option<String>>, |
350 | constructed: RefCell<bool>, |
351 | } |
352 | |
353 | #[glib::object_subclass] |
354 | impl ObjectSubclass for SimpleObject { |
355 | const NAME: &'static str = "SimpleObject" ; |
356 | type Type = super::SimpleObject; |
357 | type Interfaces = (super::Dummy,); |
358 | } |
359 | |
360 | impl ObjectImpl for SimpleObject { |
361 | fn properties() -> &'static [ParamSpec] { |
362 | use once_cell::sync::Lazy; |
363 | static PROPERTIES: Lazy<Vec<ParamSpec>> = Lazy::new(|| { |
364 | vec![ |
365 | crate::ParamSpecString::builder("name" ).build(), |
366 | crate::ParamSpecString::builder("construct-name" ) |
367 | .construct_only() |
368 | .build(), |
369 | crate::ParamSpecBoolean::builder("constructed" ) |
370 | .read_only() |
371 | .build(), |
372 | crate::ParamSpecObject::builder::<super::ChildObject>("child" ).build(), |
373 | ] |
374 | }); |
375 | |
376 | PROPERTIES.as_ref() |
377 | } |
378 | |
379 | fn signals() -> &'static [super::Signal] { |
380 | use once_cell::sync::Lazy; |
381 | static SIGNALS: Lazy<Vec<super::Signal>> = Lazy::new(|| { |
382 | vec![ |
383 | super::Signal::builder("name-changed" ) |
384 | .param_types([String::static_type()]) |
385 | .build(), |
386 | super::Signal::builder("change-name" ) |
387 | .param_types([String::static_type()]) |
388 | .return_type::<String>() |
389 | .action() |
390 | .class_handler(|_, args| { |
391 | let obj = args[0] |
392 | .get::<super::SimpleObject>() |
393 | .expect("Failed to get Object from args[0]" ); |
394 | let new_name = args[1] |
395 | .get::<String>() |
396 | .expect("Failed to get Object from args[1]" ); |
397 | let imp = obj.imp(); |
398 | |
399 | let old_name = imp.name.replace(Some(new_name)); |
400 | |
401 | obj.emit_by_name::<()>("name-changed" , &[&*imp.name.borrow()]); |
402 | |
403 | Some(old_name.to_value()) |
404 | }) |
405 | .build(), |
406 | super::Signal::builder("create-string" ) |
407 | .return_type::<String>() |
408 | .build(), |
409 | super::Signal::builder("create-child-object" ) |
410 | .return_type::<super::ChildObject>() |
411 | .build(), |
412 | ] |
413 | }); |
414 | |
415 | SIGNALS.as_ref() |
416 | } |
417 | |
418 | fn set_property(&self, _id: usize, value: &Value, pspec: &crate::ParamSpec) { |
419 | match pspec.name() { |
420 | "name" => { |
421 | let name = value |
422 | .get() |
423 | .expect("type conformity checked by 'Object::set_property'" ); |
424 | self.name.replace(name); |
425 | self.obj() |
426 | .emit_by_name::<()>("name-changed" , &[&*self.name.borrow()]); |
427 | } |
428 | "construct-name" => { |
429 | let name = value |
430 | .get() |
431 | .expect("type conformity checked by 'Object::set_property'" ); |
432 | self.construct_name.replace(name); |
433 | } |
434 | "child" => { |
435 | // not stored, only used to test `set_property` with `Objects` |
436 | } |
437 | _ => unimplemented!(), |
438 | } |
439 | } |
440 | |
441 | fn property(&self, _id: usize, pspec: &crate::ParamSpec) -> Value { |
442 | match pspec.name() { |
443 | "name" => self.name.borrow().to_value(), |
444 | "construct-name" => self.construct_name.borrow().to_value(), |
445 | "constructed" => self.constructed.borrow().to_value(), |
446 | _ => unimplemented!(), |
447 | } |
448 | } |
449 | |
450 | fn constructed(&self) { |
451 | self.parent_constructed(); |
452 | |
453 | debug_assert_eq!(self as *const _, self.obj().imp() as *const _); |
454 | |
455 | *self.constructed.borrow_mut() = true; |
456 | } |
457 | } |
458 | |
459 | #[derive (Clone, Copy)] |
460 | #[repr (C)] |
461 | pub struct DummyInterface { |
462 | parent: gobject_ffi::GTypeInterface, |
463 | } |
464 | |
465 | #[glib::object_interface] |
466 | unsafe impl ObjectInterface for DummyInterface { |
467 | const NAME: &'static str = "Dummy" ; |
468 | } |
469 | } |
470 | |
471 | wrapper! { |
472 | pub struct ChildObject(ObjectSubclass<imp::ChildObject>); |
473 | } |
474 | |
475 | wrapper! { |
476 | pub struct SimpleObject(ObjectSubclass<imp::SimpleObject>); |
477 | } |
478 | |
479 | wrapper! { |
480 | pub struct Dummy(ObjectInterface<imp::DummyInterface>); |
481 | } |
482 | |
483 | unsafe impl<T: ObjectSubclass> IsImplementable<T> for Dummy {} |
484 | |
485 | #[test ] |
486 | fn test_create() { |
487 | let type_ = SimpleObject::static_type(); |
488 | let obj = Object::with_type(type_); |
489 | |
490 | assert!(obj.type_().is_a(Dummy::static_type())); |
491 | |
492 | // Assert that the object representation is equivalent to the underlying C GObject pointer |
493 | assert_eq!( |
494 | mem::size_of::<SimpleObject>(), |
495 | mem::size_of::<ffi::gpointer>() |
496 | ); |
497 | assert_eq!(obj.as_ptr() as ffi::gpointer, unsafe { |
498 | *(&obj as *const _ as *const ffi::gpointer) |
499 | }); |
500 | |
501 | assert!(obj.property::<bool>("constructed" )); |
502 | |
503 | let weak = obj.downgrade(); |
504 | drop(obj); |
505 | assert!(weak.upgrade().is_none()); |
506 | } |
507 | |
508 | #[test ] |
509 | fn test_properties() { |
510 | let type_ = SimpleObject::static_type(); |
511 | let obj = Object::with_type(type_); |
512 | |
513 | assert!(obj.type_().is_a(Dummy::static_type())); |
514 | |
515 | let properties = obj.list_properties(); |
516 | assert_eq!(properties.len(), 4); |
517 | assert_eq!(properties[0].name(), "name" ); |
518 | assert_eq!(properties[1].name(), "construct-name" ); |
519 | assert_eq!(properties[2].name(), "constructed" ); |
520 | assert_eq!(properties[3].name(), "child" ); |
521 | } |
522 | |
523 | #[test ] |
524 | fn test_create_child_object() { |
525 | let obj: ChildObject = Object::new(); |
526 | |
527 | assert_eq!(&obj, obj.imp().obj().as_ref()); |
528 | } |
529 | |
530 | #[test ] |
531 | fn test_builder() { |
532 | let obj = Object::builder::<SimpleObject>() |
533 | .property("construct-name" , "meh" ) |
534 | .property("name" , "initial" ) |
535 | .build(); |
536 | |
537 | assert_eq!( |
538 | obj.property::<String>("construct-name" ), |
539 | String::from("meh" ) |
540 | ); |
541 | |
542 | assert_eq!(obj.property::<String>("name" ), String::from("initial" )); |
543 | } |
544 | |
545 | #[test ] |
546 | fn test_set_property() { |
547 | let obj = Object::builder::<SimpleObject>() |
548 | .property("construct-name" , "meh" ) |
549 | .property("name" , "initial" ) |
550 | .build(); |
551 | |
552 | assert_eq!( |
553 | obj.property::<String>("construct-name" ), |
554 | String::from("meh" ) |
555 | ); |
556 | |
557 | assert_eq!( |
558 | obj.property::<String>("construct-name" ), |
559 | String::from("meh" ) |
560 | ); |
561 | |
562 | assert_eq!(obj.property::<String>("name" ), String::from("initial" )); |
563 | obj.set_property("name" , "test" ); |
564 | assert_eq!(obj.property::<String>("name" ), String::from("test" )); |
565 | |
566 | let child = Object::with_type(ChildObject::static_type()); |
567 | obj.set_property("child" , &child); |
568 | } |
569 | |
570 | #[test ] |
571 | #[should_panic = "property 'construct-name' of type 'SimpleObject' is not writable" ] |
572 | fn test_set_property_non_writable() { |
573 | let obj = Object::builder::<SimpleObject>() |
574 | .property("construct-name" , "meh" ) |
575 | .property("name" , "initial" ) |
576 | .build(); |
577 | |
578 | obj.set_property("construct-name" , "test" ); |
579 | } |
580 | |
581 | #[test ] |
582 | #[should_panic = "property 'test' of type 'SimpleObject' not found" ] |
583 | fn test_set_property_not_found() { |
584 | let obj = Object::builder::<SimpleObject>() |
585 | .property("construct-name" , "meh" ) |
586 | .property("name" , "initial" ) |
587 | .build(); |
588 | |
589 | obj.set_property("test" , true); |
590 | } |
591 | |
592 | #[test ] |
593 | #[should_panic = "property 'constructed' of type 'SimpleObject' is not writable" ] |
594 | fn test_set_property_not_writable() { |
595 | let obj = Object::builder::<SimpleObject>() |
596 | .property("construct-name" , "meh" ) |
597 | .property("name" , "initial" ) |
598 | .build(); |
599 | |
600 | obj.set_property("constructed" , false); |
601 | } |
602 | |
603 | #[test ] |
604 | #[should_panic = "property 'name' of type 'SimpleObject' can't be set from the given type (expected: 'gchararray', got: 'gboolean')" ] |
605 | fn test_set_property_wrong_type() { |
606 | let obj = Object::builder::<SimpleObject>() |
607 | .property("construct-name" , "meh" ) |
608 | .property("name" , "initial" ) |
609 | .build(); |
610 | |
611 | obj.set_property("name" , false); |
612 | } |
613 | |
614 | #[test ] |
615 | #[should_panic = "property 'child' of type 'SimpleObject' can't be set from the given type (expected: 'ChildObject', got: 'SimpleObject')" ] |
616 | fn test_set_property_wrong_type_2() { |
617 | let obj = Object::builder::<SimpleObject>() |
618 | .property("construct-name" , "meh" ) |
619 | .property("name" , "initial" ) |
620 | .build(); |
621 | |
622 | let other_obj = Object::with_type(SimpleObject::static_type()); |
623 | |
624 | obj.set_property("child" , &other_obj); |
625 | } |
626 | |
627 | #[test ] |
628 | #[should_panic = "Can't set construct property 'construct-name' for type 'SimpleObject' twice" ] |
629 | fn test_construct_property_set_twice() { |
630 | let _obj = Object::builder::<SimpleObject>() |
631 | .property("construct-name" , "meh" ) |
632 | .property("construct-name" , "meh2" ) |
633 | .build(); |
634 | } |
635 | |
636 | #[test ] |
637 | fn test_signals() { |
638 | use std::sync::{ |
639 | atomic::{AtomicBool, Ordering}, |
640 | Arc, |
641 | }; |
642 | |
643 | let obj = Object::builder::<SimpleObject>() |
644 | .property("name" , "old-name" ) |
645 | .build(); |
646 | |
647 | let name_changed_triggered = Arc::new(AtomicBool::new(false)); |
648 | let name_changed_clone = name_changed_triggered.clone(); |
649 | obj.connect("name-changed" , false, move |args| { |
650 | let _obj = args[0].get::<Object>().expect("Failed to get args[0]" ); |
651 | let name = args[1].get::<&str>().expect("Failed to get args[1]" ); |
652 | |
653 | assert_eq!(name, "new-name" ); |
654 | name_changed_clone.store(true, Ordering::Relaxed); |
655 | |
656 | None |
657 | }); |
658 | |
659 | assert_eq!(obj.property::<String>("name" ), String::from("old-name" )); |
660 | assert!(!name_changed_triggered.load(Ordering::Relaxed)); |
661 | |
662 | assert_eq!( |
663 | obj.emit_by_name::<String>("change-name" , &[&"new-name" ]), |
664 | "old-name" |
665 | ); |
666 | assert!(name_changed_triggered.load(Ordering::Relaxed)); |
667 | } |
668 | |
669 | #[test ] |
670 | fn test_signal_return_expected_type() { |
671 | let obj = Object::with_type(SimpleObject::static_type()); |
672 | |
673 | obj.connect("create-string" , false, move |_args| { |
674 | Some("return value" .to_value()) |
675 | }); |
676 | |
677 | let signal_id = imp::SimpleObject::signals()[2].signal_id(); |
678 | |
679 | let value = obj.emit::<String>(signal_id, &[]); |
680 | assert_eq!(value, "return value" ); |
681 | } |
682 | |
683 | #[test ] |
684 | fn test_callback_validity() { |
685 | use std::sync::{ |
686 | atomic::{AtomicBool, Ordering}, |
687 | Arc, |
688 | }; |
689 | |
690 | let obj = Object::builder::<SimpleObject>() |
691 | .property("name" , "old-name" ) |
692 | .build(); |
693 | |
694 | let name_changed_triggered = Arc::new(AtomicBool::new(false)); |
695 | let name_changed_clone = name_changed_triggered.clone(); |
696 | |
697 | obj.connect_notify(Some("name" ), move |_, _| { |
698 | name_changed_clone.store(true, Ordering::Relaxed); |
699 | }); |
700 | obj.notify("name" ); |
701 | assert!(name_changed_triggered.load(Ordering::Relaxed)); |
702 | } |
703 | |
704 | // Note: can't test type mismatch in signals since panics accross FFI boundaries |
705 | // are UB. See https://github.com/gtk-rs/glib/issues/518 |
706 | |
707 | #[test ] |
708 | fn test_signal_return_expected_object_type() { |
709 | let obj = Object::with_type(SimpleObject::static_type()); |
710 | |
711 | obj.connect("create-child-object" , false, move |_args| { |
712 | Some(Object::with_type(ChildObject::static_type()).to_value()) |
713 | }); |
714 | let value: glib::Object = obj.emit_by_name("create-child-object" , &[]); |
715 | assert!(value.type_().is_a(ChildObject::static_type())); |
716 | } |
717 | } |
718 | |