|
1 | 1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | 2 |
|
3 | | -use std::cmp::Ordering; |
| 3 | +use std::{cell::Cell, cmp::Ordering, rc::Rc}; |
4 | 4 |
|
5 | 5 | use glib::{prelude::*, translate::*, Object}; |
6 | 6 |
|
@@ -76,6 +76,60 @@ impl ListStore { |
76 | 76 | self.splice(self.n_items(), 0, additions) |
77 | 77 | } |
78 | 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 | + |
79 | 133 | #[cfg(feature = "v2_74")] |
80 | 134 | #[cfg_attr(docsrs, doc(cfg(feature = "v2_74")))] |
81 | 135 | #[doc(alias = "g_list_store_find_with_equal_func_full")] |
@@ -223,4 +277,46 @@ mod tests { |
223 | 277 | let res = list.find_with_equal_func(|item| item == &item1); |
224 | 278 | assert_eq!(res, Some(1)); |
225 | 279 | } |
| 280 | + |
| 281 | + #[test] |
| 282 | + fn retain() { |
| 283 | + let list = { |
| 284 | + let list = ListStore::new::<ListStore>(); |
| 285 | + for _ in 0..10 { |
| 286 | + list.append(&ListStore::new::<ListStore>()); |
| 287 | + } |
| 288 | + list |
| 289 | + }; |
| 290 | + |
| 291 | + use std::cell::Cell; |
| 292 | + use std::rc::Rc; |
| 293 | + |
| 294 | + let signal_count = Rc::new(Cell::new(0)); |
| 295 | + let signal_count_clone = signal_count.clone(); |
| 296 | + list.connect_items_changed(move |_, _, _, _| { |
| 297 | + signal_count_clone.set(signal_count_clone.get() + 1); |
| 298 | + }); |
| 299 | + |
| 300 | + let to_keep = [ |
| 301 | + // list.item(0).unwrap(), |
| 302 | + list.item(1).unwrap(), |
| 303 | + // list.item(2).unwrap(), |
| 304 | + list.item(3).unwrap(), |
| 305 | + // list.item(4).unwrap(), |
| 306 | + // list.item(5).unwrap(), |
| 307 | + // list.item(6).unwrap(), |
| 308 | + list.item(7).unwrap(), |
| 309 | + // list.item(8).unwrap(), |
| 310 | + // list.item(9).unwrap(), |
| 311 | + ]; |
| 312 | + list.retain(|item| to_keep.contains(item)); |
| 313 | + |
| 314 | + // Check that we removed the correct items |
| 315 | + assert_eq!(list.n_items(), 3); |
| 316 | + assert_eq!(list.item(0).as_ref(), Some(&to_keep[0])); |
| 317 | + assert_eq!(list.item(1).as_ref(), Some(&to_keep[1])); |
| 318 | + assert_eq!(list.item(2).as_ref(), Some(&to_keep[2])); |
| 319 | + |
| 320 | + assert_eq!(signal_count.get(), 4); |
| 321 | + } |
226 | 322 | } |
0 commit comments