gio/
list_model.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{cell::Cell, fmt, iter::FusedIterator, marker::PhantomData, rc::Rc};
4
5use glib::SignalHandlerId;
6
7use crate::{prelude::*, ListModel};
8
9mod sealed {
10    pub trait Sealed {}
11    impl<T: super::IsA<super::ListModel>> Sealed for T {}
12}
13
14pub trait ListModelExtManual: sealed::Sealed + IsA<ListModel> + Sized {
15    // rustdoc-stripper-ignore-next
16    /// Get an immutable snapshot of the container inside the `ListModel`.
17    /// Any modification done to the returned container `Vec` will not be
18    /// reflected on the `ListModel`.
19    fn snapshot(&self) -> Vec<glib::Object> {
20        let mut res = Vec::with_capacity(self.n_items() as usize);
21        for i in 0..self.n_items() {
22            res.push(self.item(i).unwrap())
23        }
24        res
25    }
26
27    // rustdoc-stripper-ignore-next
28    /// If `T::static_type().is_a(self.item_type())` then it returns an iterator over the `ListModel` elements,
29    /// else the types are not compatible and it panics.
30    ///
31    /// # Panics
32    ///
33    /// Panics if `T::static_type().is_a(self.item_type())` is not true.
34    fn iter<LT: IsA<glib::Object>>(&self) -> ListModelIter<LT> {
35        assert!(self.item_type().is_a(LT::static_type()));
36
37        let len = self.n_items();
38        let changed = Rc::new(Cell::new(false));
39
40        let changed_clone = changed.clone();
41        let signal_id = Some(self.connect_items_changed(move |_, pos, _, _| {
42            if pos < len {
43                changed_clone.set(true);
44            }
45        }));
46
47        ListModelIter {
48            ty: Default::default(),
49            i: 0,
50            reverse_pos: len,
51            model: self.upcast_ref(),
52            changed,
53            signal_id,
54        }
55    }
56}
57
58impl<T: IsA<ListModel>> ListModelExtManual for T {}
59
60#[derive(Debug, PartialEq, Eq)]
61pub struct ListModelMutatedDuringIter;
62
63impl std::error::Error for ListModelMutatedDuringIter {}
64
65impl fmt::Display for ListModelMutatedDuringIter {
66    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
67        fmt.write_str("the list model was mutated during iteration")
68    }
69}
70
71// rustdoc-stripper-ignore-next
72/// Iterator of `ListModel`'s items.
73/// This iterator will always give `n = initial_model.n_items()` items, even if the `ListModel`
74/// is mutated during iteration.
75/// If the internal `ListModel` gets mutated, the iterator
76/// will return `Some(Err(...))` for the remaining items.
77/// Mutations to the `ListModel` in position >= `initial_model.n_items()` are allowed.
78pub struct ListModelIter<'a, T: IsA<glib::Object>> {
79    ty: PhantomData<T>,
80    i: u32,
81    // it's > i when valid
82    reverse_pos: u32,
83    model: &'a ListModel,
84    changed: Rc<Cell<bool>>,
85    signal_id: Option<SignalHandlerId>,
86}
87impl<T: IsA<glib::Object>> Iterator for ListModelIter<'_, T> {
88    type Item = Result<T, ListModelMutatedDuringIter>;
89
90    fn next(&mut self) -> Option<Self::Item> {
91        if self.i >= self.reverse_pos {
92            return None;
93        }
94        let res = match self.changed.get() {
95            true => Err(ListModelMutatedDuringIter),
96            false => Ok(self.model.item(self.i).unwrap().downcast::<T>().unwrap()),
97        };
98        self.i += 1;
99        Some(res)
100    }
101
102    fn size_hint(&self) -> (usize, Option<usize>) {
103        let n: usize = (self.reverse_pos - self.i) as _;
104        (n, Some(n))
105    }
106
107    fn count(self) -> usize {
108        (self.reverse_pos - self.i) as usize
109    }
110
111    fn nth(&mut self, n: usize) -> Option<Self::Item> {
112        let (end, overflow) = (self.i as usize).overflowing_add(n);
113        if end >= self.reverse_pos as usize || overflow {
114            self.i = self.reverse_pos;
115            None
116        } else {
117            let end = end as u32;
118            self.i = end + 1;
119
120            let res = match self.changed.get() {
121                true => Err(ListModelMutatedDuringIter),
122                false => Ok(self.model.item(end).unwrap().downcast::<T>().unwrap()),
123            };
124            Some(res)
125        }
126    }
127
128    fn last(self) -> Option<Self::Item> {
129        if self.i == self.reverse_pos {
130            None
131        } else {
132            let res = match self.changed.get() {
133                true => Err(ListModelMutatedDuringIter),
134                false => Ok(self
135                    .model
136                    .item(self.reverse_pos - 1)
137                    .unwrap()
138                    .downcast::<T>()
139                    .unwrap()),
140            };
141            Some(res)
142        }
143    }
144}
145
146impl<T: IsA<glib::Object>> FusedIterator for ListModelIter<'_, T> {}
147
148impl<T: IsA<glib::Object>> ExactSizeIterator for ListModelIter<'_, T> {}
149
150impl<T: IsA<glib::Object>> DoubleEndedIterator for ListModelIter<'_, T> {
151    fn next_back(&mut self) -> Option<Self::Item> {
152        if self.reverse_pos == self.i {
153            return None;
154        }
155        self.reverse_pos -= 1;
156        let res = match self.changed.get() {
157            true => Err(ListModelMutatedDuringIter),
158            false => Ok(self
159                .model
160                .item(self.reverse_pos)
161                .unwrap()
162                .downcast::<T>()
163                .unwrap()),
164        };
165        Some(res)
166    }
167
168    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
169        let (end, overflow) = (self.reverse_pos as usize).overflowing_sub(n);
170        if end <= self.i as usize || overflow {
171            self.i = self.reverse_pos;
172            None
173        } else {
174            let end = end as u32;
175            self.reverse_pos = end - 1;
176
177            let res = match self.changed.get() {
178                true => Err(ListModelMutatedDuringIter),
179                false => Ok(self.model.item(end - 1).unwrap().downcast::<T>().unwrap()),
180            };
181            Some(res)
182        }
183    }
184}
185impl<T: IsA<glib::Object>> Drop for ListModelIter<'_, T> {
186    #[inline]
187    fn drop(&mut self) {
188        self.model.disconnect(self.signal_id.take().unwrap());
189    }
190}
191
192impl<'a> std::iter::IntoIterator for &'a ListModel {
193    type Item = Result<glib::Object, ListModelMutatedDuringIter>;
194    type IntoIter = ListModelIter<'a, glib::Object>;
195
196    fn into_iter(self) -> Self::IntoIter {
197        self.iter()
198    }
199}
200
201#[test]
202fn list_model_iter_ok() {
203    let list = crate::ListStore::new::<crate::Menu>();
204    let m1 = crate::Menu::new();
205    let m2 = crate::Menu::new();
206    let m3 = crate::Menu::new();
207    let m4 = crate::Menu::new();
208
209    list.append(&m1);
210    list.append(&m2);
211    list.append(&m3);
212
213    let mut iter = list.iter::<crate::Menu>();
214
215    assert_eq!(iter.len(), 3);
216    assert_eq!(iter.next(), Some(Ok(m1)));
217    // Appending items at the end of the `ListModel` can't affect the items
218    // we are iterating over.
219    list.append(&m4);
220    assert_eq!(iter.next_back(), Some(Ok(m3)));
221    assert_eq!(iter.len(), 1);
222    assert_eq!(iter.next_back(), Some(Ok(m2)));
223    assert_eq!(iter.next(), None);
224    assert_eq!(iter.next_back(), None);
225}
226
227#[test]
228fn list_model_iter_err() {
229    let list = crate::ListStore::new::<crate::Menu>();
230    let m1 = crate::Menu::new();
231    let m2 = crate::Menu::new();
232    let m3 = crate::Menu::new();
233    let m4 = crate::Menu::new();
234
235    list.append(&m1);
236    list.append(&m2);
237    list.append(&m3);
238    list.append(&m4);
239
240    let mut iter = list.iter::<crate::Menu>();
241
242    assert_eq!(iter.next_back(), Some(Ok(m4)));
243
244    // These two don't affect the iter
245    list.append(&m2);
246    list.append(&m2);
247
248    assert_eq!(iter.next(), Some(Ok(m1)));
249
250    // Does affect the iter
251    list.remove(2);
252    // Doesn't affect the iter, but the iter should stay tainted.
253    list.remove(4);
254    assert_eq!(iter.next(), Some(Err(ListModelMutatedDuringIter)));
255    assert_eq!(iter.next(), Some(Err(ListModelMutatedDuringIter)));
256    // Returned n items
257    assert_eq!(iter.next(), None);
258}
259
260#[test]
261fn list_model_iter_nth() {
262    let list = crate::ListStore::new::<crate::Menu>();
263    let m1 = crate::Menu::new();
264    let m2 = crate::Menu::new();
265    let m3 = crate::Menu::new();
266    let m4 = crate::Menu::new();
267    let m5 = crate::Menu::new();
268    let m6 = crate::Menu::new();
269
270    list.append(&m1);
271    list.append(&m2);
272    list.append(&m3);
273    list.append(&m4);
274    list.append(&m5);
275    list.append(&m6);
276
277    let mut iter = list.iter::<crate::Menu>();
278
279    assert_eq!(iter.len(), 6);
280    assert_eq!(iter.nth(1), Some(Ok(m2)));
281    assert_eq!(iter.len(), 4);
282    assert_eq!(iter.next(), Some(Ok(m3)));
283    assert_eq!(iter.nth_back(2), Some(Ok(m4)));
284    assert_eq!(iter.len(), 0);
285    assert_eq!(iter.next(), None);
286    assert_eq!(iter.next_back(), None);
287}
288
289#[test]
290fn list_model_iter_last() {
291    let list = crate::ListStore::new::<crate::Menu>();
292    let m1 = crate::Menu::new();
293    let m2 = crate::Menu::new();
294    let m3 = crate::Menu::new();
295
296    list.append(&m1);
297    list.append(&m2);
298    list.append(&m3);
299
300    let iter = list.iter::<crate::Menu>();
301
302    assert_eq!(iter.len(), 3);
303    assert_eq!(iter.last(), Some(Ok(m3)));
304}
305
306#[test]
307fn list_model_iter_count() {
308    let list = crate::ListStore::new::<crate::Menu>();
309    let m1 = crate::Menu::new();
310    let m2 = crate::Menu::new();
311    let m3 = crate::Menu::new();
312
313    list.append(&m1);
314    list.append(&m2);
315    list.append(&m3);
316
317    let iter = list.iter::<crate::Menu>();
318
319    assert_eq!(iter.len(), 3);
320    assert_eq!(iter.count(), 3);
321}