def test_mapped_model_values_after_delete_are_correct(self): l = ListModel.ListModel("items") l.append_item(A("1")) l.append_item(A("2")) l.append_item(A("3")) l2 = ListModel.MappedListModel(container=l, master_items_key="items", items_key="itemsb", map_fn=B) l.remove_item(1) self.assertEqual([b.s for b in map(B, l.items)], [b.s for b in l2.items]) self.assertEqual(l2.itemsb, l2.items) self.assertEqual("3_B", l2.items[1].s)
def test_mapped_model_selection_after_delete_are_correct(self): l = ListModel.ListModel("items") l.append_item(A("1")) l.append_item(A("2")) l.append_item(A("3")) l2 = ListModel.MappedListModel(container=l, master_items_key="items", items_key="itemsb", map_fn=B) s = l2.make_selection() s.add(0) s.add(2) l.remove_item(1) self.assertEqual({0, 1}, s.indexes)
def test_initial_mapped_model_values_are_correct(self) -> None: l = ListModel.ListModel[typing.Any]("items") l.append_item(A("1")) l.append_item(A("2")) l2 = ListModel.MappedListModel(container=l, master_items_key="items", items_key="itemsb", map_fn=B) self.assertEqual([b.s for b in map(B, l.items)], [b.s for b in l2.items]) self.assertEqual(l2.itemsb, l2.items) self.assertEqual("2_B", l2.items[1].s)
def test_mapped_model_selection_after_insert_are_correct(self) -> None: l = ListModel.ListModel[typing.Any]("items") l.append_item(A("1")) l.append_item(A("2")) l2 = ListModel.MappedListModel(container=l, master_items_key="items", items_key="itemsb", map_fn=B) s = l2.make_selection() s.add(0) s.add(1) l.insert_item(1, A("1.5")) self.assertEqual({0, 2}, s.indexes)
def test_mapped_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") 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): 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 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())
def __init__(self, document_controller: DocumentController.DocumentController, panel_id: str, properties: typing.Dict): super().__init__(document_controller, panel_id, _("Data Items")) ui = document_controller.ui def show_context_menu(display_item: typing.Optional[DisplayItem.DisplayItem], x: int, y: int, gx: int, gy: int) -> bool: menu = document_controller.create_context_menu_for_display(display_item, use_selection=True) menu.popup(gx, gy) return True def map_display_item_to_display_item_adapter(display_item: DisplayItem.DisplayItem) -> DisplayItemAdapter: return DisplayItemAdapter(display_item, ui) def unmap_display_item_to_display_item_adapter(display_item_adapter: DisplayItemAdapter) -> None: display_item_adapter.close() self.__filtered_display_item_adapters_model = ListModel.MappedListModel(container=document_controller.filtered_display_items_model, master_items_key="display_items", items_key="display_item_adapters", map_fn=map_display_item_to_display_item_adapter, unmap_fn=unmap_display_item_to_display_item_adapter) self.__selection = self.document_controller.selection self.__focused = False def selection_changed() -> None: # called when the selection changes; notify selected display item changed if focused. self.__notify_focus_changed() self.__selection_changed_event_listener = self.__selection.changed_event.listen(selection_changed) def display_item_adapter_selection_changed(display_item_adapters: typing.List[DisplayItemAdapter]) -> None: indexes = set() for index, display_item_adapter in enumerate(self.__filtered_display_item_adapters_model.display_item_adapters): if display_item_adapter in display_item_adapters: indexes.add(index) self.__selection.set_multiple(indexes) self.__notify_focus_changed() def focus_changed(focused: bool) -> None: self.focused = focused def delete_display_item_adapters(display_item_adapters: typing.List[DisplayItemAdapter]) -> None: document_controller.delete_display_items([display_item_adapter.display_item for display_item_adapter in display_item_adapters if display_item_adapter.display_item]) self.data_list_controller = DataListController(document_controller.event_loop, ui, self.__filtered_display_item_adapters_model, self.__selection) self.data_list_controller.on_display_item_adapter_selection_changed = display_item_adapter_selection_changed self.data_list_controller.on_context_menu_event = show_context_menu self.data_list_controller.on_focus_changed = focus_changed self.data_list_controller.on_delete_display_item_adapters = delete_display_item_adapters self.data_grid_controller = DataGridController(document_controller.event_loop, ui, self.__filtered_display_item_adapters_model, self.__selection) self.data_grid_controller.on_display_item_adapter_selection_changed = display_item_adapter_selection_changed self.data_grid_controller.on_context_menu_event = show_context_menu self.data_grid_controller.on_focus_changed = focus_changed self.data_grid_controller.on_delete_display_item_adapters = delete_display_item_adapters data_list_widget = DataListWidget(ui, self.data_list_controller) data_grid_widget = DataGridWidget(ui, self.data_grid_controller) list_icon_button = CanvasItem.BitmapButtonCanvasItem(CanvasItem.load_rgba_data_from_bytes(pkgutil.get_data(__name__, "resources/list_icon_20.png"))) grid_icon_button = CanvasItem.BitmapButtonCanvasItem(CanvasItem.load_rgba_data_from_bytes(pkgutil.get_data(__name__, "resources/grid_icon_20.png"))) list_icon_button.sizing.set_fixed_size(Geometry.IntSize(20, 20)) grid_icon_button.sizing.set_fixed_size(Geometry.IntSize(20, 20)) button_row = CanvasItem.CanvasItemComposition() button_row.layout = CanvasItem.CanvasItemRowLayout(spacing=4) button_row.add_canvas_item(list_icon_button) button_row.add_canvas_item(grid_icon_button) buttons_widget = ui.create_canvas_widget(properties={"height": 20, "width": 44}) buttons_widget.canvas_item.add_canvas_item(button_row) search_widget = ui.create_row_widget() search_widget.add_spacing(8) search_widget.add(ui.create_label_widget(_("Filter"))) search_widget.add_spacing(8) search_line_edit = ui.create_line_edit_widget() search_line_edit.placeholder_text = _("No Filter") search_line_edit.clear_button_enabled = True # Qt 5.3 doesn't signal text edited or editing finished when clearing. useless so disabled. search_line_edit.on_text_edited = self.document_controller.filter_controller.text_filter_changed search_line_edit.on_editing_finished = self.document_controller.filter_controller.text_filter_changed search_widget.add(search_line_edit) search_widget.add_spacing(6) search_widget.add(buttons_widget) search_widget.add_spacing(8) self.data_view_widget = ui.create_stack_widget() self.data_view_widget.add(data_list_widget) self.data_view_widget.add(data_grid_widget) self.data_view_widget.current_index = 0 self.__view_button_group = CanvasItem.RadioButtonGroup([list_icon_button, grid_icon_button]) self.__view_button_group.current_index = 0 self.__view_button_group.on_current_index_changed = lambda index: setattr(self.data_view_widget, "current_index", index) widget = ui.create_column_widget(properties=properties) widget.add(self.data_view_widget) widget.add_spacing(6) widget.add(search_widget) widget.add_spacing(6) self.widget = widget self._data_list_widget = data_list_widget self._data_grid_widget = data_grid_widget