Skip to content

Commit 80d6fd2

Browse files
authored
Merge pull request #1133 from ranfdev/master
Add retain method to ListStore
2 parents 3238e82 + f6fd43b commit 80d6fd2

File tree

1 file changed

+97
-1
lines changed

1 file changed

+97
-1
lines changed

gio/src/list_store.rs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Take a look at the license at the top of the repository in the LICENSE file.
22

3-
use std::cmp::Ordering;
3+
use std::{cell::Cell, cmp::Ordering, rc::Rc};
44

55
use glib::{prelude::*, translate::*, Object};
66

@@ -76,6 +76,60 @@ impl ListStore {
7676
self.splice(self.n_items(), 0, additions)
7777
}
7878

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+
79133
#[cfg(feature = "v2_74")]
80134
#[cfg_attr(docsrs, doc(cfg(feature = "v2_74")))]
81135
#[doc(alias = "g_list_store_find_with_equal_func_full")]
@@ -223,4 +277,46 @@ mod tests {
223277
let res = list.find_with_equal_func(|item| item == &item1);
224278
assert_eq!(res, Some(1));
225279
}
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+
}
226322
}

0 commit comments

Comments
 (0)