def slow_test_threaded_filtered_model_updates(self): for _ in range(1000): list_model = ListModel.ListModel("data_items") filtered_data_items = ListModel.FilteredListModel(items_key="data_items") data_items = list() cc = 30 for _ in range(cc): data_item = DataItem.DataItem(numpy.zeros((16, 16), numpy.uint32)) data_items.append(data_item) c1 = [n for n in range(cc) if random.randint(0,100) > 50] def is_live_filter(data_item): return data_items.index(data_item) in c1 filtered_data_items.container = list_model filtered_data_items.sort_key = lambda x: data_items.index(x) selection = Selection.IndexedSelection() filtered_data_items2 = ListModel.FilteredListModel(items_key="data_items", container=filtered_data_items, selection=selection) filtered_data_items2.filter = ListModel.PredicateFilter(is_live_filter) finished = threading.Event() def update_randomly(): for _ in range(cc): data_item = random.choice(filtered_data_items._get_master_items()) data_item.data_item_changed_event.fire() finished.set() list_model.insert_item(0, data_items[0]) threading.Thread(target = update_randomly).start() for index in range(1, cc): list_model.insert_item(index, data_items[index]) finished.wait() filtered_data_items2.close() filtered_data_items.close()
def test_random_filtered_model_updates(self): list_model = ListModel.ListModel("data_items") filtered_data_items = ListModel.FilteredListModel(items_key="data_items") filtered_data_items.container = list_model data_items = list() cc = 30 for _ in range(cc): data_item = DataItem.DataItem(numpy.zeros((16, 16), numpy.uint32)) list_model.insert_item(0, data_item) data_items.append(data_item) selection = Selection.IndexedSelection() filtered_data_items2 = ListModel.FilteredListModel(items_key="data_items", container=filtered_data_items, selection=selection) import random for xx in range(10): c1 = [n for n in range(cc)] c2 = [n for n in range(cc) if random.randint(0,100) > 20] random.shuffle(c1) random.shuffle(c2) def is_live_filter(data_item): return data_items.index(data_item) in c1 def is_live_filter2(data_item): return data_items.index(data_item) in c2 filtered_data_items.sort_key = lambda x: data_items.index(x) with filtered_data_items.changes(): filtered_data_items.filter = ListModel.PredicateFilter(is_live_filter) filtered_data_items.filter = ListModel.PredicateFilter(is_live_filter2) self.assertEqual(set(c2), set([data_items.index(d) for d in filtered_data_items2.items])) for data_item in data_items: data_item.close()
def test_filtered_model_updates_when_source_model_has_data_item_that_updates( self): document_model = DocumentModel.DocumentModel() with contextlib.closing(document_model): filtered_data_items = ListModel.FilteredListModel( items_key="data_items") filtered_data_items.container = document_model for _ in range(4): data_item = DataItem.DataItem( numpy.zeros((16, 16), numpy.uint32)) document_model.append_data_item(data_item) self.assertEqual(len(filtered_data_items.items), 4) selection = Selection.IndexedSelection() filtered_data_items2 = ListModel.FilteredListModel( items_key="data_items", container=filtered_data_items, selection=selection) filtered_data_items2.filter = ListModel.EqFilter("is_live", True) self.assertEqual(len(filtered_data_items2.items), 0) with document_model.data_item_live(document_model.data_items[0]): document_model.data_items[0].data_item_changed_event.fire() self.assertEqual(len(filtered_data_items.items), 4) # verify assumption self.assertEqual(len(filtered_data_items2.items), 1) # verify assumption self.assertTrue( document_model.data_items[0] in filtered_data_items2.items)
def test_filtered_list_changing_container_under_changes_retains_order( self): # the bug was that if list model is under a change and items are only # being rearranged (easy to occur with dependent list being sorted differently) # then there is no way to detect that it is not sorted anymore so it proceeds # as if it is already sorted properly. l = ListModel.ListModel("items") l.append_item("3") l.append_item("1") l.append_item("4") l.append_item("2") l2 = ListModel.ListModel("items") l2.append_item("4") l2.append_item("1") l2.append_item("2") l2.append_item("3") l3 = ListModel.FilteredListModel(container=l, items_key="items") self.assertEqual(["3", "1", "4", "2"], l3.items) l4 = ListModel.FilteredListModel(container=l3, items_key="items") self.assertEqual(["3", "1", "4", "2"], l4.items) l4.begin_change() l3.begin_change() l3.filter = ListModel.Filter(True) l3.end_change() l4.end_change() l4.begin_change() l3.begin_change() l3.container = l2 l3.end_change() l4.end_change() self.assertEqual(["4", "1", "2", "3"], l3.items) self.assertEqual(["4", "1", "2", "3"], l4.items)
def test_filtered_list_sends_begin_end_changes_for_single_insert_and_remove( self) -> None: l = ListModel.ListModel[typing.Any]("items") l.append_item("3") l.append_item("1") l.append_item("4") l.append_item("2") l2 = ListModel.FilteredListModel(container=l, items_key="items") l2.sort_key = lambda x: x begin_changes_count = 0 end_changes_count = 0 def begin_changes(key: str) -> None: nonlocal begin_changes_count begin_changes_count += 1 def end_changes(key: str) -> None: nonlocal end_changes_count end_changes_count += 1 with l2.begin_changes_event.listen( begin_changes), l2.end_changes_event.listen(end_changes): l.insert_item(0, "5") l.remove_item(0) self.assertEqual(2, begin_changes_count) self.assertEqual(2, end_changes_count)
def test_filtered_list_sends_begin_end_changes_for_grouped_insert_and_remove( self): l = ListModel.ListModel("items") l.append_item("3") l.append_item("1") l.append_item("4") l.append_item("2") l2 = ListModel.FilteredListModel(container=l, items_key="items") l2.sort_key = lambda x: x begin_changes_count = 0 end_changes_count = 0 def begin_changes(key): nonlocal begin_changes_count begin_changes_count += 1 def end_changes(key): nonlocal end_changes_count end_changes_count += 1 with l2.begin_changes_event.listen( begin_changes), l2.end_changes_event.listen(end_changes): with l2.changes(): l.insert_item(0, "5") l.insert_item(0, "6") l.remove_item(0) l.remove_item(0) self.assertEqual(1, begin_changes_count) self.assertEqual(1, end_changes_count)
def test_processed_data_items_sorted_by_source_data_modified_date(self): document_model = DocumentModel.DocumentModel() with contextlib.closing(document_model): filtered_data_items = ListModel.FilteredListModel( items_key="data_items") filtered_data_items.container = document_model filtered_data_items.sort_key = DataItem.sort_by_date_key for _ in range(4): data_item = DataItem.DataItem( numpy.zeros((16, 16), numpy.uint32)) document_model.append_data_item(data_item) time.sleep(0.01) data_item = document_model.get_invert_new( document_model.display_items[0]) document_model.recompute_all() self.assertEqual(len(filtered_data_items.items), 5) # new data item should be last self.assertEqual( filtered_data_items.items.index(document_model.data_items[4]), 4) self.assertEqual( filtered_data_items.items.index(document_model.data_items[0]), 0) self.assertEqual( list(document_model.data_items[2:5]), filtered_data_items.items[2:]) # rest of list matches
def test_mapped_list_sends_begin_end_changes_for_single_insert_and_remove( self): l = ListModel.ListModel("items") l.append_item("3") l.append_item("1") l.append_item("4") l.append_item("2") l1 = ListModel.FilteredListModel(container=l, master_items_key="items", items_key="mitems") l1.sort_key = lambda x: x l2 = ListModel.MappedListModel(container=l1, master_items_key="mitems", items_key="items") begin_changes_count = 0 end_changes_count = 0 def begin_changes(key): nonlocal begin_changes_count begin_changes_count += 1 def end_changes(key): nonlocal end_changes_count end_changes_count += 1 with l2.begin_changes_event.listen( begin_changes), l2.end_changes_event.listen(end_changes): l.insert_item(0, "5") l.remove_item(0) self.assertEqual(2, begin_changes_count) self.assertEqual(2, end_changes_count)
def test_sorted_filtered_model_updates_when_data_item_enters_filter(self): def sort_by_date_key(data_item): return data_item.created document_model = DocumentModel.DocumentModel() with contextlib.closing(document_model): filtered_data_items = ListModel.FilteredListModel( items_key="data_items") filtered_data_items.container = document_model filtered_data_items.filter = ListModel.EqFilter("is_live", True) filtered_data_items.sort_key = sort_by_date_key for _ in range(4): data_item = DataItem.DataItem( numpy.zeros((16, 16), numpy.uint32)) document_model.append_data_item(data_item) self.assertEqual(len(filtered_data_items.items), 0) with document_model.data_item_live(document_model.data_items[0]): document_model.data_items[0].data_item_changed_event.fire() self.assertEqual(len(filtered_data_items.items), 1) with document_model.data_item_live( document_model.data_items[2]): document_model.data_items[2].data_item_changed_event.fire() self.assertEqual(len(filtered_data_items.items), 2) self.assertTrue( filtered_data_items.items.index( document_model.data_items[0]) < filtered_data_items .items.index(document_model.data_items[2]))
def test_filter_model_inits_with_source_model(self): list_model = ListModel.ListModel("data_items") filtered_data_items = ListModel.FilteredListModel(items_key="data_items") filtered_data_items.container = list_model filtered_data_items.sort_key = operator.attrgetter("title") data_items = list() for value in TestDataItemsModelModule.values: data_item = DataItem.DataItem(numpy.zeros((16, 16), numpy.uint32)) data_item.title = value list_model.insert_item(0, data_item) data_items.append(data_item) self.assertEqual([d.title for d in filtered_data_items.items], sorted([d.title for d in filtered_data_items.items])) selection = Selection.IndexedSelection() filtered_data_items2 = ListModel.FilteredListModel(items_key="data_items", container=filtered_data_items, selection=selection) self.assertEqual([d.title for d in filtered_data_items.items], [d.title for d in filtered_data_items2.items]) for data_item in data_items: data_item.close()
def test_inserting_items_into_model_index0_without_sort_key_puts_them_in_same_order(self): list_model = ListModel.ListModel("data_items") filtered_data_items = ListModel.FilteredListModel(items_key="data_items") filtered_data_items.container = list_model data_items = list() for index, value in enumerate(TestDataItemsModelModule.values): data_item = DataItem.DataItem(numpy.zeros((16, 16), numpy.uint32)) data_item.title = value list_model.insert_item(TestDataItemsModelModule.indexes[index], data_item) data_items.append(data_item) self.assertEqual([d.title for d in filtered_data_items.items], TestDataItemsModelModule.result) for data_item in data_items: data_item.close()
def test_filtered_list_changing_from_sorted_to_unsorted_retains_order(self): l = ListModel.ListModel("items") l.append_item("3") l.append_item("1") l.append_item("4") l.append_item("2") l2 = ListModel.FilteredListModel(container=l, items_key="items") l2.sort_key = lambda x: x self.assertEqual(["1", "2", "3", "4"], l2.items) l2.filter = ListModel.PredicateFilter(lambda x: x != "4") l2.sort_key = None self.assertEqual(["3", "1", "2"], l2.items) l.remove_item(1) self.assertEqual(["3", "2"], l2.items)
def test_inserting_items_into_model_index0_with_sort_key_puts_them_in_correct_order(self): list_model = ListModel.ListModel("data_items") filtered_data_items = ListModel.FilteredListModel(items_key="data_items") filtered_data_items.container = list_model filtered_data_items.sort_key = operator.attrgetter("title") data_items = list() for value in TestDataItemsModelModule.values: data_item = DataItem.DataItem(numpy.zeros((16, 16), numpy.uint32)) data_item.title = value list_model.insert_item(0, data_item) data_items.append(data_item) self.assertEqual([d.title for d in filtered_data_items.items], sorted([d.title for d in filtered_data_items.items])) for data_item in data_items: data_item.close()
def test_data_items_sorted_by_data_modified_date(self): with TestContext.create_memory_context() as test_context: document_model = test_context.create_document_model() filtered_data_items = ListModel.FilteredListModel(items_key="data_items") filtered_data_items.container = document_model filtered_data_items.sort_key = DataItem.sort_by_date_key for _ in range(4): data_item = DataItem.DataItem(numpy.zeros((16, 16), numpy.uint32)) document_model.append_data_item(data_item) time.sleep(0.01) self.assertEqual(len(filtered_data_items.items), 4) self.assertEqual(list(document_model.data_items), filtered_data_items.items) with document_model.data_items[0].data_ref() as dr: dr.data += 1 self.assertEqual([document_model.data_items[1], document_model.data_items[2], document_model.data_items[3], document_model.data_items[0]], filtered_data_items.items)
def test_sorted_filtered_model_updates_when_data_item_exits_filter(self): def sort_by_date_key(data_item): return data_item.created with TestContext.create_memory_context() as test_context: document_model = test_context.create_document_model() filtered_data_items = ListModel.FilteredListModel(items_key="data_items") filtered_data_items.container = document_model filtered_data_items.filter = ListModel.NotFilter(ListModel.EqFilter("is_live", True)) filtered_data_items.sort_key = sort_by_date_key for _ in range(4): data_item = DataItem.DataItem(numpy.zeros((16, 16), numpy.uint32)) document_model.append_data_item(data_item) self.assertEqual(len(filtered_data_items.items), 4) with document_model.data_item_live(document_model.data_items[0]): document_model.data_items[0].data_item_changed_event.fire() self.assertEqual(len(filtered_data_items.items), 3)
def test_filtered_list_is_sorted(self): l = ListModel.ListModel("items") l.append_item("3") l.append_item("1") l.append_item("4") l.append_item("2") l2 = ListModel.FilteredListModel(container=l, items_key="items") l2.sort_key = lambda x: x l2.mark_changed() self.assertEqual(["1", "2", "3", "4"], l2.items) l.remove_item(1) l.remove_item(1) self.assertEqual(["2", "3"], l2.items) l.insert_item(0, "5") l.insert_item(0, "1") self.assertEqual(["1", "2", "3", "5"], l2.items)
def test_inserting_items_into_model_index0_without_sort_key__but_with_filter_puts_them_in_same_order(self): values = ["DEF", "ABC", "GHI", "DFG", "ACD", "GIJ"] indexes = [0, 0, 1, 1, 2, 4] result = ["ABC", "DFG", "ACD", "GHI", "GIJ", "DEF"] list_model = ListModel.ListModel("data_items") filtered_data_items = ListModel.FilteredListModel(items_key="data_items") filtered_data_items.container = list_model filtered_data_items.filter = ListModel.NotFilter(ListModel.StartsWithFilter("title", "D")) data_items = list() for index, value in enumerate(values): data_item = DataItem.DataItem(numpy.zeros((16, 16), numpy.uint32)) data_item.title = value list_model.insert_item(indexes[index], data_item) data_items.append(data_item) self.assertEqual([d.title for d in filtered_data_items.items], [v for v in result if not v.startswith("D")]) for data_item in data_items: data_item.close()
def test_sorted_model_updates_when_transaction_started(self): def sort_by_date_key(data_item): """ A sort key to for the modification date field of a data item. """ return data_item.is_live, data_item.created with TestContext.create_memory_context() as test_context: document_model = test_context.create_document_model() filtered_data_items = ListModel.FilteredListModel(items_key="data_items") filtered_data_items.container = document_model filtered_data_items.sort_key = sort_by_date_key filtered_data_items.sort_reverse = True for value in TestDataItemsModelModule.values: data_item = DataItem.DataItem(numpy.zeros((16, 16), numpy.uint32)) data_item.title = value document_model.append_data_item(data_item) live_data_item = filtered_data_items.items[2] with document_model.data_item_live(live_data_item): self.assertEqual(filtered_data_items.items.index(live_data_item), 0)
def test_filtered_list_updates_filtered_selection(self) -> None: l = ListModel.ListModel[typing.Any]("items") l.append_item("A1") l.append_item("B1") l.append_item("A2") l.append_item("B2") l.append_item("A3") l.append_item("B3") l2 = ListModel.FilteredListModel(container=l, items_key="items") s = l2.make_selection() s.set_multiple({0, 1, 2, 3}) l2.filter = ListModel.PredicateFilter( lambda x: bool(x.startswith("A"))) s.set_multiple({0, 1, 2}) l.remove_item(1) # B self.assertEqual({0, 1, 2}, s.indexes) l.remove_item(0) # A self.assertEqual({0, 1}, s.indexes)
def test_filtered_list_unsorted_retains_order(self): l = ListModel.ListModel("items") l.append_item("3") l.append_item("1") l.append_item("4") l.append_item("2") l2 = ListModel.FilteredListModel(container=l, items_key="items") l2.filter = ListModel.PredicateFilter(lambda x: x != "4") self.assertEqual(["3", "1", "2"], l2.items) l.remove_item(0) self.assertEqual(["1", "2"], l2.items) l.insert_item(0, "3") l.append_item("44") self.assertEqual(["3", "1", "2", "44"], l2.items) l2.begin_change() l.insert_item(0, "5") l.append_item("6") l2.end_change() self.assertEqual(["5", "3", "1", "2", "44", "6"], l2.items)
def test_filtered_list_does_not_access_container_when_closing(self): class Container(Observable.Observable): def __init__(self): super().__init__() self.__items = [1, 2, 3] self.closed = False def close(self): self.closed = True @property def items(self): if not self.closed: return self.__items return None c = Container() l2 = ListModel.FilteredListModel(container=c, items_key="items") c.close() l2.close()
def show_choose_project_dialog(self) -> None: with self.prevent_close(): u = Declarative.DeclarativeUI() button_row = u.create_row( u.create_push_button(text=_("New..."), on_clicked="new_project"), u.create_push_button(text=_("Open..."), on_clicked="open_project"), u.create_stretch(), u.create_push_button(text=_("Cancel"), on_clicked="close_window"), u.create_push_button(text=_("Open Recent"), on_clicked="open_recent"), spacing=8) project_references_model = ListModel.FilteredListModel( container=self.__profile, items_key="project_references") project_references_model.filter = ListModel.PredicateFilter( lambda pr: pr.project_state != "loaded") project_references_model.sort_key = lambda pr: pr.last_used project_references_model.sort_reverse = True class ProjectReferenceItem: # provides a str converter and a tool tip. def __init__(self, project_reference: Profile.ProjectReference): self.project_reference = project_reference def __str__(self) -> str: project_reference = self.project_reference project_title = project_reference.title if project_reference.project_state == "needs_upgrade": project_title += " " + _("(NEEDS UPGRADE)") elif project_reference.project_state != "unloaded" or project_reference.project_version != FileStorageSystem.PROJECT_VERSION: project_title += " " + _("(MISSING OR UNREADABLE)") return project_title @property def tool_tip(self) -> str: return str(self.project_reference.path) project_reference_items_model = ListModel.MappedListModel( container=project_references_model, master_items_key="project_references", items_key="project_reference_items", map_fn=ProjectReferenceItem) item_list = u.create_list_box( items_ref="@binding(list_property_model.value)", current_index="@binding(current_index)", height=240, min_height=180, size_policy_horizontal="expanding", on_item_selected="recent_item_selected", on_item_handle_context_menu="item_handle_context_menu") main_column = u.create_column( u.create_label(text=_("Recent Projects")), item_list, u.create_spacing(13), button_row, spacing=8, width=380) window = u.create_window(main_column, title=_("Choose Project"), margin=12, window_style="tool") def open_project_reference( project_reference: Profile.ProjectReference) -> None: self.open_project_reference(project_reference) def show_open_project_dialog() -> None: self.show_open_project_dialog() def show_new_project_dialog() -> None: NewProjectAction().invoke( UIWindow.ActionContext(self, None, None)) from nion.utils import Observable class ListPropertyModel(Observable.Observable): # copied from nionutils to avoid requiring new version. # remove this code once nionutils 0.3.24+ is released. def __init__(self, list_model): super().__init__() self.__list_model = list_model self.__item_inserted_event_listener = list_model.item_inserted_event.listen( self.__item_inserted) self.__item_removed_event_listener = list_model.item_removed_event.listen( self.__item_removed) def close(self) -> None: self.__list_model = None self.__item_inserted_event_listener = None self.__item_removed_event_listener = None def __item_inserted(self, key: str, item, before_index: int) -> None: self.notify_property_changed("value") def __item_removed(self, key: str, item, index: int) -> None: self.notify_property_changed("value") @property def value(self): return self.__list_model.items class ChooseProjectHandler(Declarative.WindowHandler): def __init__(self, application: Application): super().__init__() self.__application = application self.current_index = 0 self.list_property_model = ListPropertyModel( project_reference_items_model) def recent_item_selected(self, widget: Declarative.UIWidget, current_index: int) -> None: if 0 <= current_index < len(project_reference_items_model. project_reference_items): # to ensure the application does not close upon closing the last window, force it # to stay open while the window is closed and another reopened. with self.__application.prevent_close(): self.close_window() project_reference_item = project_reference_items_model.project_reference_items[ current_index] open_project_reference( project_reference_item.project_reference) def new_project(self, widget: Declarative.UIWidget) -> None: # to ensure the application does not close upon closing the last window, force it # to stay open while the window is closed and another reopened. with self.__application.prevent_close(): show_new_project_dialog() self.close_window() def open_project(self, widget: Declarative.UIWidget) -> None: # to ensure the application does not close upon closing the last window, force it # to stay open while the window is closed and another reopened. with self.__application.prevent_close(): show_open_project_dialog() self.close_window() def open_recent(self, widget: Declarative.UIWidget) -> None: self.recent_item_selected(widget, self.current_index) def item_handle_context_menu(self, widget: Declarative.UIWidget, *, gx: int, gy: int, index: typing.Optional[int], **kwargs) -> bool: if index is not None: project_reference_item = project_reference_items_model.project_reference_items[ index] menu = self.window.create_context_menu() menu.add_menu_item( _(f"Open Project Location"), functools.partial( ProjectPanel.reveal_project, project_reference_item.project_reference)) menu.add_separator() def remove_project(index: int) -> None: project_reference_item = project_reference_items_model.project_reference_items[ index] self.__application.profile.remove_project_reference( project_reference_item.project_reference) menu.add_menu_item( _(f"Remove Project from List"), functools.partial(remove_project, index)) menu.popup(gx, gy) return True ChooseProjectHandler(self).run(window, app=self)
def test_refcounts(self) -> None: # list model model = ListModel.ListModel[typing.Any]("items") model_ref = weakref.ref(model) del model self.assertIsNone(model_ref()) # filtered model l = ListModel.ListModel[typing.Any]("items") model2 = ListModel.FilteredListModel(container=l, items_key="items") model_ref2 = weakref.ref(model2) del model2 self.assertIsNone(model_ref2()) # nested filtered model l = ListModel.ListModel[typing.Any]("items") l2 = ListModel.FilteredListModel(container=l, items_key="items") model3 = ListModel.FilteredListModel(container=l2, items_key="items") model_ref3 = weakref.ref(model3) del model3 self.assertIsNone(model_ref3()) # filtered model with item changed event l = ListModel.ListModel[typing.Any]("items") l.append_item(C()) model4 = ListModel.FilteredListModel(container=l, items_key="items") model_ref4 = weakref.ref(model4) del model4 self.assertIsNone(model_ref4()) # mapped model l = ListModel.ListModel[typing.Any]("items") model5 = ListModel.MappedListModel(container=l, master_items_key="items", items_key="items") model_ref5 = weakref.ref(model5) del model5 self.assertIsNone(model_ref5()) # mapped model of filtered model l = ListModel.ListModel[typing.Any]("items") l2 = ListModel.FilteredListModel(container=l, items_key="items") model6 = ListModel.MappedListModel(container=l2, master_items_key="items", items_key="items") model_ref6 = weakref.ref(model6) del model6 self.assertIsNone(model_ref6()) # flattened model l = ListModel.ListModel[typing.Any]("items") model7 = ListModel.FlattenedListModel(container=l, master_items_key="items", child_items_key="items", items_key="items") model_ref7 = weakref.ref(model7) del model7 self.assertIsNone(model_ref7()) # flattened model with items l = ListModel.ListModel[typing.Any]("items") l.append_item(ListModel.ListModel[typing.Any]("items")) model8 = ListModel.FlattenedListModel(container=l, master_items_key="items", child_items_key="items", items_key="items") model_ref8 = weakref.ref(model8) del model8 self.assertIsNone(model_ref8()) # list property model l = ListModel.ListModel[typing.Any]("items") model9 = ListModel.ListPropertyModel(l) model_ref9 = weakref.ref(model9) del model9 self.assertIsNone(model_ref9())