use super::{get_mut_value, get_value, Filter, OrdFn, RelmSelectionExt, TypedListItem};
use gtk::{
gio, glib,
prelude::{Cast, CastNone, IsA, ListItemExt, ListModelExt, ObjectExt},
};
use std::{
any::Any,
cmp::Ordering,
collections::HashMap,
fmt::{Debug, Display},
marker::PhantomData,
};
pub trait RelmColumn: Any {
type Root: IsA<gtk::Widget>;
type Widgets;
type Item: Any;
const COLUMN_NAME: &'static str;
const ENABLE_RESIZE: bool = false;
const ENABLE_EXPAND: bool = false;
fn setup(list_item: >k::ListItem) -> (Self::Root, Self::Widgets);
fn bind(_item: &mut Self::Item, _widgets: &mut Self::Widgets, _root: &mut Self::Root) {}
fn unbind(_item: &mut Self::Item, _widgets: &mut Self::Widgets, _root: &mut Self::Root) {}
fn teardown(_list_item: >k::ListItem) {}
#[must_use]
fn sort_fn() -> OrdFn<Self::Item> {
None
}
}
pub trait LabelColumn: 'static {
type Item: Any;
type Value: PartialOrd + Display;
const COLUMN_NAME: &'static str;
const ENABLE_SORT: bool;
const ENABLE_RESIZE: bool = false;
const ENABLE_EXPAND: bool = false;
fn get_cell_value(item: &Self::Item) -> Self::Value;
fn format_cell_value(value: &Self::Value) -> String {
value.to_string()
}
}
impl<C> RelmColumn for C
where
C: LabelColumn,
{
type Root = gtk::Label;
type Widgets = ();
type Item = C::Item;
const COLUMN_NAME: &'static str = C::COLUMN_NAME;
const ENABLE_RESIZE: bool = C::ENABLE_RESIZE;
const ENABLE_EXPAND: bool = C::ENABLE_EXPAND;
fn setup(_: >k::ListItem) -> (Self::Root, Self::Widgets) {
(gtk::Label::new(None), ())
}
fn bind(item: &mut Self::Item, _: &mut Self::Widgets, label: &mut Self::Root) {
label.set_label(&C::format_cell_value(&C::get_cell_value(item)));
}
fn sort_fn() -> OrdFn<Self::Item> {
if C::ENABLE_SORT {
Some(Box::new(|a, b| {
let a = C::get_cell_value(a);
let b = C::get_cell_value(b);
a.partial_cmp(&b).unwrap_or(Ordering::Equal)
}))
} else {
None
}
}
}
pub struct TypedColumnView<T, S> {
pub view: gtk::ColumnView,
pub selection_model: S,
columns: HashMap<&'static str, gtk::ColumnViewColumn>,
store: gio::ListStore,
filters: Vec<Filter>,
active_model: gio::ListModel,
base_model: gio::ListModel,
_ty: PhantomData<*const T>,
}
impl<T, S> Debug for TypedColumnView<T, S>
where
T: Debug,
S: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TypedColumnView")
.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> Default for TypedColumnView<T, S>
where
T: Any,
S: RelmSelectionExt,
{
fn default() -> Self {
Self::new()
}
}
impl<T, S> TypedColumnView<T, S>
where
T: Any,
S: RelmSelectionExt,
{
#[must_use]
pub fn new() -> Self {
let store = gio::ListStore::new::<glib::BoxedAnyObject>();
let model: gio::ListModel = store.clone().upcast();
let b = gtk::SortListModel::new(Some(model), None::<gtk::Sorter>);
let base_model: gio::ListModel = b.clone().upcast();
let selection_model = S::new_model(base_model.clone());
let view = gtk::ColumnView::new(Some(selection_model.clone()));
b.set_sorter(view.sorter().as_ref());
Self {
store,
view,
columns: HashMap::new(),
filters: Vec::new(),
active_model: base_model.clone(),
base_model,
_ty: PhantomData,
selection_model,
}
}
pub fn append_column<C>(&mut self)
where
C: RelmColumn<Item = T>,
{
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) = C::setup(list_item);
unsafe { root.set_data("widgets", widgets) };
list_item.set_child(Some(&root));
});
#[inline]
fn modify_widgets<T, C>(
list_item: &glib::Object,
f: impl FnOnce(&mut T, &mut C::Widgets, &mut C::Root),
) where
T: Any,
C: RelmColumn<Item = T>,
{
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
let widget = list_item.child();
let obj = list_item.item().unwrap();
let mut obj = get_mut_value::<T>(&obj);
let mut root = widget.and_downcast::<C::Root>().unwrap();
let mut widgets = unsafe { root.steal_data("widgets") }.unwrap();
(f)(&mut *obj, &mut widgets, &mut root);
unsafe { root.set_data("widgets", widgets) };
}
factory.connect_bind(move |_, list_item| {
modify_widgets::<T, C>(list_item.upcast_ref(), |obj, widgets, root| {
C::bind(obj, widgets, root);
});
});
factory.connect_unbind(move |_, list_item| {
modify_widgets::<T, C>(list_item.upcast_ref(), |obj, widgets, root| {
C::unbind(obj, widgets, root);
});
});
factory.connect_teardown(move |_, list_item| {
let list_item = list_item
.downcast_ref::<gtk::ListItem>()
.expect("Needs to be ListItem");
C::teardown(list_item);
});
let sort_fn = C::sort_fn();
let c = gtk::ColumnViewColumn::new(Some(C::COLUMN_NAME), Some(factory));
c.set_resizable(C::ENABLE_RESIZE);
c.set_expand(C::ENABLE_EXPAND);
if let Some(sort_fn) = sort_fn {
c.set_sorter(Some(>k::CustomSorter::new(move |first, second| {
let first = get_value::<T>(first);
let second = get_value::<T>(second);
sort_fn(&first, &second).into()
})))
}
self.view.append_column(&c);
self.columns.insert(C::COLUMN_NAME, c);
}
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 get_columns(&self) -> &HashMap<&'static str, gtk::ColumnViewColumn> {
&self.columns
}
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 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>(&self, value: T, mut compare_func: F) -> u32
where
F: FnMut(&T, &T) -> Ordering,
{
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();
}
}