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