1use glib::{BoolError, StrV, Variant, prelude::*, translate::*};
4
5use crate::{Settings, SettingsBindFlags, ffi, prelude::*};
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 unsafe {
105 let user_data = &*(user_data as *const Mappings);
106 let f = user_data.0.as_ref().unwrap();
107 let value = &mut *(value as *mut glib::Value);
108 match f(&from_glib_borrow(variant), value.type_()) {
109 Some(v) => {
110 *value = v;
111 true
112 }
113 _ => false,
114 }
115 .into_glib()
116 }
117 }
118 unsafe extern "C" fn bind_with_mapping_set_trampoline(
119 value: *const glib::gobject_ffi::GValue,
120 variant_type: *const glib::ffi::GVariantType,
121 user_data: glib::ffi::gpointer,
122 ) -> *mut glib::ffi::GVariant {
123 unsafe {
124 let user_data = &*(user_data as *const Mappings);
125 let f = user_data.1.as_ref().unwrap();
126 let value = &*(value as *const glib::Value);
127 f(value, from_glib_none(variant_type)).into_glib_ptr()
128 }
129 }
130 unsafe extern "C" fn destroy_closure(ptr: *mut libc::c_void) {
131 unsafe {
132 let _ = Box::<Mappings>::from_raw(ptr as *mut _);
133 }
134 }
135
136 if self.get_mapping.is_none() && self.set_mapping.is_none() {
137 unsafe {
138 ffi::g_settings_bind(
139 self.settings.to_glib_none().0,
140 self.key.to_glib_none().0,
141 self.object.to_glib_none().0,
142 self.property.to_glib_none().0,
143 self.flags.into_glib(),
144 );
145 }
146 } else {
147 let get_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> =
148 if self.get_mapping.is_none() {
149 None
150 } else {
151 Some(bind_with_mapping_get_trampoline)
152 };
153 let set_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> =
154 if self.set_mapping.is_none() {
155 None
156 } else {
157 Some(bind_with_mapping_set_trampoline)
158 };
159 let mappings: Mappings = (self.get_mapping, self.set_mapping);
160 unsafe {
161 ffi::g_settings_bind_with_mapping(
162 self.settings.to_glib_none().0,
163 self.key.to_glib_none().0,
164 self.object.to_glib_none().0,
165 self.property.to_glib_none().0,
166 self.flags.into_glib(),
167 get_trampoline,
168 set_trampoline,
169 Box::into_raw(Box::new(mappings)) as *mut libc::c_void,
170 Some(destroy_closure),
171 )
172 }
173 }
174 }
175}
176
177pub trait SettingsExtManual: IsA<Settings> {
178 fn get<U: FromVariant>(&self, key: &str) -> U {
179 let val = self.value(key);
180 FromVariant::from_variant(&val).unwrap_or_else(|| {
181 panic!(
182 "Type mismatch: Expected '{}' got '{}'",
183 U::static_variant_type().as_str(),
184 val.type_()
185 )
186 })
187 }
188
189 fn set(&self, key: &str, value: impl Into<Variant>) -> Result<(), BoolError> {
190 self.set_value(key, &value.into())
191 }
192
193 #[doc(alias = "g_settings_get_strv")]
206 #[doc(alias = "get_strv")]
207 fn strv(&self, key: &str) -> StrV {
208 unsafe {
209 FromGlibPtrContainer::from_glib_full(ffi::g_settings_get_strv(
210 self.as_ref().to_glib_none().0,
211 key.to_glib_none().0,
212 ))
213 }
214 }
215
216 #[doc(alias = "g_settings_set_strv")]
233 fn set_strv(&self, key: &str, value: impl IntoStrV) -> Result<(), glib::error::BoolError> {
234 unsafe {
235 value.run_with_strv(|value| {
236 glib::result_from_gboolean!(
237 ffi::g_settings_set_strv(
238 self.as_ref().to_glib_none().0,
239 key.to_glib_none().0,
240 value.as_ptr() as *mut _,
241 ),
242 "Can't set readonly key"
243 )
244 })
245 }
246 }
247
248 #[doc(alias = "g_settings_bind")]
277 #[doc(alias = "g_settings_bind_with_mapping")]
278 fn bind<'a, P: IsA<glib::Object>>(
279 &'a self,
280 key: &'a str,
281 object: &'a P,
282 property: &'a str,
283 ) -> BindingBuilder<'a> {
284 BindingBuilder {
285 settings: self.upcast_ref(),
286 key,
287 object: object.upcast_ref(),
288 property,
289 flags: SettingsBindFlags::DEFAULT,
290 get_mapping: None,
291 set_mapping: None,
292 }
293 }
294}
295
296impl<O: IsA<Settings>> SettingsExtManual for O {}
297
298#[cfg(test)]
299mod test {
300 use std::{env::set_var, process::Command, str::from_utf8, sync::Once};
301
302 use super::*;
303
304 static INIT: Once = Once::new();
305
306 fn set_env() {
307 INIT.call_once(|| {
308 let tmp_dir = glib::mkdtemp("gio-rs-test-schemas-XXXXXX").unwrap();
309
310 let output = Command::new("glib-compile-schemas")
311 .args([
312 &format!("{}/tests", env!("CARGO_MANIFEST_DIR")),
313 "--targetdir",
314 tmp_dir.to_str().unwrap(),
315 ])
316 .output()
317 .unwrap();
318
319 if !output.status.success() {
320 println!("Failed to generate GSchema!");
321 println!(
322 "glib-compile-schemas stdout: {}",
323 from_utf8(&output.stdout).unwrap()
324 );
325 println!(
326 "glib-compile-schemas stderr: {}",
327 from_utf8(&output.stderr).unwrap()
328 );
329 panic!("Can't test without GSchemas!");
330 }
331
332 unsafe { set_var("GSETTINGS_SCHEMA_DIR", tmp_dir) };
334 unsafe { set_var("GSETTINGS_BACKEND", "memory") };
336 });
337 }
338
339 #[test]
340 #[serial_test::serial]
341 fn string_get() {
342 set_env();
343 let settings = Settings::new("com.github.gtk-rs.test");
344 assert_eq!(settings.get::<String>("test-string").as_str(), "Good");
345 }
346
347 #[test]
348 #[serial_test::serial]
349 fn bool_set_get() {
350 set_env();
351 let settings = Settings::new("com.github.gtk-rs.test");
352 settings.set("test-bool", false).unwrap();
353 assert!(!settings.get::<bool>("test-bool"));
354 }
355
356 #[test]
357 #[should_panic]
358 #[serial_test::serial]
359 fn wrong_type() {
360 set_env();
361 let settings = Settings::new("com.github.gtk-rs.test");
362 settings.get::<u8>("test-string");
363 }
364}