use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
use tracing::error;
use crate::utils::BoundObject;
mod imp {
use std::cell::RefCell;
use super::*;
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::ExpressionListModel)]
pub struct ExpressionListModel {
#[property(get)]
pub model: BoundObject<gio::ListModel>,
pub expressions: RefCell<Vec<gtk::Expression>>,
pub watches: RefCell<Vec<Vec<gtk::ExpressionWatch>>>,
}
#[glib::object_subclass]
impl ObjectSubclass for ExpressionListModel {
const NAME: &'static str = "ExpressionListModel";
type Type = super::ExpressionListModel;
type Interfaces = (gio::ListModel,);
}
#[glib::derived_properties]
impl ObjectImpl for ExpressionListModel {
fn dispose(&self) {
for watch in self.watches.take().iter().flatten() {
watch.unwatch()
}
}
}
impl ListModelImpl for ExpressionListModel {
fn item_type(&self) -> glib::Type {
self.model
.obj()
.map(|m| m.item_type())
.unwrap_or_else(glib::Object::static_type)
}
fn n_items(&self) -> u32 {
self.model.obj().map(|m| m.n_items()).unwrap_or_default()
}
fn item(&self, position: u32) -> Option<glib::Object> {
self.model.obj().and_then(|m| m.item(position))
}
}
}
glib::wrapper! {
pub struct ExpressionListModel(ObjectSubclass<imp::ExpressionListModel>)
@implements gio::ListModel;
}
impl ExpressionListModel {
pub fn new() -> Self {
glib::Object::new()
}
pub fn set_model(&self, model: Option<impl IsA<gio::ListModel>>) {
let imp = self.imp();
let model = model.and_upcast();
let removed = self.n_items();
imp.model.disconnect_signals();
for watch in imp.watches.take().iter().flatten() {
watch.unwatch();
}
let added = if let Some(model) = model {
let items_changed_handler = model.connect_items_changed(clone!(
#[strong(rename_to = obj)]
self,
move |_, pos, removed, added| {
obj.watch_items(pos, removed, added);
obj.items_changed(pos, removed, added);
}
));
let added = model.n_items();
imp.model.set(model, vec![items_changed_handler]);
self.watch_items(0, 0, added);
added
} else {
0
};
self.items_changed(0, removed, added);
self.notify_model();
}
pub fn expressions(&self) -> Vec<gtk::Expression> {
self.imp().expressions.borrow().clone()
}
pub fn set_expressions(&self, expressions: Vec<gtk::Expression>) {
let imp = self.imp();
for watch in imp.watches.take().iter().flatten() {
watch.unwatch();
}
imp.expressions.replace(expressions);
self.watch_items(0, 0, self.n_items());
}
fn watch_items(&self, pos: u32, removed: u32, added: u32) {
let Some(model) = self.model() else {
return;
};
let expressions = self.expressions();
if expressions.is_empty() {
return;
}
let imp = self.imp();
let mut new_watches = Vec::with_capacity(added as usize);
for item_pos in pos..pos + added {
let Some(item) = model.item(item_pos) else {
error!("Out of bounds item");
break;
};
let mut item_watches = Vec::with_capacity(expressions.len());
for expression in &expressions {
item_watches.push(expression.watch(
Some(&item),
clone!(
#[strong(rename_to = obj)]
self,
#[weak]
item,
move || {
obj.item_expr_changed(&item);
}
),
));
}
new_watches.push(item_watches);
}
let mut watches = imp.watches.borrow_mut();
let removed_range = (pos as usize)..((pos + removed) as usize);
for watch in watches.splice(removed_range, new_watches).flatten() {
watch.unwatch();
}
}
fn item_expr_changed(&self, item: &glib::Object) {
let Some(model) = self.model() else {
return;
};
for (pos, obj) in model.snapshot().iter().enumerate() {
if obj == item {
self.items_changed(pos as u32, 1, 1);
break;
}
}
}
}
impl Default for ExpressionListModel {
fn default() -> Self {
Self::new()
}
}