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