You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
5.6 KiB
183 lines
5.6 KiB
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, set = Self::set_model, explicit_notify, nullable)] |
|
model: BoundObject<gio::ListModel>, |
|
expressions: RefCell<Vec<gtk::Expression>>, |
|
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_or_else(glib::Object::static_type, |m| m.item_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)) |
|
} |
|
} |
|
|
|
impl ExpressionListModel { |
|
/// Set the underlying model. |
|
fn set_model(&self, model: Option<gio::ListModel>) { |
|
if self.model.obj() == model { |
|
return; |
|
} |
|
|
|
let obj = self.obj(); |
|
let removed = self.n_items(); |
|
|
|
self.model.disconnect_signals(); |
|
for watch in self.watches.take().iter().flatten() { |
|
watch.unwatch(); |
|
} |
|
|
|
let added = if let Some(model) = model { |
|
let items_changed_handler = model.connect_items_changed(clone!( |
|
#[strong] |
|
obj, |
|
move |_, pos, removed, added| { |
|
obj.imp().watch_items(pos, removed, added); |
|
obj.items_changed(pos, removed, added); |
|
} |
|
)); |
|
|
|
let added = model.n_items(); |
|
self.model.set(model, vec![items_changed_handler]); |
|
|
|
self.watch_items(0, 0, added); |
|
added |
|
} else { |
|
0 |
|
}; |
|
|
|
let obj = self.obj(); |
|
obj.items_changed(0, removed, added); |
|
obj.notify_model(); |
|
} |
|
|
|
/// Set the expressions to watch. |
|
pub(super) fn set_expressions(&self, expressions: Vec<gtk::Expression>) { |
|
for watch in self.watches.take().iter().flatten() { |
|
watch.unwatch(); |
|
} |
|
|
|
self.expressions.replace(expressions); |
|
self.watch_items(0, 0, self.n_items()); |
|
} |
|
|
|
/// Watch and unwatch items according to changes in the underlying |
|
/// model. |
|
fn watch_items(&self, pos: u32, removed: u32, added: u32) { |
|
let Some(model) = self.model.obj() else { |
|
return; |
|
}; |
|
|
|
let expressions = self.expressions.borrow().clone(); |
|
if expressions.is_empty() { |
|
return; |
|
} |
|
|
|
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 obj = self.obj(); |
|
let mut item_watches = Vec::with_capacity(expressions.len()); |
|
for expression in &expressions { |
|
item_watches.push(expression.watch( |
|
Some(&item), |
|
clone!( |
|
#[strong] |
|
obj, |
|
#[weak] |
|
item, |
|
move || { |
|
obj.imp().item_expr_changed(&item); |
|
} |
|
), |
|
)); |
|
} |
|
|
|
new_watches.push(item_watches); |
|
} |
|
|
|
let mut watches = self.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.obj() else { |
|
return; |
|
}; |
|
|
|
for (pos, obj) in model.snapshot().iter().enumerate() { |
|
if obj == item { |
|
self.obj().items_changed(pos as u32, 1, 1); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
glib::wrapper! { |
|
/// A list model that signals an item as changed when the expression's value changes. |
|
pub struct ExpressionListModel(ObjectSubclass<imp::ExpressionListModel>) |
|
@implements gio::ListModel; |
|
} |
|
|
|
impl ExpressionListModel { |
|
pub fn new() -> Self { |
|
glib::Object::new() |
|
} |
|
|
|
/// Set the expressions to watch. |
|
pub(crate) fn set_expressions(&self, expressions: Vec<gtk::Expression>) { |
|
self.imp().set_expressions(expressions); |
|
} |
|
} |
|
|
|
impl Default for ExpressionListModel { |
|
fn default() -> Self { |
|
Self::new() |
|
} |
|
}
|
|
|