1use std::marker::PhantomData;
4
5use glib::{object::IsClass, prelude::*, Type};
6
7use crate::{value::GstValueExt, IdStr};
8
9impl crate::Object {
10 #[inline]
13 pub fn builder<'a, O>() -> GObjectBuilder<'a, O>
14 where
15 O: IsA<crate::Object> + IsClass,
16 {
17 assert_initialized_main_thread!();
18 GObjectBuilder {
19 type_: Some(O::static_type()),
20 properties: smallvec::SmallVec::new(),
21 phantom: PhantomData,
22 }
23 }
24
25 #[inline]
28 pub fn builder_for<'a, O>(type_: Type) -> GObjectBuilder<'a, O>
29 where
30 O: IsA<crate::Object> + IsClass,
31 {
32 assert_initialized_main_thread!();
33 GObjectBuilder {
34 type_: Some(type_),
35 properties: smallvec::SmallVec::new(),
36 phantom: PhantomData,
37 }
38 }
39
40 #[inline]
47 pub fn builder_for_deferred_type<'a, O>() -> GObjectBuilder<'a, O>
48 where
49 O: IsA<crate::Object> + IsClass,
50 {
51 assert_initialized_main_thread!();
52 GObjectBuilder {
53 type_: None,
54 properties: smallvec::SmallVec::new(),
55 phantom: PhantomData,
56 }
57 }
58}
59
60#[derive(Debug, Eq, PartialEq, thiserror::Error)]
61pub enum GObjectError {
62 #[error("property {property} for type {type_} not found")]
63 PropertyNotFound { type_: Type, property: IdStr },
64
65 #[error("property {property} for type {type_} can't be set from string {value}")]
66 PropertyFromStr {
67 type_: Type,
68 property: IdStr,
69 value: IdStr,
70 },
71}
72
73fn value_from_property_str(
74 pspec: glib::ParamSpec,
75 value: &str,
76) -> Result<glib::Value, GObjectError> {
77 skip_assert_initialized!(); if pspec.value_type() == crate::Structure::static_type() && value == "NULL" {
80 Ok(None::<crate::Structure>.to_value())
81 } else {
82 cfg_if::cfg_if! {
83 if #[cfg(feature = "v1_20")] {
84 let res = glib::Value::deserialize_with_pspec(value, &pspec);
85 } else {
86 let res = glib::Value::deserialize(value, pspec.value_type());
87 }
88 }
89 res.map_err(|_| GObjectError::PropertyFromStr {
90 type_: pspec.owner_type(),
91 property: pspec.name().into(),
92 value: value.into(),
93 })
94 }
95}
96
97pub trait GObjectExtManualGst: IsA<glib::Object> + 'static {
98 #[doc(alias = "gst_util_set_object_arg")]
99 #[track_caller]
100 fn set_property_from_str(&self, name: &str, value: &str) {
101 let pspec = self.find_property(name).unwrap_or_else(|| {
102 panic!("property '{}' of type '{}' not found", name, self.type_());
103 });
104
105 self.set_property(name, value_from_property_str(pspec, value).unwrap())
106 }
107}
108
109impl<O: IsA<glib::Object>> GObjectExtManualGst for O {}
110
111#[must_use = "The builder must be built to be used"]
114pub struct GObjectBuilder<'a, O> {
115 type_: Option<Type>,
116 properties: smallvec::SmallVec<[(&'a str, ValueOrStr<'a>); 16]>,
117 phantom: PhantomData<O>,
118}
119
120enum ValueOrStr<'a> {
121 Value(glib::Value),
122 Str(&'a str),
123}
124
125impl<'a, O: IsA<crate::Object> + IsClass> GObjectBuilder<'a, O> {
126 #[inline]
132 pub fn type_(mut self, type_: Type) -> Self {
133 self.type_ = Some(type_);
134 self
135 }
136
137 #[inline]
142 pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self {
143 Self {
144 properties: {
145 let mut properties = self.properties;
146 properties.push((name, ValueOrStr::Value(value.into())));
147 properties
148 },
149 ..self
150 }
151 }
152
153 #[inline]
156 pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self {
157 Self {
158 properties: {
159 let mut properties = self.properties;
160 properties.push((name, ValueOrStr::Str(value)));
161 properties
162 },
163 ..self
164 }
165 }
166
167 impl_builder_gvalue_extra_setters!(property_and_name);
168
169 #[track_caller]
185 #[must_use = "Building the element without using it has no effect"]
186 pub fn build(self) -> Result<O, GObjectError> {
187 let type_ = self.type_.expect("Deferred Type must be set");
188
189 let mut properties = smallvec::SmallVec::<[_; 16]>::with_capacity(self.properties.len());
190 let klass = glib::Class::<O>::from_type(type_).unwrap();
191 for (name, value) in self.properties {
192 let pspec =
193 klass
194 .find_property(name)
195 .ok_or_else(|| GObjectError::PropertyNotFound {
196 type_,
197 property: name.into(),
198 })?;
199
200 match value {
201 ValueOrStr::Value(value) => properties.push((name, value)),
202 ValueOrStr::Str(value) => {
203 properties.push((name, value_from_property_str(pspec, value)?));
204 }
205 }
206 }
207
208 let object =
209 unsafe { glib::Object::with_mut_values(type_, &mut properties).unsafe_cast::<O>() };
210
211 Ok(object)
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::{prelude::*, Bin, Element, ElementFactory, Object};
219
220 #[test]
221 fn test_set_property_from_str() {
222 crate::init().unwrap();
223
224 let fakesink = ElementFactory::make("fakesink").build().unwrap();
225 fakesink.set_property_from_str("state-error", "ready-to-paused");
226 let v = fakesink.property_value("state-error");
227 let (_klass, e) = glib::EnumValue::from_value(&v).unwrap();
228 assert_eq!(e.nick(), "ready-to-paused");
229 }
230
231 #[test]
232 fn builder() {
233 crate::init().unwrap();
234
235 let msg_fwd = "message-forward";
236 let bin = Object::builder::<Bin>()
237 .name("test-bin")
238 .property("async-handling", true)
239 .property_from_str(msg_fwd, "True")
240 .build()
241 .unwrap();
242
243 assert_eq!(bin.name(), "test-bin");
244 assert!(bin.property::<bool>("async-handling"));
245 assert!(bin.property::<bool>("message-forward"));
246 }
247
248 #[test]
249 fn builder_err() {
250 crate::init().unwrap();
251
252 assert_eq!(
253 Object::builder::<Bin>()
254 .property("not-a-prop", true)
255 .build(),
256 Err(GObjectError::PropertyNotFound {
257 type_: Bin::static_type(),
258 property: idstr!("not-a-prop")
259 })
260 );
261
262 assert_eq!(
263 Object::builder::<Bin>()
264 .property_from_str("async-handling", "not-a-bool")
265 .build(),
266 Err(GObjectError::PropertyFromStr {
267 type_: Bin::static_type(),
268 property: idstr!("async-handling"),
269 value: idstr!("not-a-bool")
270 })
271 );
272 }
273
274 #[test]
275 fn builder_for() {
276 crate::init().unwrap();
277
278 let fakesink = ElementFactory::make("fakesink").build().unwrap();
279
280 let fakesink = Object::builder_for::<Element>(fakesink.type_())
281 .name("test-fakesink")
282 .property("can-activate-pull", true)
283 .property_from_str("state-error", "ready-to-paused")
284 .build()
285 .unwrap();
286
287 assert_eq!(fakesink.name(), "test-fakesink");
288 assert!(fakesink.property::<bool>("can-activate-pull"));
289 let v = fakesink.property_value("state-error");
290 let (_klass, e) = glib::EnumValue::from_value(&v).unwrap();
291 assert_eq!(e.nick(), "ready-to-paused");
292 }
293}