|
|
|
|
@ -8,20 +8,20 @@ mod imp {
|
|
|
|
|
use super::*; |
|
|
|
|
|
|
|
|
|
#[derive(Debug, glib::Properties)] |
|
|
|
|
#[properties(wrapper_type = super::Selection)] |
|
|
|
|
pub struct Selection { |
|
|
|
|
#[properties(wrapper_type = super::FixedSelection)] |
|
|
|
|
pub struct FixedSelection { |
|
|
|
|
/// The underlying model.
|
|
|
|
|
#[property(get, set = Self::set_model, explicit_notify, nullable)] |
|
|
|
|
pub model: BoundObject<gio::ListModel>, |
|
|
|
|
model: BoundObject<gio::ListModel>, |
|
|
|
|
/// The position of the selected item.
|
|
|
|
|
#[property(get, set = Self::set_selected, explicit_notify, default = gtk::INVALID_LIST_POSITION)] |
|
|
|
|
pub selected: Cell<u32>, |
|
|
|
|
selected: Cell<u32>, |
|
|
|
|
/// The selected item.
|
|
|
|
|
#[property(get, set = Self::set_selected_item, explicit_notify, nullable)] |
|
|
|
|
pub selected_item: RefCell<Option<glib::Object>>, |
|
|
|
|
selected_item: RefCell<Option<glib::Object>>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Default for Selection { |
|
|
|
|
impl Default for FixedSelection { |
|
|
|
|
fn default() -> Self { |
|
|
|
|
Self { |
|
|
|
|
model: Default::default(), |
|
|
|
|
@ -32,16 +32,16 @@ mod imp {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[glib::object_subclass] |
|
|
|
|
impl ObjectSubclass for Selection { |
|
|
|
|
const NAME: &'static str = "SidebarSelection"; |
|
|
|
|
type Type = super::Selection; |
|
|
|
|
impl ObjectSubclass for FixedSelection { |
|
|
|
|
const NAME: &'static str = "FixedSelection"; |
|
|
|
|
type Type = super::FixedSelection; |
|
|
|
|
type Interfaces = (gio::ListModel, gtk::SelectionModel); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[glib::derived_properties] |
|
|
|
|
impl ObjectImpl for Selection {} |
|
|
|
|
impl ObjectImpl for FixedSelection {} |
|
|
|
|
|
|
|
|
|
impl ListModelImpl for Selection { |
|
|
|
|
impl ListModelImpl for FixedSelection { |
|
|
|
|
fn item_type(&self) -> glib::Type { |
|
|
|
|
glib::Object::static_type() |
|
|
|
|
} |
|
|
|
|
@ -55,7 +55,7 @@ mod imp {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl SelectionModelImpl for Selection { |
|
|
|
|
impl SelectionModelImpl for FixedSelection { |
|
|
|
|
fn selection_in_range(&self, _position: u32, _n_items: u32) -> gtk::Bitset { |
|
|
|
|
let bitset = gtk::Bitset::new_empty(); |
|
|
|
|
let selected = self.selected.get(); |
|
|
|
|
@ -72,44 +72,52 @@ mod imp {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Selection { |
|
|
|
|
impl FixedSelection { |
|
|
|
|
/// Set the underlying model.
|
|
|
|
|
fn set_model(&self, model: Option<gio::ListModel>) { |
|
|
|
|
let obj = self.obj(); |
|
|
|
|
let _guard = obj.freeze_notify(); |
|
|
|
|
|
|
|
|
|
let model = model.map(|m| m.clone().upcast()); |
|
|
|
|
let prev_model = self.model.obj(); |
|
|
|
|
|
|
|
|
|
let old_model = self.model.obj(); |
|
|
|
|
if old_model == model { |
|
|
|
|
if prev_model == model { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let n_items_before = old_model.map_or(0, |model| model.n_items()); |
|
|
|
|
let prev_n_items = prev_model |
|
|
|
|
.as_ref() |
|
|
|
|
.map(ListModelExt::n_items) |
|
|
|
|
.unwrap_or_default(); |
|
|
|
|
let n_items = model |
|
|
|
|
.as_ref() |
|
|
|
|
.map(ListModelExt::n_items) |
|
|
|
|
.unwrap_or_default(); |
|
|
|
|
|
|
|
|
|
self.model.disconnect_signals(); |
|
|
|
|
|
|
|
|
|
let obj = self.obj(); |
|
|
|
|
let _guard = obj.freeze_notify(); |
|
|
|
|
|
|
|
|
|
if let Some(model) = model { |
|
|
|
|
let items_changed_handler = model.connect_items_changed(clone!( |
|
|
|
|
#[weak] |
|
|
|
|
obj, |
|
|
|
|
#[weak(rename_to = imp)] |
|
|
|
|
self, |
|
|
|
|
move |m, p, r, a| { |
|
|
|
|
obj.items_changed_cb(m, p, r, a); |
|
|
|
|
imp.items_changed_cb(m, p, r, a); |
|
|
|
|
} |
|
|
|
|
)); |
|
|
|
|
|
|
|
|
|
self.model.set(model.clone(), vec![items_changed_handler]); |
|
|
|
|
obj.items_changed_cb(&model, 0, n_items_before, model.n_items()); |
|
|
|
|
} else { |
|
|
|
|
if self.selected.get() != gtk::INVALID_LIST_POSITION { |
|
|
|
|
self.selected.replace(gtk::INVALID_LIST_POSITION); |
|
|
|
|
obj.notify_selected(); |
|
|
|
|
} |
|
|
|
|
if self.selected_item.borrow().is_some() { |
|
|
|
|
self.selected_item.replace(None); |
|
|
|
|
obj.notify_selected_item(); |
|
|
|
|
} |
|
|
|
|
self.model.set(model, vec![items_changed_handler]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if self.selected.get() != gtk::INVALID_LIST_POSITION { |
|
|
|
|
self.selected.replace(gtk::INVALID_LIST_POSITION); |
|
|
|
|
obj.notify_selected(); |
|
|
|
|
} |
|
|
|
|
if self.selected_item.borrow().is_some() { |
|
|
|
|
self.selected_item.replace(None); |
|
|
|
|
obj.notify_selected_item(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
obj.items_changed(0, n_items_before, 0); |
|
|
|
|
if prev_n_items > 0 || n_items > 0 { |
|
|
|
|
obj.items_changed(0, prev_n_items, n_items); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
obj.notify_model(); |
|
|
|
|
@ -117,8 +125,8 @@ mod imp {
|
|
|
|
|
|
|
|
|
|
/// Set the selected item by its position.
|
|
|
|
|
fn set_selected(&self, position: u32) { |
|
|
|
|
let old_selected = self.selected.get(); |
|
|
|
|
if old_selected == position { |
|
|
|
|
let prev_selected = self.selected.get(); |
|
|
|
|
if prev_selected == position { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -130,7 +138,7 @@ mod imp {
|
|
|
|
|
position |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if old_selected == selected { |
|
|
|
|
if prev_selected == selected { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
let obj = self.obj(); |
|
|
|
|
@ -138,14 +146,14 @@ mod imp {
|
|
|
|
|
self.selected.replace(selected); |
|
|
|
|
self.selected_item.replace(selected_item); |
|
|
|
|
|
|
|
|
|
if old_selected == gtk::INVALID_LIST_POSITION { |
|
|
|
|
if prev_selected == gtk::INVALID_LIST_POSITION { |
|
|
|
|
obj.selection_changed(selected, 1); |
|
|
|
|
} else if selected == gtk::INVALID_LIST_POSITION { |
|
|
|
|
obj.selection_changed(old_selected, 1); |
|
|
|
|
} else if selected < old_selected { |
|
|
|
|
obj.selection_changed(selected, old_selected - selected + 1); |
|
|
|
|
obj.selection_changed(prev_selected, 1); |
|
|
|
|
} else if selected < prev_selected { |
|
|
|
|
obj.selection_changed(selected, prev_selected - selected + 1); |
|
|
|
|
} else { |
|
|
|
|
obj.selection_changed(old_selected, selected - old_selected + 1); |
|
|
|
|
obj.selection_changed(prev_selected, selected - prev_selected + 1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
obj.notify_selected(); |
|
|
|
|
@ -159,7 +167,7 @@ mod imp {
|
|
|
|
|
} |
|
|
|
|
let obj = self.obj(); |
|
|
|
|
|
|
|
|
|
let old_selected = self.selected.get(); |
|
|
|
|
let prev_selected = self.selected.get(); |
|
|
|
|
let mut selected = gtk::INVALID_LIST_POSITION; |
|
|
|
|
|
|
|
|
|
if item.is_some() { |
|
|
|
|
@ -176,77 +184,89 @@ mod imp {
|
|
|
|
|
|
|
|
|
|
self.selected_item.replace(item); |
|
|
|
|
|
|
|
|
|
if old_selected != selected { |
|
|
|
|
if prev_selected != selected { |
|
|
|
|
self.selected.replace(selected); |
|
|
|
|
|
|
|
|
|
if old_selected == gtk::INVALID_LIST_POSITION { |
|
|
|
|
if prev_selected == gtk::INVALID_LIST_POSITION { |
|
|
|
|
obj.selection_changed(selected, 1); |
|
|
|
|
} else if selected == gtk::INVALID_LIST_POSITION { |
|
|
|
|
obj.selection_changed(old_selected, 1); |
|
|
|
|
} else if selected < old_selected { |
|
|
|
|
obj.selection_changed(selected, old_selected - selected + 1); |
|
|
|
|
obj.selection_changed(prev_selected, 1); |
|
|
|
|
} else if selected < prev_selected { |
|
|
|
|
obj.selection_changed(selected, prev_selected - selected + 1); |
|
|
|
|
} else { |
|
|
|
|
obj.selection_changed(old_selected, selected - old_selected + 1); |
|
|
|
|
obj.selection_changed(prev_selected, selected - prev_selected + 1); |
|
|
|
|
} |
|
|
|
|
obj.notify_selected(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
obj.notify_selected_item(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
glib::wrapper! { |
|
|
|
|
/// A `GtkSelectionModel` that keeps track of the selected item even if its position changes or it is removed from the list.
|
|
|
|
|
pub struct Selection(ObjectSubclass<imp::Selection>) |
|
|
|
|
@implements gio::ListModel, gtk::SelectionModel; |
|
|
|
|
} |
|
|
|
|
/// Handle when items changed in the underlying model.
|
|
|
|
|
fn items_changed_cb( |
|
|
|
|
&self, |
|
|
|
|
model: &gio::ListModel, |
|
|
|
|
position: u32, |
|
|
|
|
removed: u32, |
|
|
|
|
added: u32, |
|
|
|
|
) { |
|
|
|
|
let obj = self.obj(); |
|
|
|
|
let _guard = obj.freeze_notify(); |
|
|
|
|
|
|
|
|
|
impl Selection { |
|
|
|
|
pub fn new<P: IsA<gio::ListModel>>(model: Option<&P>) -> Selection { |
|
|
|
|
let model = model.map(|m| m.clone().upcast()); |
|
|
|
|
glib::Object::builder().property("model", &model).build() |
|
|
|
|
} |
|
|
|
|
let selected = self.selected.get(); |
|
|
|
|
let selected_item = self.selected_item.borrow().clone(); |
|
|
|
|
|
|
|
|
|
if selected_item.is_none() || selected < position { |
|
|
|
|
// unchanged
|
|
|
|
|
} else if selected != gtk::INVALID_LIST_POSITION && selected >= position + removed { |
|
|
|
|
self.selected.set(selected + added - removed); |
|
|
|
|
obj.notify_selected(); |
|
|
|
|
} else { |
|
|
|
|
let mut found = false; |
|
|
|
|
|
|
|
|
|
for i in position..(position + added) { |
|
|
|
|
let item = model.item(i); |
|
|
|
|
|
|
|
|
|
fn items_changed_cb(&self, model: &gio::ListModel, position: u32, removed: u32, added: u32) { |
|
|
|
|
let imp = self.imp(); |
|
|
|
|
|
|
|
|
|
let _guard = self.freeze_notify(); |
|
|
|
|
|
|
|
|
|
let selected = self.selected(); |
|
|
|
|
let selected_item = self.selected_item(); |
|
|
|
|
|
|
|
|
|
if selected_item.is_none() || selected < position { |
|
|
|
|
// unchanged
|
|
|
|
|
} else if selected != gtk::INVALID_LIST_POSITION && selected >= position + removed { |
|
|
|
|
imp.selected.replace(selected + added - removed); |
|
|
|
|
self.notify_selected(); |
|
|
|
|
} else { |
|
|
|
|
for i in 0..=added { |
|
|
|
|
if i == added { |
|
|
|
|
// the item really was deleted
|
|
|
|
|
imp.selected.replace(gtk::INVALID_LIST_POSITION); |
|
|
|
|
self.notify_selected(); |
|
|
|
|
} else { |
|
|
|
|
let item = model.item(position + i); |
|
|
|
|
if item == selected_item { |
|
|
|
|
// the item moved
|
|
|
|
|
if selected != position + i { |
|
|
|
|
imp.selected.replace(position + i); |
|
|
|
|
self.notify_selected(); |
|
|
|
|
if selected != i { |
|
|
|
|
// The position of the item changed.
|
|
|
|
|
self.selected.set(i); |
|
|
|
|
obj.notify_selected(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
found = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !found { |
|
|
|
|
// The item is no longer in the model.
|
|
|
|
|
self.selected.set(gtk::INVALID_LIST_POSITION); |
|
|
|
|
obj.notify_selected(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
obj.items_changed(position, removed, added); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
glib::wrapper! { |
|
|
|
|
/// A `GtkSelectionModel` that keeps track of the selected item even if its
|
|
|
|
|
/// position changes or it is removed from the list.
|
|
|
|
|
pub struct FixedSelection(ObjectSubclass<imp::FixedSelection>) |
|
|
|
|
@implements gio::ListModel, gtk::SelectionModel; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.items_changed(position, removed, added); |
|
|
|
|
impl FixedSelection { |
|
|
|
|
/// Construct a new `FixedSelection` with the given model.
|
|
|
|
|
pub fn new(model: Option<&impl IsA<gio::ListModel>>) -> Self { |
|
|
|
|
glib::Object::builder().property("model", model).build() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Default for Selection { |
|
|
|
|
impl Default for FixedSelection { |
|
|
|
|
fn default() -> Self { |
|
|
|
|
Self::new(gio::ListModel::NONE) |
|
|
|
|
Self::new(None::<&gio::ListModel>) |
|
|
|
|
} |
|
|
|
|
} |