use super::{
Filter, OrdFn, RelmSelectionExt, TypedListItem, get_mut_value, get_value,
iterator::TypedIterator,
};
use gtk::{
gio, glib,
prelude::{Cast, CastNone, FilterExt, IsA, ListItemExt, ListModelExt, ObjectExt},
};
use std::{any::Any, cmp::Ordering, marker::PhantomData};
pub trait RelmGridItem: Any {
type Root: IsA<gtk::Widget>;
type Widgets;
fn setup(grid_item: >k::ListItem) -> (Self::Root, Self::Widgets);
fn bind(&mut self, _widgets: &mut Self::Widgets, _root: &mut Self::Root) {}
fn unbind(&mut self, _widgets: &mut Self::Widgets, _root: &mut Self::Root) {}
fn teardown(_grid_item: >k::ListItem) {}
}
pub struct TypedGridView<T, S> {
pub view: gtk::GridView,
pub selection_model: S,
store: gio::ListStore,
filters: Vec<Filter>,
active_model: gio::ListModel,
base_model: gio::ListModel,
_ty: PhantomData<*const T>,
}
impl<T: std::fmt::Debug, S: std::fmt::Debug> std::fmt::Debug for TypedGridView<T, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TypedGridView")
.field("store", &self.store)
.field("view", &self.view)
.field("filters", &"<Vec<gtk::Filter>>")
.field("active_model", &self.active_model)
.field("base_model", &self.base_model)
.field("selection_model", &self.selection_model)
.finish()
}
}
impl<T, S> TypedGridView<T, S>
where
T: RelmGridItem + Ord,
S: RelmSelectionExt,
{
#[must_use]
pub fn with_sorting() -> Self {
Self::init(Some(Box::new(T::cmp)))
}
}
impl<T, S> Default for TypedGridView<T, S>
where
T: RelmGridItem,
S: RelmSelectionExt,
{
fn default() -> Self {
Self::new()
}
}
impl<T, S> TypedGridView<T, S>
where
T: RelmGridItem,
S: RelmSelectionExt,
{
#[must_use]
pub fn new() -> Self {
Self::init(None)
}
fn init(sort_fn: OrdFn<T>) -> Self {
let store = gio::ListStore::new::<glib::BoxedAnyObject>();
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
let (root, widgets) = T::setup(list_item);
unsafe { root.set_data("widgets", widgets) };
list_item.set_child(Some(&root));
});
factory.connect_bind(move |_, list_item| {
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
let widget = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem")
.child();
let obj = list_item.item().unwrap();
let mut obj = get_mut_value::<T>(&obj);
let mut root = widget.and_downcast::<T::Root>().unwrap();
let mut widgets = unsafe { root.steal_data("widgets") }.unwrap();
obj.bind(&mut widgets, &mut root);
unsafe { root.set_data("widgets", widgets) };
});
factory.connect_unbind(move |_, list_item| {
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
let widget = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem")
.child();
let obj = list_item.item().unwrap();
let mut obj = get_mut_value::<T>(&obj);
let mut root = widget.and_downcast::<T::Root>().unwrap();
let mut widgets = unsafe { root.steal_data("widgets") }.unwrap();
obj.unbind(&mut widgets, &mut root);
unsafe { root.set_data("widgets", widgets) };
});
factory.connect_teardown(move |_, list_item| {
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
T::teardown(list_item);
});
let model: gio::ListModel = store.clone().upcast();
let base_model = if let Some(sort_fn) = sort_fn {
let sorter = gtk::CustomSorter::new(move |first, second| {
let first = get_value::<T>(first);
let second = get_value::<T>(second);
match sort_fn(&first, &second) {
Ordering::Less => gtk::Ordering::Smaller,
Ordering::Equal => gtk::Ordering::Equal,
Ordering::Greater => gtk::Ordering::Larger,
}
});
gtk::SortListModel::new(Some(model), Some(sorter)).upcast()
} else {
model
};
let selection_model = S::new_model(base_model.clone());
let view = gtk::GridView::new(Some(selection_model.clone()), Some(factory));
Self {
store,
view,
filters: Vec::new(),
active_model: base_model.clone(),
base_model,
_ty: PhantomData,
selection_model,
}
}
pub fn add_filter<F: Fn(&T) -> bool + 'static>(&mut self, f: F) {
let filter = gtk::CustomFilter::new(move |obj| {
let value = get_value::<T>(obj);
f(&value)
});
let filter_model =
gtk::FilterListModel::new(Some(self.active_model.clone()), Some(filter.clone()));
self.active_model = filter_model.clone().upcast();
self.selection_model.set_list_model(&self.active_model);
self.filters.push(Filter {
filter,
model: filter_model,
});
}
pub fn filters_len(&self) -> usize {
self.filters.len()
}
pub fn set_filter_status(&mut self, idx: usize, active: bool) -> bool {
if let Some(filter) = self.filters.get(idx) {
if active {
filter.model.set_filter(Some(&filter.filter));
} else {
filter.model.set_filter(None::<>k::CustomFilter>);
}
true
} else {
false
}
}
pub fn notify_filter_changed(&self, idx: usize) -> bool {
if let Some(filter) = self.filters.get(idx) {
filter.filter.changed(gtk::FilterChange::Different);
true
} else {
false
}
}
pub fn pop_filter(&mut self) {
let filter = self.filters.pop();
if let Some(filter) = filter {
self.active_model = filter.model.model().unwrap();
self.selection_model.set_list_model(&self.active_model);
}
}
pub fn clear_filters(&mut self) {
self.filters.clear();
self.active_model = self.base_model.clone();
self.selection_model.set_list_model(&self.active_model);
}
pub fn append(&mut self, value: T) {
self.store.append(&glib::BoxedAnyObject::new(value));
}
pub fn extend_from_iter<I: IntoIterator<Item = T>>(&mut self, init: I) {
let objects: Vec<glib::BoxedAnyObject> =
init.into_iter().map(glib::BoxedAnyObject::new).collect();
self.store.extend_from_slice(&objects);
}
#[cfg(feature = "gnome_43")]
#[cfg_attr(docsrs, doc(cfg(feature = "gnome_43")))]
pub fn find<F: FnMut(&T) -> bool>(&self, mut equal_func: F) -> Option<u32> {
self.store.find_with_equal_func(move |obj| {
let value = get_value::<T>(obj);
equal_func(&value)
})
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> u32 {
self.store.n_items()
}
pub fn get(&self, position: u32) -> Option<TypedListItem<T>> {
if let Some(obj) = self.store.item(position) {
let wrapper = obj.downcast::<glib::BoxedAnyObject>().unwrap();
Some(TypedListItem::new(wrapper))
} else {
None
}
}
pub fn get_visible(&self, position: u32) -> Option<TypedListItem<T>> {
if let Some(obj) = self.active_model.item(position) {
let wrapper = obj.downcast::<glib::BoxedAnyObject>().unwrap();
Some(TypedListItem::new(wrapper))
} else {
None
}
}
pub fn insert(&mut self, position: u32, value: T) {
self.store
.insert(position, &glib::BoxedAnyObject::new(value));
}
pub fn insert_sorted<F: FnMut(&T, &T) -> Ordering>(
&self,
value: T,
mut compare_func: F,
) -> u32 {
let item = glib::BoxedAnyObject::new(value);
let compare = move |first: &glib::Object, second: &glib::Object| -> Ordering {
let first = get_value::<T>(first);
let second = get_value::<T>(second);
compare_func(&first, &second)
};
self.store.insert_sorted(&item, compare)
}
pub fn remove(&mut self, position: u32) {
self.store.remove(position);
}
pub fn clear(&mut self) {
self.store.remove_all();
}
pub fn iter(&self) -> TypedIterator<'_, TypedGridView<T, S>> {
TypedIterator {
list: self,
index: 0,
}
}
pub fn sort<F: FnMut(&T, &T) -> Ordering>(&self, mut compare_func: F) {
let compare = move |first: &glib::Object, second: &glib::Object| -> Ordering {
let first = get_value::<T>(first);
let second = get_value::<T>(second);
compare_func(&first, &second)
};
self.store.sort(compare);
}
}