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    /// Creates a new #GListStore with items of type @item_type. @item_type
11    /// must be a subclass of #GObject.
12    /// ## `item_type`
13    /// the #GType of items in the list
14    ///
15    /// # Returns
16    ///
17    /// a new #GListStore
18    #[doc(alias = "g_list_store_new")]
19    pub fn new<T: IsA<Object>>() -> Self {
20        Self::with_type(T::static_type())
21    }
22
23    #[doc(alias = "g_list_store_new")]
24    pub fn with_type(type_: glib::types::Type) -> Self {
25        unsafe { from_glib_full(ffi::g_list_store_new(type_.into_glib())) }
26    }
27
28    /// Inserts @item into @self at a position to be determined by the
29    /// @compare_func.
30    ///
31    /// The list must already be sorted before calling this function or the
32    /// result is undefined.  Usually you would approach this by only ever
33    /// inserting items by way of this function.
34    ///
35    /// This function takes a ref on @item.
36    /// ## `item`
37    /// the new item
38    /// ## `compare_func`
39    /// pairwise comparison function for sorting
40    ///
41    /// # Returns
42    ///
43    /// the position at which @item was inserted
44    #[doc(alias = "g_list_store_insert_sorted")]
45    pub fn insert_sorted<P: IsA<glib::Object>, F: FnMut(&Object, &Object) -> Ordering>(
46        &self,
47        item: &P,
48        compare_func: F,
49    ) -> u32 {
50        unsafe {
51            let mut func = compare_func;
52            let func_obj: &mut (dyn FnMut(&Object, &Object) -> Ordering) = &mut func;
53            let func_ptr = &func_obj as *const &mut (dyn FnMut(&Object, &Object) -> Ordering)
54                as glib::ffi::gpointer;
55
56            ffi::g_list_store_insert_sorted(
57                self.to_glib_none().0,
58                item.as_ref().to_glib_none().0,
59                Some(compare_func_trampoline),
60                func_ptr,
61            )
62        }
63    }
64
65    /// Sort the items in @self according to @compare_func.
66    /// ## `compare_func`
67    /// pairwise comparison function for sorting
68    #[doc(alias = "g_list_store_sort")]
69    pub fn sort<F: FnMut(&Object, &Object) -> Ordering>(&self, compare_func: F) {
70        unsafe {
71            let mut func = compare_func;
72            let func_obj: &mut (dyn FnMut(&Object, &Object) -> Ordering) = &mut func;
73            let func_ptr = &func_obj as *const &mut (dyn FnMut(&Object, &Object) -> Ordering)
74                as glib::ffi::gpointer;
75
76            ffi::g_list_store_sort(
77                self.to_glib_none().0,
78                Some(compare_func_trampoline),
79                func_ptr,
80            )
81        }
82    }
83
84    /// Changes @self by removing @n_removals items and adding @n_additions
85    /// items to it. @additions must contain @n_additions items of type
86    /// #GListStore:item-type.  [`None`] is not permitted.
87    ///
88    /// This function is more efficient than g_list_store_insert() and
89    /// g_list_store_remove(), because it only emits
90    /// #GListModel::items-changed once for the change.
91    ///
92    /// This function takes a ref on each item in @additions.
93    ///
94    /// The parameters @position and @n_removals must be correct (ie:
95    /// @position + @n_removals must be less than or equal to the length of
96    /// the list at the time this function is called).
97    /// ## `position`
98    /// the position at which to make the change
99    /// ## `n_removals`
100    /// the number of items to remove
101    /// ## `additions`
102    /// the items to add
103    #[doc(alias = "g_list_store_splice")]
104    pub fn splice(&self, position: u32, n_removals: u32, additions: &[impl IsA<glib::Object>]) {
105        let n_additions = additions.len() as u32;
106        unsafe {
107            let additions = additions.as_ptr() as *mut *mut glib::gobject_ffi::GObject;
108
109            ffi::g_list_store_splice(
110                self.to_glib_none().0,
111                position,
112                n_removals,
113                additions,
114                n_additions,
115            );
116        }
117    }
118
119    // rustdoc-stripper-ignore-next
120    /// Appends all elements in a slice to the `ListStore`.
121    pub fn extend_from_slice(&self, additions: &[impl IsA<glib::Object>]) {
122        self.splice(self.n_items(), 0, additions)
123    }
124
125    // rustdoc-stripper-ignore-next
126    /// Retains only the elements specified by the predicate.
127    /// This method operates in place, visiting each element exactly once in the original order,
128    /// and preserves the order of the retained elements.
129    /// Because the elements are visited exactly once in the original order,
130    /// external state may be used to decide which elements to keep.
131    ///
132    /// # Panics
133    /// Panics if the predicate closure mutates the list by removing or adding items.
134    pub fn retain(&self, mut f: impl FnMut(&glib::Object) -> bool) {
135        let mut consec_removed = 0;
136        let mut i = 0;
137        const ADDITIONS: &[glib::Object] = &[]; // To satisfy the type checker
138
139        let changed = Rc::new(Cell::new(false));
140        let changed_clone = changed.clone();
141        let signal_id = self.connect_items_changed(move |_list, _, _, _| changed_clone.set(true));
142
143        let _signal_guard = {
144            struct Guard<'a> {
145                list_store: &'a ListStore,
146                signal_id: Option<glib::SignalHandlerId>,
147            }
148            impl Drop for Guard<'_> {
149                fn drop(&mut self) {
150                    self.list_store.disconnect(self.signal_id.take().unwrap());
151                }
152            }
153            Guard {
154                list_store: self,
155                signal_id: Some(signal_id),
156            }
157        };
158
159        while i < self.n_items() {
160            let keep = f(self.item(i).unwrap().as_ref());
161            if changed.get() {
162                panic!("The closure passed to ListStore::retain() must not mutate the list store");
163            }
164            if !keep {
165                consec_removed += 1;
166            } else if consec_removed > 0 {
167                self.splice(i - consec_removed, consec_removed, ADDITIONS);
168                changed.set(false);
169                i -= consec_removed;
170                consec_removed = 0;
171            }
172            i += 1;
173        }
174        if consec_removed > 0 {
175            self.splice(i - consec_removed, consec_removed, ADDITIONS);
176        }
177    }
178
179    /// Looks up the given @item in the list store by looping over the items and
180    /// comparing them with @equal_func until the first occurrence of @item which
181    /// matches. If @item was not found, then @position will not be set, and this
182    /// method will return [`false`].
183    ///
184    /// @item is always passed as second parameter to @equal_func.
185    ///
186    /// Since GLib 2.76 it is possible to pass `NULL` for @item.
187    /// ## `item`
188    /// an item
189    /// ## `equal_func`
190    /// A custom equality check function
191    ///
192    /// # Returns
193    ///
194    /// Whether @self contains @item. If it was found, @position will be
195    /// set to the position where @item occurred for the first time.
196    ///
197    /// ## `position`
198    /// the first position of @item, if it was found.
199    #[cfg(feature = "v2_74")]
200    #[cfg_attr(docsrs, doc(cfg(feature = "v2_74")))]
201    #[doc(alias = "g_list_store_find_with_equal_func_full")]
202    #[doc(alias = "g_list_store_find_with_equal_func")]
203    pub fn find_with_equal_func<F: FnMut(&glib::Object) -> bool>(
204        &self,
205        equal_func: F,
206    ) -> Option<u32> {
207        unsafe extern "C" fn equal_func_trampoline(
208            a: glib::ffi::gconstpointer,
209            _b: glib::ffi::gconstpointer,
210            func: glib::ffi::gpointer,
211        ) -> glib::ffi::gboolean {
212            let func = func as *mut &mut (dyn FnMut(&Object) -> bool);
213
214            let a = from_glib_borrow(a as *mut glib::gobject_ffi::GObject);
215
216            (*func)(&a).into_glib()
217        }
218
219        unsafe {
220            // GIO requires a non-NULL item to be passed in so we're constructing a fake item here.
221            // See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3284
222            let item = glib::gobject_ffi::GObject {
223                g_type_instance: glib::gobject_ffi::GTypeInstance {
224                    g_class: glib::gobject_ffi::g_type_class_peek(self.item_type().into_glib())
225                        as *mut _,
226                },
227                ref_count: 1,
228                qdata: std::ptr::null_mut(),
229            };
230            let mut func = equal_func;
231            let func_obj: &mut (dyn FnMut(&Object) -> bool) = &mut func;
232            let func_ptr =
233                &func_obj as *const &mut (dyn FnMut(&Object) -> bool) as glib::ffi::gpointer;
234
235            let mut position = std::mem::MaybeUninit::uninit();
236
237            let found = bool::from_glib(ffi::g_list_store_find_with_equal_func_full(
238                self.to_glib_none().0,
239                mut_override(&item as *const _),
240                Some(equal_func_trampoline),
241                func_ptr,
242                position.as_mut_ptr(),
243            ));
244
245            found.then(|| position.assume_init())
246        }
247    }
248}
249
250impl<P: IsA<glib::Object>> std::iter::FromIterator<P> for ListStore {
251    fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
252        let store = Self::new::<P>();
253        for item in iter.into_iter() {
254            store.append(&item)
255        }
256        store
257    }
258}
259
260impl<'a> std::iter::IntoIterator for &'a ListStore {
261    type Item = <&'a ListModel as IntoIterator>::Item;
262    type IntoIter = <&'a ListModel as IntoIterator>::IntoIter;
263
264    fn into_iter(self) -> Self::IntoIter {
265        self.upcast_ref::<ListModel>().into_iter()
266    }
267}
268
269unsafe extern "C" fn compare_func_trampoline(
270    a: glib::ffi::gconstpointer,
271    b: glib::ffi::gconstpointer,
272    func: glib::ffi::gpointer,
273) -> i32 {
274    let func = func as *mut &mut (dyn FnMut(&Object, &Object) -> Ordering);
275
276    let a = from_glib_borrow(a as *mut glib::gobject_ffi::GObject);
277    let b = from_glib_borrow(b as *mut glib::gobject_ffi::GObject);
278
279    (*func)(&a, &b).into_glib()
280}
281
282impl<A: AsRef<glib::Object>> std::iter::Extend<A> for ListStore {
283    fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) {
284        let additions = iter
285            .into_iter()
286            .map(|o| o.as_ref().clone())
287            .collect::<Vec<_>>();
288        self.splice(self.n_items(), 0, &additions)
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use crate::{prelude::*, ListStore};
295
296    #[test]
297    fn splice() {
298        let item0 = ListStore::new::<ListStore>();
299        let item1 = ListStore::new::<ListStore>();
300        let list = ListStore::new::<ListStore>();
301        list.splice(0, 0, &[item0.clone(), item1.clone()]);
302        assert_eq!(list.item(0), Some(item0.upcast()));
303        assert_eq!(list.item(1), Some(item1.upcast()));
304    }
305
306    #[test]
307    fn extend() {
308        let item0 = ListStore::new::<ListStore>();
309        let item1 = ListStore::new::<ListStore>();
310        let mut list = ListStore::new::<ListStore>();
311        list.extend([&item0, &item1]);
312        assert_eq!(list.item(0).as_ref(), Some(item0.upcast_ref()));
313        assert_eq!(list.item(1).as_ref(), Some(item1.upcast_ref()));
314        list.extend([item0.clone(), item1.clone()]);
315        assert_eq!(list.item(2).as_ref(), Some(item0.upcast_ref()));
316        assert_eq!(list.item(3).as_ref(), Some(item1.upcast_ref()));
317
318        let list_from_slice = ListStore::new::<ListStore>();
319        list_from_slice.extend_from_slice(&[item0, item1.clone()]);
320        assert_eq!(list_from_slice.item(1).as_ref(), Some(item1.upcast_ref()));
321    }
322
323    #[test]
324    fn from_iterator() {
325        let item0 = ListStore::new::<ListStore>();
326        let item1 = ListStore::new::<ListStore>();
327        let v = vec![item0.clone(), item1.clone()];
328        let list = ListStore::from_iter(v);
329        assert_eq!(list.item(0).as_ref(), Some(item0.upcast_ref()));
330        assert_eq!(list.item(1).as_ref(), Some(item1.upcast_ref()));
331        assert_eq!(list.item(2).as_ref(), None);
332    }
333
334    #[cfg(feature = "v2_74")]
335    #[test]
336    fn find() {
337        let item0 = ListStore::new::<ListStore>();
338        let item1 = ListStore::new::<ListStore>();
339        let list = ListStore::new::<ListStore>();
340        list.append(&item0);
341        list.append(&item1);
342
343        let res = list.find_with_equal_func(|item| item == &item1);
344        assert_eq!(res, Some(1));
345    }
346
347    #[test]
348    fn retain() {
349        let list = {
350            let list = ListStore::new::<ListStore>();
351            for _ in 0..10 {
352                list.append(&ListStore::new::<ListStore>());
353            }
354            list
355        };
356
357        use std::cell::Cell;
358        use std::rc::Rc;
359
360        let signal_count = Rc::new(Cell::new(0));
361        let signal_count_clone = signal_count.clone();
362        list.connect_items_changed(move |_, _, _, _| {
363            signal_count_clone.set(signal_count_clone.get() + 1);
364        });
365
366        let to_keep = [
367            // list.item(0).unwrap(),
368            list.item(1).unwrap(),
369            // list.item(2).unwrap(),
370            list.item(3).unwrap(),
371            // list.item(4).unwrap(),
372            // list.item(5).unwrap(),
373            // list.item(6).unwrap(),
374            list.item(7).unwrap(),
375            // list.item(8).unwrap(),
376            // list.item(9).unwrap(),
377        ];
378        list.retain(|item| to_keep.contains(item));
379
380        // Check that we removed the correct items
381        assert_eq!(list.n_items(), 3);
382        assert_eq!(list.item(0).as_ref(), Some(&to_keep[0]));
383        assert_eq!(list.item(1).as_ref(), Some(&to_keep[1]));
384        assert_eq!(list.item(2).as_ref(), Some(&to_keep[2]));
385
386        assert_eq!(signal_count.get(), 4);
387    }
388}