From 7460af8d588f70ad0ed654acc886f93d6b79e84e Mon Sep 17 00:00:00 2001 From: Chris Meyer Date: Wed, 21 Feb 2024 11:21:07 -0800 Subject: [PATCH] Fix #1001. Splitting multiple panels now works. --- nion/swift/DocumentController.py | 86 ++++++++++++++----------------- nion/swift/Workspace.py | 78 +++++++++++++++------------- nion/swift/resources/changes.json | 4 ++ nion/swift/test/Workspace_test.py | 13 +++++ 4 files changed, 96 insertions(+), 85 deletions(-) diff --git a/nion/swift/DocumentController.py b/nion/swift/DocumentController.py index f558d0b1c..ffb910cac 100755 --- a/nion/swift/DocumentController.py +++ b/nion/swift/DocumentController.py @@ -2682,6 +2682,7 @@ def __init__(self, focus_widget: typing.Optional[UserInterface.Widget], display_panel: typing.Optional[DisplayPanel.DisplayPanel], display_panels: typing.List[DisplayPanel.DisplayPanel], + selected_display_panel: typing.Optional[DisplayPanel.DisplayPanel], model: DocumentModel.DocumentModel, display_item: typing.Optional[DisplayItem.DisplayItem], display_items: typing.Sequence[DisplayItem.DisplayItem], @@ -2692,6 +2693,7 @@ def __init__(self, super().__init__(application, window, focus_widget) self.display_panel = display_panel self.display_panels = display_panels + self.selected_display_panel = selected_display_panel self.model = model self.display_item = display_item self.display_items = display_items @@ -2702,6 +2704,7 @@ def __init__(self, def _get_action_context(self) -> ActionContext: focus_widget = self.focus_widget + selected_display_panel = self.selected_display_panel display_panel = self.selected_display_panel if not self.__secondary_display_panels else None display_panels = ([self.__selected_display_panel] if self.__selected_display_panel else list()) + self.__secondary_display_panels model = self.document_model @@ -2716,8 +2719,9 @@ def _get_action_context(self) -> ActionContext: data_items.append(data_item_1) selected_graphics = display_item.selected_graphics if display_item else list() return DocumentController.ActionContext(typing.cast("Application.Application", self.app), self, focus_widget, - display_panel, display_panels, model, display_item, display_items, - crop_graphic, selected_graphics, data_item, data_items) + display_panel, display_panels, selected_display_panel, model, + display_item, display_items, crop_graphic, selected_graphics, data_item, + data_items) def _get_action_context_for_display_items(self, display_items: typing.Sequence[DisplayItem.DisplayItem], display_panel: typing.Optional[DisplayPanel.DisplayPanel]) -> ActionContext: focus_widget = self.focus_widget @@ -2726,6 +2730,7 @@ def _get_action_context_for_display_items(self, display_items: typing.Sequence[D # the one that was context clicked. if multiple display panels are selected and the user context clicks on one # of the selected ones, use the selected ones. if multiple display panels are selected and the user context # clicks on an unselected one, then there is no display panel selected. + selected_display_panel = self.__selected_display_panel used_display_panel = None used_display_panels: typing.List[DisplayPanel.DisplayPanel] = list() if display_panel: @@ -2756,9 +2761,9 @@ def _get_action_context_for_display_items(self, display_items: typing.Sequence[D used_data_items.append(data_item_1) selected_graphics = used_display_item.selected_graphics if used_display_item else list() return DocumentController.ActionContext(typing.cast("Application.Application", self.app), self, focus_widget, - used_display_panel, used_display_panels, model, used_display_item, - used_display_items, crop_graphic, selected_graphics, used_data_item, - used_data_items) + used_display_panel, used_display_panels, selected_display_panel, model, + used_display_item, used_display_items, crop_graphic, selected_graphics, + used_data_item, used_data_items) class GraphicFactoryBase: @@ -3483,46 +3488,6 @@ def execute(self, context: Window.ActionContext) -> Window.ActionResult: raise ValueError("Missing workspace controller") -class WorkspaceSplitHorizontalAction(Window.Action): - action_id = "workspace.split_horizontal" - action_name = _("Split Panel Into Left and Right") - action_command_icon_png = pkgutil.get_data(__name__, "resources/workspace_split_vertical.png") - - def execute(self, context: Window.ActionContext) -> Window.ActionResult: - context = typing.cast(DocumentController.ActionContext, context) - window = typing.cast(DocumentController, context.window) - workspace_controller = window.workspace_controller - display_panel = context.display_panel - if workspace_controller and display_panel: - command = workspace_controller.insert_display_panel(display_panel, "right") - window.push_undo_command(command) - return Window.ActionResult(Window.ActionStatus.FINISHED) - - def is_enabled(self, context: Window.ActionContext) -> bool: - context = typing.cast(DocumentController.ActionContext, context) - return context.display_panel is not None - - -class WorkspaceSplitVerticalAction(Window.Action): - action_id = "workspace.split_vertical" - action_name = _("Split Panel Into Top and Bottom") - action_command_icon_png = pkgutil.get_data(__name__, "resources/workspace_split_horizontal.png") - - def execute(self, context: Window.ActionContext) -> Window.ActionResult: - context = typing.cast(DocumentController.ActionContext, context) - window = typing.cast(DocumentController, context.window) - workspace_controller = window.workspace_controller - display_panel = context.display_panel - if workspace_controller and display_panel: - command = workspace_controller.insert_display_panel(display_panel, "bottom") - window.push_undo_command(command) - return Window.ActionResult(Window.ActionStatus.FINISHED) - - def is_enabled(self, context: Window.ActionContext) -> bool: - context = typing.cast(DocumentController.ActionContext, context) - return context.display_panel is not None - - class WorkspaceSplitAction(Window.Action): action_id = "workspace.split" action_name = _("Split Display Panel") @@ -3535,13 +3500,14 @@ def execute(self, context: Window.ActionContext) -> Window.ActionResult: context = typing.cast(DocumentController.ActionContext, context) window = typing.cast(DocumentController, context.window) workspace_controller = window.workspace_controller - display_panel = context.display_panel - if workspace_controller and display_panel: + display_panels = context.display_panels + selected_display_panel = context.selected_display_panel + if workspace_controller and display_panels and selected_display_panel: h = self.get_int_property(context, "horizontal_count") v = self.get_int_property(context, "vertical_count") h = max(1, min(8, h)) v = max(1, min(8, v)) - display_panels = workspace_controller.apply_layout(display_panel, h, v) + display_panels = workspace_controller.apply_layouts(selected_display_panel, display_panels, h, v) action_result = Window.ActionResult(Window.ActionStatus.FINISHED) action_result.results["display_panels"] = list(display_panels) return action_result @@ -3552,6 +3518,30 @@ def is_enabled(self, context: Window.ActionContext) -> bool: return context.display_panel is not None +class WorkspaceSplitHorizontalAction(WorkspaceSplitAction): + action_id = "workspace.split_horizontal" + action_name = _("Split Panel Into Left and Right") + action_command_icon_png = pkgutil.get_data(__name__, "resources/workspace_split_vertical.png") + + def execute(self, context: Window.ActionContext) -> Window.ActionResult: + context = typing.cast(DocumentController.ActionContext, context) + self.set_int_property(context, "horizontal_count", 2) + self.set_int_property(context, "vertical_count", 1) + return super().execute(context) + + +class WorkspaceSplitVerticalAction(WorkspaceSplitAction): + action_id = "workspace.split_vertical" + action_name = _("Split Panel Into Top and Bottom") + action_command_icon_png = pkgutil.get_data(__name__, "resources/workspace_split_horizontal.png") + + def execute(self, context: Window.ActionContext) -> Window.ActionResult: + context = typing.cast(DocumentController.ActionContext, context) + self.set_int_property(context, "horizontal_count", 1) + self.set_int_property(context, "vertical_count", 2) + return super().execute(context) + + class WorkspaceSplit2x2Action(WorkspaceSplitAction): action_id = "workspace.split_2x2" action_name = _("Split Panel 2x2") diff --git a/nion/swift/Workspace.py b/nion/swift/Workspace.py index 6a41324c3..21d88b122 100644 --- a/nion/swift/Workspace.py +++ b/nion/swift/Workspace.py @@ -994,50 +994,54 @@ def _remove_display_panel(self, display_panel: DisplayPanel.DisplayPanel, self.__sync_layout() return old_display_panel, old_splits, region_id - def apply_layout(self, display_panel: DisplayPanel.DisplayPanel, w: int, h: int) -> typing.List[DisplayPanel.DisplayPanel]: - display_panel_container = display_panel.container - assert display_panel_container + def apply_layouts(self, primary_display_panel: DisplayPanel.DisplayPanel, display_panels: typing.Sequence[DisplayPanel.DisplayPanel], w: int, h: int) -> typing.List[DisplayPanel.DisplayPanel]: assert self.__workspace change_workspace_contents_command = ChangeWorkspaceContentsCommand(self, _("Change Workspace Contents")) new_display_panels = list() - # insert the rows - row_splitter_canvas_item = CanvasItem.SplitterCanvasItem(orientation="horizontal") - row_splitter_canvas_item.on_splits_will_change = functools.partial(self._splits_will_change, row_splitter_canvas_item) - row_splitter_canvas_item.on_splits_changed = functools.partial(self._splits_did_change, row_splitter_canvas_item) - display_panel_container.wrap_canvas_item(display_panel, row_splitter_canvas_item) - new_display_panels.append(display_panel) - dest_index = self.__display_panels.index(display_panel) - for row in range(h): - if row > 0: - row_display_panel = DisplayPanel.DisplayPanel(self.document_controller, dict()) - self.__insert_display_panel(dest_index + 1, row_display_panel) - new_display_panels.append(row_display_panel) - dest_index += 1 - row_splitter_canvas_item.insert_canvas_item(row + 1, row_display_panel) - else: - row_display_panel = display_panel - - # insert the columns - column_splitter_canvas_item = CanvasItem.SplitterCanvasItem(orientation="vertical") - column_splitter_canvas_item.on_splits_will_change = functools.partial(self._splits_will_change, column_splitter_canvas_item) - column_splitter_canvas_item.on_splits_changed = functools.partial(self._splits_did_change, column_splitter_canvas_item) - row_display_panel_container = row_display_panel.container - assert row_display_panel_container - row_display_panel_container.wrap_canvas_item(row_display_panel, column_splitter_canvas_item) - for column in range(1, w): - column_display_panel = DisplayPanel.DisplayPanel(self.document_controller, dict()) - new_display_panels.append(column_display_panel) - self.__insert_display_panel(dest_index + 1, column_display_panel) - dest_index += 1 - column_splitter_canvas_item.insert_canvas_item(column + 1, column_display_panel) - column_splitter_canvas_item.splits = [1//w] * w - - row_splitter_canvas_item.splits = [1//h] * h + for display_panel in display_panels: + # find the display panel container + display_panel_container = display_panel.container + assert display_panel_container + + # insert the rows + row_splitter_canvas_item = CanvasItem.SplitterCanvasItem(orientation="horizontal") + row_splitter_canvas_item.on_splits_will_change = functools.partial(self._splits_will_change, row_splitter_canvas_item) + row_splitter_canvas_item.on_splits_changed = functools.partial(self._splits_did_change, row_splitter_canvas_item) + display_panel_container.wrap_canvas_item(display_panel, row_splitter_canvas_item) + new_display_panels.append(display_panel) + dest_index = self.__display_panels.index(display_panel) + for row in range(h): + if row > 0: + row_display_panel = DisplayPanel.DisplayPanel(self.document_controller, dict()) + self.__insert_display_panel(dest_index + 1, row_display_panel) + new_display_panels.append(row_display_panel) + dest_index += 1 + row_splitter_canvas_item.insert_canvas_item(row + 1, row_display_panel) + else: + row_display_panel = display_panel + + # insert the columns + column_splitter_canvas_item = CanvasItem.SplitterCanvasItem(orientation="vertical") + column_splitter_canvas_item.on_splits_will_change = functools.partial(self._splits_will_change, column_splitter_canvas_item) + column_splitter_canvas_item.on_splits_changed = functools.partial(self._splits_did_change, column_splitter_canvas_item) + row_display_panel_container = row_display_panel.container + assert row_display_panel_container + row_display_panel_container.wrap_canvas_item(row_display_panel, column_splitter_canvas_item) + for column in range(1, w): + column_display_panel = DisplayPanel.DisplayPanel(self.document_controller, dict()) + new_display_panels.append(column_display_panel) + self.__insert_display_panel(dest_index + 1, column_display_panel) + dest_index += 1 + column_splitter_canvas_item.insert_canvas_item(column + 1, column_display_panel) + column_splitter_canvas_item.splits = [1//w] * w + + row_splitter_canvas_item.splits = [1//h] * h self.__sync_layout() - display_panel.request_focus() + + primary_display_panel.request_focus() self.document_controller.push_undo_command(change_workspace_contents_command) diff --git a/nion/swift/resources/changes.json b/nion/swift/resources/changes.json index 4b02b2023..e911ffc29 100644 --- a/nion/swift/resources/changes.json +++ b/nion/swift/resources/changes.json @@ -6,6 +6,10 @@ "issues": ["https://github.com/nion-software/nionswift/issues/1002"], "summary": "Fix issue when removing workspace to select next most recent workspace." }, + { + "issues": ["https://github.com/nion-software/nionswift/issues/1001"], + "summary": "Fix issue when splitting display panels with multiple panels selected." + }, { "issues": ["https://github.com/nion-software/nionswift/issues/922", "https://github.com/nion-software/nionswift/issues/327"], "summary": "Make filtering work on modified date text and session metadata text when filtering data items in data panel." diff --git a/nion/swift/test/Workspace_test.py b/nion/swift/test/Workspace_test.py index 5f5196841..d589f9c0f 100644 --- a/nion/swift/test/Workspace_test.py +++ b/nion/swift/test/Workspace_test.py @@ -608,6 +608,19 @@ def test_workspace_remove_bottom_two_in_2x2_undo_and_redo_works_cleanly(self): # compare against new layout self.assertEqual(new_workspace_layout, workspace_controller._workspace_layout) + def test_workspace_split_multiple_panels(self): + with TestContext.create_memory_context() as test_context: + document_controller = test_context.create_document_controller() + workspace_controller = document_controller.workspace_controller + self.assertEqual(1, len(workspace_controller.display_panels)) + document_controller.selected_display_panel = workspace_controller.display_panels[0] + document_controller.perform_action("workspace.split_2x2") + self.assertEqual(4, len(workspace_controller.display_panels)) + document_controller.selected_display_panel = workspace_controller.display_panels[0] + document_controller.add_secondary_display_panel(workspace_controller.display_panels[1]) + document_controller.perform_action("workspace.split_vertical") + self.assertEqual(6, len(workspace_controller.display_panels)) + def test_workspace_change_workspace_to_data_thumbnail_grid_works(self): with TestContext.create_memory_context() as test_context: document_controller = test_context.create_document_controller()