1use super::{
4 Filter, OrdFn, RelmSelectionExt, TypedListItem, get_mut_value, get_value,
5 iterator::TypedIterator,
6};
7use gtk::{
8 gio, glib,
9 prelude::{Cast, CastNone, FilterExt, IsA, ListItemExt, ListModelExt, ObjectExt},
10};
11use std::{
12 any::Any,
13 cmp::Ordering,
14 collections::HashMap,
15 fmt::{Debug, Display},
16 marker::PhantomData,
17};
18
19pub trait RelmColumn: Any {
21 type Root: IsA<gtk::Widget>;
23
24 type Widgets;
26
27 type Item: Any;
29
30 const COLUMN_NAME: &'static str;
32 const ENABLE_RESIZE: bool = false;
34 const ENABLE_EXPAND: bool = false;
36
37 #[must_use]
39 fn header_title() -> String {
40 String::from(Self::COLUMN_NAME)
41 }
42
43 fn setup(list_item: >k::ListItem) -> (Self::Root, Self::Widgets);
45
46 fn bind(_item: &mut Self::Item, _widgets: &mut Self::Widgets, _root: &mut Self::Root) {}
48
49 fn unbind(_item: &mut Self::Item, _widgets: &mut Self::Widgets, _root: &mut Self::Root) {}
51
52 fn teardown(_list_item: >k::ListItem) {}
54
55 #[must_use]
57 fn sort_fn() -> OrdFn<Self::Item> {
58 None
59 }
60}
61
62pub trait LabelColumn: 'static {
64 type Item: Any;
66 type Value: PartialOrd + Display;
68
69 const COLUMN_NAME: &'static str;
71 const ENABLE_SORT: bool;
73 const ENABLE_RESIZE: bool = false;
75 const ENABLE_EXPAND: bool = false;
77
78 fn get_cell_value(item: &Self::Item) -> Self::Value;
80 fn format_cell_value(value: &Self::Value) -> String {
82 value.to_string()
83 }
84}
85
86impl<C> RelmColumn for C
87where
88 C: LabelColumn,
89{
90 type Root = gtk::Label;
91 type Widgets = ();
92 type Item = C::Item;
93
94 const COLUMN_NAME: &'static str = C::COLUMN_NAME;
95 const ENABLE_RESIZE: bool = C::ENABLE_RESIZE;
96 const ENABLE_EXPAND: bool = C::ENABLE_EXPAND;
97
98 fn setup(_: >k::ListItem) -> (Self::Root, Self::Widgets) {
99 (gtk::Label::new(None), ())
100 }
101
102 fn bind(item: &mut Self::Item, _: &mut Self::Widgets, label: &mut Self::Root) {
103 label.set_label(&C::format_cell_value(&C::get_cell_value(item)));
104 }
105
106 fn sort_fn() -> OrdFn<Self::Item> {
107 if C::ENABLE_SORT {
108 Some(Box::new(|a, b| {
109 let a = C::get_cell_value(a);
110 let b = C::get_cell_value(b);
111 a.partial_cmp(&b).unwrap_or(Ordering::Equal)
112 }))
113 } else {
114 None
115 }
116 }
117}
118
119pub struct TypedColumnView<T, S> {
126 pub view: gtk::ColumnView,
128 pub selection_model: S,
130 columns: HashMap<&'static str, gtk::ColumnViewColumn>,
131 store: gio::ListStore,
132 filters: Vec<Filter>,
133 active_model: gio::ListModel,
134 base_model: gio::ListModel,
135 _ty: PhantomData<*const T>,
136}
137
138impl<T, S> Debug for TypedColumnView<T, S>
139where
140 T: Debug,
141 S: Debug,
142{
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 f.debug_struct("TypedColumnView")
145 .field("store", &self.store)
146 .field("view", &self.view)
147 .field("filters", &"<Vec<gtk::Filter>>")
148 .field("active_model", &self.active_model)
149 .field("base_model", &self.base_model)
150 .field("selection_model", &self.selection_model)
151 .finish()
152 }
153}
154
155impl<T, S> Default for TypedColumnView<T, S>
156where
157 T: Any,
158 S: RelmSelectionExt,
159{
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165impl<T, S> TypedColumnView<T, S>
166where
167 T: Any,
168 S: RelmSelectionExt,
169{
170 #[must_use]
172 pub fn new() -> Self {
173 let store = gio::ListStore::new::<glib::BoxedAnyObject>();
174
175 let model: gio::ListModel = store.clone().upcast();
176
177 let b = gtk::SortListModel::new(Some(model), None::<gtk::Sorter>);
178
179 let base_model: gio::ListModel = b.clone().upcast();
180
181 let selection_model = S::new_model(base_model.clone());
182 let view = gtk::ColumnView::new(Some(selection_model.clone()));
183 b.set_sorter(view.sorter().as_ref());
184
185 Self {
186 store,
187 view,
188 columns: HashMap::new(),
189 filters: Vec::new(),
190 active_model: base_model.clone(),
191 base_model,
192 _ty: PhantomData,
193 selection_model,
194 }
195 }
196
197 pub fn append_column<C>(&mut self)
199 where
200 C: RelmColumn<Item = T>,
201 {
202 let factory = gtk::SignalListItemFactory::new();
203 factory.connect_setup(move |_, list_item| {
204 let list_item = list_item
205 .downcast_ref::<gtk::ListItem>()
206 .expect("Needs to be ListItem");
207
208 let (root, widgets) = C::setup(list_item);
209 unsafe { root.set_data("widgets", widgets) };
210 list_item.set_child(Some(&root));
211 });
212
213 #[inline]
214 fn modify_widgets<T, C>(
215 list_item: &glib::Object,
216 f: impl FnOnce(&mut T, &mut C::Widgets, &mut C::Root),
217 ) where
218 T: Any,
219 C: RelmColumn<Item = T>,
220 {
221 let list_item = list_item
222 .downcast_ref::<gtk::ListItem>()
223 .expect("Needs to be ListItem");
224
225 let widget = list_item.child();
226
227 let obj = list_item.item().unwrap();
228 let mut obj = get_mut_value::<T>(&obj);
229
230 let mut root = widget.and_downcast::<C::Root>().unwrap();
231
232 let mut widgets = unsafe { root.steal_data("widgets") }.unwrap();
233 (f)(&mut *obj, &mut widgets, &mut root);
234 unsafe { root.set_data("widgets", widgets) };
235 }
236
237 factory.connect_bind(move |_, list_item| {
238 modify_widgets::<T, C>(list_item.upcast_ref(), |obj, widgets, root| {
239 C::bind(obj, widgets, root);
240 });
241 });
242
243 factory.connect_unbind(move |_, list_item| {
244 modify_widgets::<T, C>(list_item.upcast_ref(), |obj, widgets, root| {
245 C::unbind(obj, widgets, root);
246 });
247 });
248
249 factory.connect_teardown(move |_, list_item| {
250 let list_item = list_item
251 .downcast_ref::<gtk::ListItem>()
252 .expect("Needs to be ListItem");
253
254 C::teardown(list_item);
255 });
256
257 let sort_fn = C::sort_fn();
258
259 let c = gtk::ColumnViewColumn::new(Some(&C::header_title()), Some(factory));
260 c.set_resizable(C::ENABLE_RESIZE);
261 c.set_expand(C::ENABLE_EXPAND);
262
263 if let Some(sort_fn) = sort_fn {
264 c.set_sorter(Some(>k::CustomSorter::new(move |first, second| {
265 let first = get_value::<T>(first);
266 let second = get_value::<T>(second);
267
268 sort_fn(&first, &second).into()
269 })))
270 }
271
272 self.view.append_column(&c);
273 self.columns.insert(C::COLUMN_NAME, c);
274 }
275
276 pub fn add_filter<F: Fn(&T) -> bool + 'static>(&mut self, f: F) {
281 let filter = gtk::CustomFilter::new(move |obj| {
282 let value = get_value::<T>(obj);
283 f(&value)
284 });
285 let filter_model =
286 gtk::FilterListModel::new(Some(self.active_model.clone()), Some(filter.clone()));
287 self.active_model = filter_model.clone().upcast();
288 self.selection_model.set_list_model(&self.active_model);
289 self.filters.push(Filter {
290 filter,
291 model: filter_model,
292 });
293 }
294
295 pub fn get_columns(&self) -> &HashMap<&'static str, gtk::ColumnViewColumn> {
297 &self.columns
298 }
299
300 pub fn filters_len(&self) -> usize {
302 self.filters.len()
303 }
304
305 pub fn set_filter_status(&mut self, idx: usize, active: bool) -> bool {
307 if let Some(filter) = self.filters.get(idx) {
308 if active {
309 filter.model.set_filter(Some(&filter.filter));
310 } else {
311 filter.model.set_filter(None::<>k::CustomFilter>);
312 }
313 true
314 } else {
315 false
316 }
317 }
318
319 pub fn notify_filter_changed(&self, idx: usize) -> bool {
324 if let Some(filter) = self.filters.get(idx) {
325 filter.filter.changed(gtk::FilterChange::Different);
326 true
327 } else {
328 false
329 }
330 }
331
332 pub fn pop_filter(&mut self) {
334 let filter = self.filters.pop();
335 if let Some(filter) = filter {
336 self.active_model = filter.model.model().unwrap();
337 self.selection_model.set_list_model(&self.active_model);
338 }
339 }
340
341 pub fn clear_filters(&mut self) {
343 self.filters.clear();
344 self.active_model = self.base_model.clone();
345 self.selection_model.set_list_model(&self.active_model);
346 }
347
348 pub fn append(&mut self, value: T) {
350 self.store.append(&glib::BoxedAnyObject::new(value));
351 }
352
353 pub fn extend_from_iter<I: IntoIterator<Item = T>>(&mut self, init: I) {
355 let objects: Vec<glib::BoxedAnyObject> =
356 init.into_iter().map(glib::BoxedAnyObject::new).collect();
357 self.store.extend_from_slice(&objects);
358 }
359
360 #[cfg(feature = "gnome_43")]
361 #[cfg_attr(docsrs, doc(cfg(feature = "gnome_43")))]
362 pub fn find<F: FnMut(&T) -> bool>(&self, mut equal_func: F) -> Option<u32> {
364 self.store.find_with_equal_func(move |obj| {
365 let value = get_value::<T>(obj);
366 equal_func(&value)
367 })
368 }
369
370 pub fn is_empty(&self) -> bool {
372 self.len() == 0
373 }
374
375 pub fn len(&self) -> u32 {
377 self.store.n_items()
378 }
379
380 pub fn get(&self, position: u32) -> Option<TypedListItem<T>> {
384 if let Some(obj) = self.store.item(position) {
385 let wrapper = obj.downcast::<glib::BoxedAnyObject>().unwrap();
386 Some(TypedListItem::new(wrapper))
387 } else {
388 None
389 }
390 }
391
392 pub fn get_visible(&self, position: u32) -> Option<TypedListItem<T>> {
397 if let Some(obj) = self.active_model.item(position) {
398 let wrapper = obj.downcast::<glib::BoxedAnyObject>().unwrap();
399 Some(TypedListItem::new(wrapper))
400 } else {
401 None
402 }
403 }
404
405 pub fn insert(&mut self, position: u32, value: T) {
407 self.store
408 .insert(position, &glib::BoxedAnyObject::new(value));
409 }
410
411 pub fn insert_sorted<F>(&self, value: T, mut compare_func: F) -> u32
414 where
415 F: FnMut(&T, &T) -> Ordering,
416 {
417 let item = glib::BoxedAnyObject::new(value);
418
419 let compare = move |first: &glib::Object, second: &glib::Object| -> Ordering {
420 let first = get_value::<T>(first);
421 let second = get_value::<T>(second);
422 compare_func(&first, &second)
423 };
424
425 self.store.insert_sorted(&item, compare)
426 }
427
428 pub fn remove(&mut self, position: u32) {
430 self.store.remove(position);
431 }
432
433 pub fn clear(&mut self) {
435 self.store.remove_all();
436 }
437
438 pub fn iter(&self) -> TypedIterator<'_, TypedColumnView<T, S>> {
440 TypedIterator {
441 list: self,
442 index: 0,
443 }
444 }
445}