gio/
list_store.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{cell::Cell, cmp::Ordering, rc::Rc};
4
5use glib::{prelude::*, translate::*, Object};
6
7use crate::{ffi, prelude::*, ListModel, ListStore};
8
9impl ListStore {
10    #[doc(alias = "g_list_store_new")]
11    pub fn new<T: IsA<Object>>() -> Self {
12        Self::with_type(T::static_type())
13    }
14
15    #[doc(alias = "g_list_store_new")]
16    pub fn with_type(type_: glib::types::Type) -> Self {
17        unsafe { from_glib_full(ffi::g_list_store_new(type_.into_glib())) }
18    }
19
20    #[doc(alias = "g_list_store_insert_sorted")]
21    pub fn insert_sorted<P: IsA<glib::Object>, F: FnMut(&Object, &Object) -> Ordering>(
22        &self,
23        item: &P,
24        compare_func: F,
25    ) -> u32 {
26        unsafe {
27            let mut func = compare_func;
28            let func_obj: &mut dyn FnMut(&Object, &Object) -> Ordering = &mut func;
29            let func_ptr = &func_obj as *const &mut dyn FnMut(&Object, &Object) -> Ordering
30                as glib::ffi::gpointer;
31
32            ffi::g_list_store_insert_sorted(
33                self.to_glib_none().0,
34                item.as_ref().to_glib_none().0,
35                Some(compare_func_trampoline),
36                func_ptr,
37            )
38        }
39    }
40
41    #[doc(alias = "g_list_store_sort")]
42    pub fn sort<F: FnMut(&Object, &Object) -> Ordering>(&self, compare_func: F) {
43        unsafe {
44            let mut func = compare_func;
45            let func_obj: &mut dyn FnMut(&Object, &Object) -> Ordering = &mut func;
46            let func_ptr = &func_obj as *const &mut dyn FnMut(&Object, &Object) -> Ordering
47                as glib::ffi::gpointer;
48
49            ffi::g_list_store_sort(
50                self.to_glib_none().0,
51                Some(compare_func_trampoline),
52                func_ptr,
53            )
54        }
55    }
56
57    #[doc(alias = "g_list_store_splice")]
58    pub fn splice(&self, position: u32, n_removals: u32, additions: &[impl IsA<glib::Object>]) {
59        let n_additions = additions.len() as u32;
60        unsafe {
61            let additions = additions.as_ptr() as *mut *mut glib::gobject_ffi::GObject;
62
63            ffi::g_list_store_splice(
64                self.to_glib_none().0,
65                position,
66                n_removals,
67                additions,
68                n_additions,
69            );
70        }
71    }
72
73    // rustdoc-stripper-ignore-next
74    /// Appends all elements in a slice to the `ListStore`.
75    pub fn extend_from_slice(&self, additions: &[impl IsA<glib::Object>]) {
76        self.splice(self.n_items(), 0, additions)
77    }
78
79    // rustdoc-stripper-ignore-next
80    /// Retains only the elements specified by the predicate.
81    /// This method operates in place, visiting each element exactly once in the original order,
82    /// and preserves the order of the retained elements.
83    /// Because the elements are visited exactly once in the original order,
84    /// external state may be used to decide which elements to keep.
85    ///
86    /// # Panics
87    /// Panics if the predicate closure mutates the list by removing or adding items.
88    pub fn retain(&self, mut f: impl FnMut(&glib::Object) -> bool) {
89        let mut consec_removed = 0;
90        let mut i = 0;
91        const ADDITIONS: &[glib::Object] = &[]; // To satisfy the type checker
92
93        let changed = Rc::new(Cell::new(false));
94        let changed_clone = changed.clone();
95        let signal_id = self.connect_items_changed(move |_list, _, _, _| changed_clone.set(true));
96
97        let _signal_guard = {
98            struct Guard<'a> {
99                list_store: &'a ListStore,
100                signal_id: Option<glib::SignalHandlerId>,
101            }
102            impl Drop for Guard<'_> {
103                fn drop(&mut self) {
104                    self.list_store.disconnect(self.signal_id.take().unwrap());
105                }
106            }
107            Guard {
108                list_store: self,
109                signal_id: Some(signal_id),
110            }
111        };
112
113        while i < self.n_items() {
114            let keep = f(self.item(i).unwrap().as_ref());
115            if changed.get() {
116                panic!("The closure passed to ListStore::retain() must not mutate the list store");
117            }
118            if !keep {
119                consec_removed += 1;
120            } else if consec_removed > 0 {
121                self.splice(i - consec_removed, consec_removed, ADDITIONS);
122                changed.set(false);
123                i -= consec_removed;
124                consec_removed = 0;
125            }
126            i += 1;
127        }
128        if consec_removed > 0 {
129            self.splice(i - consec_removed, consec_removed, ADDITIONS);
130        }
131    }
132
133    #[cfg(feature = "v2_74")]
134    #[cfg_attr(docsrs, doc(cfg(feature = "v2_74")))]
135    #[doc(alias = "g_list_store_find_with_equal_func_full")]
136    #[doc(alias = "g_list_store_find_with_equal_func")]
137    pub fn find_with_equal_func<F: FnMut(&glib::Object) -> bool>(
138        &self,
139        equal_func: F,
140    ) -> Option<u32> {
141        unsafe extern "C" fn equal_func_trampoline(
142            a: glib::ffi::gconstpointer,
143            _b: glib::ffi::gconstpointer,
144            func: glib::ffi::gpointer,
145        ) -> glib::ffi::gboolean {
146            let func = func as *mut &mut (dyn FnMut(&Object) -> bool);
147
148            let a = from_glib_borrow(a as *mut glib::gobject_ffi::GObject);
149
150            (*func)(&a).into_glib()
151        }
152
153        let mut func = equal_func;
154        let func_obj: &mut (dyn FnMut(&Object) -> bool) = &mut func;
155        let func_ptr = &func_obj as *const &mut (dyn FnMut(&Object) -> bool) as glib::ffi::gpointer;
156        let mut position = std::mem::MaybeUninit::uninit();
157
158        // GIO prior to 2.76 requires a non-NULL item to be passed in so we're constructing a fake item here.
159        // See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3284
160        #[cfg(not(feature = "v2_76"))]
161        let result = unsafe {
162            let g_class: *mut glib::gobject_ffi::GTypeClass =
163                glib::gobject_ffi::g_type_class_peek(self.item_type().into_glib()) as *mut _;
164
165            // g_class will be `NULL` when no instance of the `item-type` has been created yet.
166            // See https://github.com/gtk-rs/gtk-rs-core/issues/1767
167            if g_class.is_null() {
168                return None;
169            }
170
171            let item = glib::gobject_ffi::GObject {
172                g_type_instance: glib::gobject_ffi::GTypeInstance { g_class },
173                ref_count: 1,
174                qdata: std::ptr::null_mut(),
175            };
176
177            bool::from_glib(ffi::g_list_store_find_with_equal_func_full(
178                self.to_glib_none().0,
179                mut_override(&item as *const _),
180                Some(equal_func_trampoline),
181                func_ptr,
182                position.as_mut_ptr(),
183            ))
184            .then(|| position.assume_init())
185        };
186
187        #[cfg(feature = "v2_76")]
188        let result = unsafe {
189            bool::from_glib(ffi::g_list_store_find_with_equal_func_full(
190                self.to_glib_none().0,
191                std::ptr::null_mut(),
192                Some(equal_func_trampoline),
193                func_ptr,
194                position.as_mut_ptr(),
195            ))
196            .then(|| position.assume_init())
197        };
198
199        result
200    }
201}
202
203impl<P: IsA<glib::Object>> std::iter::FromIterator<P> for ListStore {
204    fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
205        let store = Self::new::<P>();
206        for item in iter.into_iter() {
207            store.append(&item)
208        }
209        store
210    }
211}
212
213impl<'a> std::iter::IntoIterator for &'a ListStore {
214    type Item = <&'a ListModel as IntoIterator>::Item;
215    type IntoIter = <&'a ListModel as IntoIterator>::IntoIter;
216
217    fn into_iter(self) -> Self::IntoIter {
218        self.upcast_ref::<ListModel>().into_iter()
219    }
220}
221
222unsafe extern "C" fn compare_func_trampoline(
223    a: glib::ffi::gconstpointer,
224    b: glib::ffi::gconstpointer,
225    func: glib::ffi::gpointer,
226) -> i32 {
227    let func = func as *mut &mut dyn FnMut(&Object, &Object) -> Ordering;
228
229    let a = from_glib_borrow(a as *mut glib::gobject_ffi::GObject);
230    let b = from_glib_borrow(b as *mut glib::gobject_ffi::GObject);
231
232    (*func)(&a, &b).into_glib()
233}
234
235impl<A: AsRef<glib::Object>> std::iter::Extend<A> for ListStore {
236    fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) {
237        let additions = iter
238            .into_iter()
239            .map(|o| o.as_ref().clone())
240            .collect::<Vec<_>>();
241        self.splice(self.n_items(), 0, &additions)
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use crate::{prelude::*, ListStore};
248
249    #[test]
250    fn splice() {
251        let item0 = ListStore::new::<ListStore>();
252        let item1 = ListStore::new::<ListStore>();
253        let list = ListStore::new::<ListStore>();
254        list.splice(0, 0, &[item0.clone(), item1.clone()]);
255        assert_eq!(list.item(0), Some(item0.upcast()));
256        assert_eq!(list.item(1), Some(item1.upcast()));
257    }
258
259    #[test]
260    fn extend() {
261        let item0 = ListStore::new::<ListStore>();
262        let item1 = ListStore::new::<ListStore>();
263        let mut list = ListStore::new::<ListStore>();
264        list.extend([&item0, &item1]);
265        assert_eq!(list.item(0).as_ref(), Some(item0.upcast_ref()));
266        assert_eq!(list.item(1).as_ref(), Some(item1.upcast_ref()));
267        list.extend([item0.clone(), item1.clone()]);
268        assert_eq!(list.item(2).as_ref(), Some(item0.upcast_ref()));
269        assert_eq!(list.item(3).as_ref(), Some(item1.upcast_ref()));
270
271        let list_from_slice = ListStore::new::<ListStore>();
272        list_from_slice.extend_from_slice(&[item0, item1.clone()]);
273        assert_eq!(list_from_slice.item(1).as_ref(), Some(item1.upcast_ref()));
274    }
275
276    #[test]
277    fn from_iterator() {
278        let item0 = ListStore::new::<ListStore>();
279        let item1 = ListStore::new::<ListStore>();
280        let v = vec![item0.clone(), item1.clone()];
281        let list = ListStore::from_iter(v);
282        assert_eq!(list.item(0).as_ref(), Some(item0.upcast_ref()));
283        assert_eq!(list.item(1).as_ref(), Some(item1.upcast_ref()));
284        assert_eq!(list.item(2).as_ref(), None);
285    }
286
287    #[cfg(feature = "v2_74")]
288    #[test]
289    fn find() {
290        let item0 = ListStore::new::<ListStore>();
291        let item1 = ListStore::new::<ListStore>();
292        let list = ListStore::new::<ListStore>();
293        list.append(&item0);
294        list.append(&item1);
295
296        let res = list.find_with_equal_func(|item| item == &item1);
297        assert_eq!(res, Some(1));
298    }
299
300    #[test]
301    fn retain() {
302        let list = {
303            let list = ListStore::new::<ListStore>();
304            for _ in 0..10 {
305                list.append(&ListStore::new::<ListStore>());
306            }
307            list
308        };
309
310        use std::cell::Cell;
311        use std::rc::Rc;
312
313        let signal_count = Rc::new(Cell::new(0));
314        let signal_count_clone = signal_count.clone();
315        list.connect_items_changed(move |_, _, _, _| {
316            signal_count_clone.set(signal_count_clone.get() + 1);
317        });
318
319        let to_keep = [
320            // list.item(0).unwrap(),
321            list.item(1).unwrap(),
322            // list.item(2).unwrap(),
323            list.item(3).unwrap(),
324            // list.item(4).unwrap(),
325            // list.item(5).unwrap(),
326            // list.item(6).unwrap(),
327            list.item(7).unwrap(),
328            // list.item(8).unwrap(),
329            // list.item(9).unwrap(),
330        ];
331        list.retain(|item| to_keep.contains(item));
332
333        // Check that we removed the correct items
334        assert_eq!(list.n_items(), 3);
335        assert_eq!(list.item(0).as_ref(), Some(&to_keep[0]));
336        assert_eq!(list.item(1).as_ref(), Some(&to_keep[1]));
337        assert_eq!(list.item(2).as_ref(), Some(&to_keep[2]));
338
339        assert_eq!(signal_count.get(), 4);
340    }
341}