fractal/session/model/sidebar_data/
selection.rs1use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
2
3use crate::utils::BoundObject;
4
5mod imp {
6 use std::cell::{Cell, RefCell};
7
8 use super::*;
9
10 #[derive(Debug, glib::Properties)]
11 #[properties(wrapper_type = super::Selection)]
12 pub struct Selection {
13 #[property(get, set = Self::set_model, explicit_notify, nullable)]
15 pub model: BoundObject<gio::ListModel>,
16 #[property(get, set = Self::set_selected, explicit_notify, default = gtk::INVALID_LIST_POSITION)]
18 pub selected: Cell<u32>,
19 #[property(get, set = Self::set_selected_item, explicit_notify, nullable)]
21 pub selected_item: RefCell<Option<glib::Object>>,
22 }
23
24 impl Default for Selection {
25 fn default() -> Self {
26 Self {
27 model: Default::default(),
28 selected: Cell::new(gtk::INVALID_LIST_POSITION),
29 selected_item: Default::default(),
30 }
31 }
32 }
33
34 #[glib::object_subclass]
35 impl ObjectSubclass for Selection {
36 const NAME: &'static str = "SidebarSelection";
37 type Type = super::Selection;
38 type Interfaces = (gio::ListModel, gtk::SelectionModel);
39 }
40
41 #[glib::derived_properties]
42 impl ObjectImpl for Selection {}
43
44 impl ListModelImpl for Selection {
45 fn item_type(&self) -> glib::Type {
46 glib::Object::static_type()
47 }
48
49 fn n_items(&self) -> u32 {
50 self.model.obj().map(|m| m.n_items()).unwrap_or_default()
51 }
52
53 fn item(&self, position: u32) -> Option<glib::Object> {
54 self.model.obj()?.item(position)
55 }
56 }
57
58 impl SelectionModelImpl for Selection {
59 fn selection_in_range(&self, _position: u32, _n_items: u32) -> gtk::Bitset {
60 let bitset = gtk::Bitset::new_empty();
61 let selected = self.selected.get();
62
63 if selected != gtk::INVALID_LIST_POSITION {
64 bitset.add(selected);
65 }
66
67 bitset
68 }
69
70 fn is_selected(&self, position: u32) -> bool {
71 self.selected.get() == position
72 }
73 }
74
75 impl Selection {
76 fn set_model(&self, model: Option<gio::ListModel>) {
78 let obj = self.obj();
79 let _guard = obj.freeze_notify();
80
81 let model = model.map(|m| m.clone().upcast());
82
83 let old_model = self.model.obj();
84 if old_model == model {
85 return;
86 }
87
88 let n_items_before = old_model.map_or(0, |model| model.n_items());
89 self.model.disconnect_signals();
90
91 if let Some(model) = model {
92 let items_changed_handler = model.connect_items_changed(clone!(
93 #[weak]
94 obj,
95 move |m, p, r, a| {
96 obj.items_changed_cb(m, p, r, a);
97 }
98 ));
99
100 self.model.set(model.clone(), vec![items_changed_handler]);
101 obj.items_changed_cb(&model, 0, n_items_before, model.n_items());
102 } else {
103 if self.selected.get() != gtk::INVALID_LIST_POSITION {
104 self.selected.replace(gtk::INVALID_LIST_POSITION);
105 obj.notify_selected();
106 }
107 if self.selected_item.borrow().is_some() {
108 self.selected_item.replace(None);
109 obj.notify_selected_item();
110 }
111
112 obj.items_changed(0, n_items_before, 0);
113 }
114
115 obj.notify_model();
116 }
117
118 fn set_selected(&self, position: u32) {
120 let old_selected = self.selected.get();
121 if old_selected == position {
122 return;
123 }
124
125 let selected_item = self.model.obj().and_then(|m| m.item(position));
126
127 let selected = if selected_item.is_none() {
128 gtk::INVALID_LIST_POSITION
129 } else {
130 position
131 };
132
133 if old_selected == selected {
134 return;
135 }
136 let obj = self.obj();
137
138 self.selected.replace(selected);
139 self.selected_item.replace(selected_item);
140
141 if old_selected == gtk::INVALID_LIST_POSITION {
142 obj.selection_changed(selected, 1);
143 } else if selected == gtk::INVALID_LIST_POSITION {
144 obj.selection_changed(old_selected, 1);
145 } else if selected < old_selected {
146 obj.selection_changed(selected, old_selected - selected + 1);
147 } else {
148 obj.selection_changed(old_selected, selected - old_selected + 1);
149 }
150
151 obj.notify_selected();
152 obj.notify_selected_item();
153 }
154
155 fn set_selected_item(&self, item: Option<glib::Object>) {
157 if *self.selected_item.borrow() == item {
158 return;
159 }
160 let obj = self.obj();
161
162 let old_selected = self.selected.get();
163 let mut selected = gtk::INVALID_LIST_POSITION;
164
165 if item.is_some() {
166 if let Some(model) = self.model.obj() {
167 for i in 0..model.n_items() {
168 let current_item = model.item(i);
169 if current_item == item {
170 selected = i;
171 break;
172 }
173 }
174 }
175 }
176
177 self.selected_item.replace(item);
178
179 if old_selected != selected {
180 self.selected.replace(selected);
181
182 if old_selected == gtk::INVALID_LIST_POSITION {
183 obj.selection_changed(selected, 1);
184 } else if selected == gtk::INVALID_LIST_POSITION {
185 obj.selection_changed(old_selected, 1);
186 } else if selected < old_selected {
187 obj.selection_changed(selected, old_selected - selected + 1);
188 } else {
189 obj.selection_changed(old_selected, selected - old_selected + 1);
190 }
191 obj.notify_selected();
192 }
193
194 obj.notify_selected_item();
195 }
196 }
197}
198
199glib::wrapper! {
200 pub struct Selection(ObjectSubclass<imp::Selection>)
202 @implements gio::ListModel, gtk::SelectionModel;
203}
204
205impl Selection {
206 pub fn new<P: IsA<gio::ListModel>>(model: Option<&P>) -> Selection {
207 let model = model.map(|m| m.clone().upcast());
208 glib::Object::builder().property("model", &model).build()
209 }
210
211 fn items_changed_cb(&self, model: &gio::ListModel, position: u32, removed: u32, added: u32) {
212 let imp = self.imp();
213
214 let _guard = self.freeze_notify();
215
216 let selected = self.selected();
217 let selected_item = self.selected_item();
218
219 if selected_item.is_none() || selected < position {
220 } else if selected != gtk::INVALID_LIST_POSITION && selected >= position + removed {
222 imp.selected.replace(selected + added - removed);
223 self.notify_selected();
224 } else {
225 for i in 0..=added {
226 if i == added {
227 imp.selected.replace(gtk::INVALID_LIST_POSITION);
229 self.notify_selected();
230 } else {
231 let item = model.item(position + i);
232 if item == selected_item {
233 if selected != position + i {
235 imp.selected.replace(position + i);
236 self.notify_selected();
237 }
238 break;
239 }
240 }
241 }
242 }
243
244 self.items_changed(position, removed, added);
245 }
246}
247
248impl Default for Selection {
249 fn default() -> Self {
250 Self::new(gio::ListModel::NONE)
251 }
252}