1 // Take a look at the license at the top of the repository in the LICENSE file.
2 
3 use crate::prelude::*;
4 use crate::{Settings, SettingsBindFlags};
5 use glib::translate::{from_glib_borrow, from_glib_none, IntoGlib, ToGlibPtr};
6 use glib::variant::FromVariant;
7 use glib::{BoolError, IsA, ToVariant};
8 
9 #[must_use]
10 pub struct BindingBuilder<'a> {
11     settings: &'a Settings,
12     key: &'a str,
13     object: &'a glib::Object,
14     property: &'a str,
15     flags: SettingsBindFlags,
16     get_mapping: Option<Box<dyn Fn(&glib::Variant, glib::Type) -> Option<glib::Value>>>,
17     set_mapping: Option<Box<dyn Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant>>>,
18 }
19 
20 impl<'a> BindingBuilder<'a> {
flags(mut self, flags: SettingsBindFlags) -> Self21     pub fn flags(mut self, flags: SettingsBindFlags) -> Self {
22         self.flags = flags;
23         self
24     }
25 
26     #[doc(alias = "get_mapping")]
mapping<F: Fn(&glib::Variant, glib::Type) -> Option<glib::Value> + 'static>( mut self, f: F, ) -> Self27     pub fn mapping<F: Fn(&glib::Variant, glib::Type) -> Option<glib::Value> + 'static>(
28         mut self,
29         f: F,
30     ) -> Self {
31         self.get_mapping = Some(Box::new(f));
32         self
33     }
34 
set_mapping< F: Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant> + 'static, >( mut self, f: F, ) -> Self35     pub fn set_mapping<
36         F: Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant> + 'static,
37     >(
38         mut self,
39         f: F,
40     ) -> Self {
41         self.set_mapping = Some(Box::new(f));
42         self
43     }
44 
build(self)45     pub fn build(self) {
46         type Mappings = (
47             Option<Box<dyn Fn(&glib::Variant, glib::Type) -> Option<glib::Value>>>,
48             Option<Box<dyn Fn(&glib::Value, glib::VariantType) -> Option<glib::Variant>>>,
49         );
50         unsafe extern "C" fn bind_with_mapping_get_trampoline(
51             value: *mut glib::gobject_ffi::GValue,
52             variant: *mut glib::ffi::GVariant,
53             user_data: glib::ffi::gpointer,
54         ) -> glib::ffi::gboolean {
55             let user_data = &*(user_data as *const Mappings);
56             let f = user_data.0.as_ref().unwrap();
57             let value = &mut *(value as *mut glib::Value);
58             if let Some(v) = f(&*from_glib_borrow(variant), value.type_()) {
59                 *value = v;
60                 true
61             } else {
62                 false
63             }
64             .into_glib()
65         }
66         unsafe extern "C" fn bind_with_mapping_set_trampoline(
67             value: *const glib::gobject_ffi::GValue,
68             variant_type: *const glib::ffi::GVariantType,
69             user_data: glib::ffi::gpointer,
70         ) -> *mut glib::ffi::GVariant {
71             let user_data = &*(user_data as *const Mappings);
72             let f = user_data.1.as_ref().unwrap();
73             let value = &*(value as *const glib::Value);
74             f(value, from_glib_none(variant_type)).to_glib_full()
75         }
76         unsafe extern "C" fn destroy_closure(ptr: *mut libc::c_void) {
77             Box::<Mappings>::from_raw(ptr as *mut _);
78         }
79 
80         if self.get_mapping.is_none() && self.set_mapping.is_none() {
81             unsafe {
82                 ffi::g_settings_bind(
83                     self.settings.to_glib_none().0,
84                     self.key.to_glib_none().0,
85                     self.object.to_glib_none().0,
86                     self.property.to_glib_none().0,
87                     self.flags.into_glib(),
88                 );
89             }
90         } else {
91             let get_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> =
92                 if self.get_mapping.is_none() {
93                     None
94                 } else {
95                     Some(bind_with_mapping_get_trampoline)
96                 };
97             let set_trampoline: Option<unsafe extern "C" fn(_, _, _) -> _> =
98                 if self.set_mapping.is_none() {
99                     None
100                 } else {
101                     Some(bind_with_mapping_set_trampoline)
102                 };
103             let mappings: Mappings = (self.get_mapping, self.set_mapping);
104             unsafe {
105                 ffi::g_settings_bind_with_mapping(
106                     self.settings.to_glib_none().0,
107                     self.key.to_glib_none().0,
108                     self.object.to_glib_none().0,
109                     self.property.to_glib_none().0,
110                     self.flags.into_glib(),
111                     get_trampoline,
112                     set_trampoline,
113                     Box::into_raw(Box::new(mappings)) as *mut libc::c_void,
114                     Some(destroy_closure),
115                 )
116             }
117         }
118     }
119 }
120 
121 pub trait SettingsExtManual {
get<U: FromVariant>(&self, key: &str) -> U122     fn get<U: FromVariant>(&self, key: &str) -> U;
123 
set<U: ToVariant>(&self, key: &str, value: &U) -> Result<(), BoolError>124     fn set<U: ToVariant>(&self, key: &str, value: &U) -> Result<(), BoolError>;
125 
126     #[doc(alias = "g_settings_bind")]
127     #[doc(alias = "g_settings_bind_with_mapping")]
bind<'a, P: IsA<glib::Object>>( &'a self, key: &'a str, object: &'a P, property: &'a str, ) -> BindingBuilder<'a>128     fn bind<'a, P: IsA<glib::Object>>(
129         &'a self,
130         key: &'a str,
131         object: &'a P,
132         property: &'a str,
133     ) -> BindingBuilder<'a>;
134 }
135 
136 impl<O: IsA<Settings>> SettingsExtManual for O {
get<U: FromVariant>(&self, key: &str) -> U137     fn get<U: FromVariant>(&self, key: &str) -> U {
138         let val = self.value(key);
139         FromVariant::from_variant(&val).unwrap_or_else(|| {
140             panic!(
141                 "Type mismatch: Expected '{}' got '{}'",
142                 U::static_variant_type().to_str(),
143                 val.type_()
144             )
145         })
146     }
147 
set<U: ToVariant>(&self, key: &str, value: &U) -> Result<(), BoolError>148     fn set<U: ToVariant>(&self, key: &str, value: &U) -> Result<(), BoolError> {
149         self.set_value(key, &value.to_variant())
150     }
151 
bind<'a, P: IsA<glib::Object>>( &'a self, key: &'a str, object: &'a P, property: &'a str, ) -> BindingBuilder<'a>152     fn bind<'a, P: IsA<glib::Object>>(
153         &'a self,
154         key: &'a str,
155         object: &'a P,
156         property: &'a str,
157     ) -> BindingBuilder<'a> {
158         BindingBuilder {
159             settings: self.upcast_ref(),
160             key,
161             object: object.upcast_ref(),
162             property,
163             flags: SettingsBindFlags::DEFAULT,
164             get_mapping: None,
165             set_mapping: None,
166         }
167     }
168 }
169 
170 #[cfg(test)]
171 mod test {
172     use super::*;
173     use std::env::set_var;
174     use std::process::Command;
175     use std::str::from_utf8;
176     use std::sync::Once;
177 
178     static INIT: Once = Once::new();
179 
set_env()180     fn set_env() {
181         INIT.call_once(|| {
182             let output = Command::new("glib-compile-schemas")
183                 .args(&[
184                     &format!("{}/tests", env!("CARGO_MANIFEST_DIR")),
185                     "--targetdir",
186                     env!("OUT_DIR"),
187                 ])
188                 .output()
189                 .unwrap();
190 
191             if !output.status.success() {
192                 println!("Failed to generate GSchema!");
193                 println!(
194                     "glib-compile-schemas stdout: {}",
195                     from_utf8(&output.stdout).unwrap()
196                 );
197                 println!(
198                     "glib-compile-schemas stderr: {}",
199                     from_utf8(&output.stderr).unwrap()
200                 );
201                 panic!("Can't test without GSchemas!");
202             }
203 
204             set_var("GSETTINGS_SCHEMA_DIR", env!("OUT_DIR"));
205             set_var("GSETTINGS_BACKEND", "memory");
206         });
207     }
208 
209     #[test]
210     #[serial_test::serial]
string_get()211     fn string_get() {
212         set_env();
213         let settings = Settings::new("com.github.gtk-rs.test");
214         assert_eq!(settings.get::<String>("test-string").as_str(), "Good");
215     }
216 
217     #[test]
218     #[serial_test::serial]
bool_set_get()219     fn bool_set_get() {
220         set_env();
221         let settings = Settings::new("com.github.gtk-rs.test");
222         settings.set("test-bool", &false).unwrap();
223         assert!(!settings.get::<bool>("test-bool"));
224     }
225 
226     #[test]
227     #[should_panic]
228     #[serial_test::serial]
wrong_type()229     fn wrong_type() {
230         set_env();
231         let settings = Settings::new("com.github.gtk-rs.test");
232         settings.get::<u8>("test-string");
233     }
234 }
235