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")]
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")]
226 fn set_strv(&self, key: &str, value: impl IntoStrV) -> Result<(), glib::error::BoolError> {
227 unsafe {
228 value.run_with_strv(|value| {
229 glib::result_from_gboolean!(
230 ffi::g_settings_set_strv(
231 self.as_ref().to_glib_none().0,
232 key.to_glib_none().0,
233 value.as_ptr() as *mut _,
234 ),
235 "Can't set readonly key"
236 )
237 })
238 }
239 }
240
241 #[doc(alias = "g_settings_bind")]
270 #[doc(alias = "g_settings_bind_with_mapping")]
271 fn bind<'a, P: IsA<glib::Object>>(
272 &'a self,
273 key: &'a str,
274 object: &'a P,
275 property: &'a str,
276 ) -> BindingBuilder<'a> {
277 BindingBuilder {
278 settings: self.upcast_ref(),
279 key,
280 object: object.upcast_ref(),
281 property,
282 flags: SettingsBindFlags::DEFAULT,
283 get_mapping: None,
284 set_mapping: None,
285 }
286 }
287}
288
289impl<O: IsA<Settings>> SettingsExtManual for O {}
290
291#[cfg(test)]
292mod test {
293 use std::{env::set_var, process::Command, str::from_utf8, sync::Once};
294
295 use super::*;
296
297 static INIT: Once = Once::new();
298
299 fn set_env() {
300 INIT.call_once(|| {
301 let tmp_dir = glib::mkdtemp("gio-rs-test-schemas-XXXXXX").unwrap();
302
303 let output = Command::new("glib-compile-schemas")
304 .args([
305 &format!("{}/tests", env!("CARGO_MANIFEST_DIR")),
306 "--targetdir",
307 tmp_dir.to_str().unwrap(),
308 ])
309 .output()
310 .unwrap();
311
312 if !output.status.success() {
313 println!("Failed to generate GSchema!");
314 println!(
315 "glib-compile-schemas stdout: {}",
316 from_utf8(&output.stdout).unwrap()
317 );
318 println!(
319 "glib-compile-schemas stderr: {}",
320 from_utf8(&output.stderr).unwrap()
321 );
322 panic!("Can't test without GSchemas!");
323 }
324
325 unsafe { set_var("GSETTINGS_SCHEMA_DIR", tmp_dir) };
327 unsafe { set_var("GSETTINGS_BACKEND", "memory") };
329 });
330 }
331
332 #[test]
333 #[serial_test::serial]
334 fn string_get() {
335 set_env();
336 let settings = Settings::new("com.github.gtk-rs.test");
337 assert_eq!(settings.get::<String>("test-string").as_str(), "Good");
338 }
339
340 #[test]
341 #[serial_test::serial]
342 fn bool_set_get() {
343 set_env();
344 let settings = Settings::new("com.github.gtk-rs.test");
345 settings.set("test-bool", false).unwrap();
346 assert!(!settings.get::<bool>("test-bool"));
347 }
348
349 #[test]
350 #[should_panic]
351 #[serial_test::serial]
352 fn wrong_type() {
353 set_env();
354 let settings = Settings::new("com.github.gtk-rs.test");
355 settings.get::<u8>("test-string");
356 }
357}