diff --git a/src/view/hierarchy.rs b/src/view/hierarchy.rs index a584025..85205a9 100644 --- a/src/view/hierarchy.rs +++ b/src/view/hierarchy.rs @@ -568,6 +568,7 @@ impl<'tree, 'nodeinfo> selection::tree::TreeVisitor<'tree> for AlterNodesBulkNod #[cfg(test)] mod tests { use super::*; + use glib::clone; use rusty_fork::rusty_fork_test; fn assert_tlr_node_correct(document: &sync::Arc, node: &sync::Arc, iter: &mut impl std::iter::Iterator) { @@ -634,5 +635,56 @@ mod tests { tlm.item(1).unwrap().downcast::().unwrap().set_expanded(true); assert_tlm_correct(&document, &tlm); } + + #[test] + fn test_nest_issue_16() { + gtk::init().unwrap(); + + let root = structure::Node::builder() + .name("root") + .size(0x1000) + .child(0x0, |b| b + .name("A") + .size(0x20)) + .child(0x20, |b| b + .name("B") + .size(0x20)) + .child(0x40, |b| b + .name("C") + .size(0x20) + .child(0x0, |b| b + .name("D") + .size(0x10))) + .build(); + + let document_host = sync::Arc::new(document::Builder::new(root).host()); + let mut document = document_host.get(); + + let tlm = create_tree_list_model(document_host.clone(), document.clone(), true); + assert_tlm_correct(&document, &tlm); + + for i in 0..tlm.n_items() { + let tlr = tlm.item(i).unwrap().downcast::().unwrap(); + println!("{} {:?}", tlr.depth(), tlr.item().unwrap().downcast::().unwrap().info().props.name); + } + + document = document_host.change(document.nest( + structure::SiblingRange::new(vec![], 0, 1), + addr::Extent::sized_u64(0x0, 0x40), + structure::Properties::default(), + )).unwrap(); + + tlm.connect_items_changed(clone!(#[strong] document, move |tlm, _, _, _| { + println!(""); + println!("items changed"); + assert_tlm_correct(&document, &tlm); + for i in 0..tlm.n_items() { + let tlr = tlm.item(i).unwrap().downcast::().unwrap(); + println!("{} {:?}", tlr.depth(), tlr.item().unwrap().downcast::().unwrap().info().props.name); + } + })); + + tlm.model().downcast::().unwrap().update_document(&document); + } } } diff --git a/src/view/selection/tree.rs b/src/view/selection/tree.rs index 5542001..35c14c0 100644 --- a/src/view/selection/tree.rs +++ b/src/view/selection/tree.rs @@ -331,3 +331,96 @@ impl TreeSelectionModel { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::addr; + use crate::model::document::structure; + use crate::util; + use rusty_fork::rusty_fork_test; + + rusty_fork_test! { + #[test] + fn test_nest_crash_issue_16() { + let _pg = util::test::PanicGuard::new(); + + gtk::init().unwrap(); + + let root = structure::Node::builder() + .name("root") + .size(0x1000) + .child(0x0, |b| b + .name("A") + .size(0x20)) + .child(0x20, |b| b + .name("B") + .size(0x20)) + .child(0x40, |b| b + .name("C") + .size(0x20) + .child(0x0, |b| b + .name("D") + .size(0x10))) + .build(); + + let dh = sync::Arc::new(document::Builder::new(root).host()); + let mut document = dh.get(); + let mut selection = selection::TreeSelection::new(document.clone()); + selection.add_single(&[0]); + selection.add_single(&[1]); + let tsh = sync::Arc::new(selection::tree::Host::new(selection)); + + let ter = rc::Rc::new(helpers::test::TestErrorReporter); + let tsm = TreeSelectionModel::new(ter.clone(), tsh.clone(), dh.clone()); + let tlm = tsm.imp().borrow_interior().unwrap().gtk_model.clone(); + + for i in 0..tlm.n_items() { + let tlr = tlm.item(i).unwrap().downcast::().unwrap(); + let item = tlr.item().unwrap().downcast::().unwrap(); + let info = item.info(); + println!("{:?} {:?} {:?}", info.path, info.props.name, tsm.is_selected(i)); + } + + tsm.connect_items_changed(clone!(move |tsm, p, r, a| { + println!(""); + println!("selection model changed: {}, {}, {}", p, r, a); + for i in 0..tsm.n_items() { + let tlr = tsm.item(i).unwrap().downcast::().unwrap(); + let item = tlr.item().unwrap().downcast::().unwrap(); + let info = item.info(); + println!("{:?} {:?} {:?}", info.path, info.props.name, tsm.is_selected(i)); + } + })); + + document = dh.change(document.nest( + structure::SiblingRange::new(vec![], 0, 1), + addr::Extent::sized_u64(0x0, 0x40), + structure::Properties::default(), + )).unwrap(); + + /* tree nest action forces selection, so we do too */ + let selection = tsh.change(selection::tree::Change::SetSingle(document.clone(), vec![0])).unwrap(); + let interior = tsm.imp().borrow_interior_mut().unwrap(); + tsm.update_selection(interior, selection); + + /* panic happens in: + TreeSelectionModel::update_selection + RootListModel::update_document + NodeItem::update_document + StructureListModel::update + Versioned::changes_since + StructureListModel::update closure + StructureListModel::port_change + emit items_changed signal + gtk_tree_list_model_items_changed cb + emit items-changed signal + TreeSelectionModel::new closure for gtk_model.connect_items_changed + emit items-changed signal + gtk_list_item_manager_model_items_changed_cb + gtk_list_item_manager_ensure_items + TreeSelectionModel::imp::is_selected + */ + } + } +}