1use glib::{prelude::*, translate::*, BoolError, StrV, Variant};
4
5use crate::{ffi, prelude::*, Settings, SettingsBindFlags};
6
7#[must_use = "The builder must be built to be used"]
8pub struct BindingBuilder<'a> {
9 settings: &'a Settings,
10 key: &'a str,
11 object: &'a glib::Object,
12 property: &'a str,
13 flags: SettingsBindFlags,
14 #[allow(clippy::type_complexity)]
15 get_mapping: Option<Box<dyn Fn(&glib::Variant, glib::Type) -> Option<glib::Value>>>,
16 #[allow(clippy::type_complexity)]
17 set_mapping: Option<Box<dyn Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant>>>,
18}
19
20impl BindingBuilder<'_> {
21 pub fn flags(mut self, flags: SettingsBindFlags) -> Self {
22 self.flags = flags;
23 self
24 }
25
26 pub fn get(mut self) -> Self {
29 self.flags |= SettingsBindFlags::GET;
30 self
31 }
32
33 pub fn set(mut self) -> Self {
36 self.flags |= SettingsBindFlags::SET;
37 self
38 }
39
40 pub fn set_only(mut self) -> Self {
43 self.flags = (self.flags - SettingsBindFlags::GET) | SettingsBindFlags::SET;
44 self
45 }
46
47 pub fn get_only(mut self) -> Self {
50 self.flags = (self.flags - SettingsBindFlags::SET) | SettingsBindFlags::GET;
51 self
52 }
53
54 pub fn no_sensitivity(mut self) -> Self {
57 self.flags |= SettingsBindFlags::NO_SENSITIVITY;
58 self
59 }
60
61 pub fn get_no_changes(mut self) -> Self {
64 self.flags |= SettingsBindFlags::GET_NO_CHANGES;
65 self
66 }
67
68 pub fn invert_boolean(mut self) -> Self {
71 self.flags |= SettingsBindFlags::INVERT_BOOLEAN;
72 self
73 }
74
75 #[doc(alias = "get_mapping")]
76 pub fn mapping<F: Fn(&glib::Variant, glib::Type) -> Option<glib::Value> + 'static>(
77 mut self,
78 f: F,
79 ) -> Self {
80 self.get_mapping = Some(Box::new(f));
81 self
82 }
83
84 pub fn set_mapping<
85 F: Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant> + 'static,
86 >(
87 mut self,
88 f: F,
89 ) -> Self {
90 self.set_mapping = Some(Box::new(f));
91 self
92 }
93
94 pub fn build(self) {
95 type Mappings = (
96 Option<Box<dyn Fn(&glib::Variant, glib::Type) -> Option<glib::Value>>>,
97 Option<Box<dyn Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant>>>,
98 );
99 unsafe extern "C" fn bind_with_mapping_get_trampoline(
100 value: *mut glib::gobject_ffi::GValue,
101 variant: *mut glib::ffi::GVariant,
102 user_data: glib::ffi::gpointer,
103 ) -> glib::ffi::gboolean {
104 let user_data = &*(user_data as *const Mappings);
105 let f = user_data.0.as_ref().unwrap();
106 let value = &mut *(value as *mut glib::Value);
107 if let Some(v) = f(&from_glib_borrow(variant), value.type_()) {
108 *value = v;
109 true
110 } else {
111 false
112 }
113 .into_glib()
114 }
115 unsafe extern "C" fn bind_with_mapping_set_trampoline(
116 value: *const glib::gobject_ffi::GValue,
117 variant_type: *const glib::ffi::GVariantType,
118 user_data: glib::ffi::gpointer,
119 ) -> *mut glib::ffi::GVariant {
120 let user_data = &*(user_data as *const Mappings);
121 let f = user_data.1.as_ref().unwrap();
122 let value = &*(value as *const glib::Value);
123 f(value, from_glib_none(variant_type)).into_glib_ptr()
124 }
125 unsafe extern "C" fn destroy_closure(ptr: *mut libc::c_void) {
126 let _ = Box::<Mappings>::from_raw(ptr as *mut _);
127 }
128
129 if self.get_mapping.is_none() && self.set_mapping.is_none() {
130 unsafe {
131 ffi::g_settings_bind(
132 self.settings.to_glib_none().0,
133 self.key.to_glib_none().0,
134 self.object.to_glib_none().0,
135 self.property.to_glib_none().0,
136 self.flags.into_glib(),
137 );
138 }
139 } else {
140 let get_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> =
141 if self.get_mapping.is_none() {
142 None
143 } else {
144 Some(bind_with_mapping_get_trampoline)
145 };
146 let set_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> =
147 if self.set_mapping.is_none() {
148 None
149 } else {
150 Some(bind_with_mapping_set_trampoline)
151 };
152 let mappings: Mappings = (self.get_mapping, self.set_mapping);
153 unsafe {
154 ffi::g_settings_bind_with_mapping(
155 self.settings.to_glib_none().0,
156 self.key.to_glib_none().0,
157 self.object.to_glib_none().0,
158 self.property.to_glib_none().0,
159 self.flags.into_glib(),
160 get_trampoline,
161 set_trampoline,
162 Box::into_raw(Box::new(mappings)) as *mut libc::c_void,
163 Some(destroy_closure),
164 )
165 }
166 }
167 }
168}
169
170mod sealed {
171 pub trait Sealed {}
172 impl<T: super::IsA<super::Settings>> Sealed for T {}
173}
174
175pub trait SettingsExtManual: sealed::Sealed + IsA<Settings> {
176 fn get<U: FromVariant>(&self, key: &str) -> U {
177 let val = self.value(key);
178 FromVariant::from_variant(&val).unwrap_or_else(|| {
179 panic!(
180 "Type mismatch: Expected '{}' got '{}'",
181 U::static_variant_type().as_str(),
182 val.type_()
183 )
184 })
185 }
186
187 fn set(&self, key: &str, value: impl Into<Variant>) -> Result<(), BoolError> {
188 self.set_value(key, &value.into())
189 }
190
191 #[doc(alias = "g_settings_get_strv")]
204 #[doc(alias = "get_strv")]
205 fn strv(&self, key: &str) -> StrV {
206 unsafe {
207 FromGlibPtrContainer::from_glib_full(ffi::g_settings_get_strv(
208 self.as_ref().to_glib_none().0,
209 key.to_glib_none().0,
210 ))
211 }
212 }
213
214 #[doc(alias = "g_settings_set_strv")]
231 fn set_strv(&self, key: &str, value: impl IntoStrV) -> Result<(), glib::error::BoolError> {
232 unsafe {
233 value.run_with_strv(|value| {
234 glib::result_from_gboolean!(
235 ffi::g_settings_set_strv(
236 self.as_ref().to_glib_none().0,
237 key.to_glib_none().0,
238 value.as_ptr() as *mut _,
239 ),
240 "Can't set readonly key"
241 )
242 })
243 }
244 }
245
246 #[doc(alias = "g_settings_bind")]
275 #[doc(alias = "g_settings_bind_with_mapping")]
276 fn bind<'a, P: IsA<glib::Object>>(
277 &'a self,
278 key: &'a str,
279 object: &'a P,
280 property: &'a str,
281 ) -> BindingBuilder<'a> {
282 BindingBuilder {
283 settings: self.upcast_ref(),
284 key,
285 object: object.upcast_ref(),
286 property,
287 flags: SettingsBindFlags::DEFAULT,
288 get_mapping: None,
289 set_mapping: None,
290 }
291 }
292}
293
294impl<O: IsA<Settings>> SettingsExtManual for O {}
295
296#[cfg(test)]
297mod test {
298 use std::{env::set_var, process::Command, str::from_utf8, sync::Once};
299
300 use super::*;
301
302 static INIT: Once = Once::new();
303
304 fn set_env() {
305 INIT.call_once(|| {
306 let output = Command::new("glib-compile-schemas")
307 .args([
308 &format!("{}/tests", env!("CARGO_MANIFEST_DIR")),
309 "--targetdir",
310 env!("OUT_DIR"),
311 ])
312 .output()
313 .unwrap();
314
315 if !output.status.success() {
316 println!("Failed to generate GSchema!");
317 println!(
318 "glib-compile-schemas stdout: {}",
319 from_utf8(&output.stdout).unwrap()
320 );
321 println!(
322 "glib-compile-schemas stderr: {}",
323 from_utf8(&output.stderr).unwrap()
324 );
325 panic!("Can't test without GSchemas!");
326 }
327
328 set_var("GSETTINGS_SCHEMA_DIR", env!("OUT_DIR"));
329 set_var("GSETTINGS_BACKEND", "memory");
330 });
331 }
332
333 #[test]
334 #[serial_test::serial]
335 fn string_get() {
336 set_env();
337 let settings = Settings::new("com.github.gtk-rs.test");
338 assert_eq!(settings.get::<String>("test-string").as_str(), "Good");
339 }
340
341 #[test]
342 #[serial_test::serial]
343 fn bool_set_get() {
344 set_env();
345 let settings = Settings::new("com.github.gtk-rs.test");
346 settings.set("test-bool", false).unwrap();
347 assert!(!settings.get::<bool>("test-bool"));
348 }
349
350 #[test]
351 #[should_panic]
352 #[serial_test::serial]
353 fn wrong_type() {
354 set_env();
355 let settings = Settings::new("com.github.gtk-rs.test");
356 settings.get::<u8>("test-string");
357 }
358}