From 9dd24250ec65e15d5440390113bdf5bfc2cea944 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Thu, 13 Apr 2023 18:18:50 -0400 Subject: [PATCH 1/7] Allow moving viewers between tabs. --- glue/app/qt/application.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/glue/app/qt/application.py b/glue/app/qt/application.py index e7466e165..d2ac6ea71 100644 --- a/glue/app/qt/application.py +++ b/glue/app/qt/application.py @@ -1410,6 +1410,29 @@ def screenshot(self, filename): image.save(filename) painter.end() + def move_viewer_to_tab(self, viewer, tab): + """ + Move the given viewer to the given tab. + If the given tab is the same as the current tab, + do nothing. + """ + current_window = viewer.parent() + current_tab = current_window.mdiArea() + new_tab = self.tab(tab) + if new_tab is None: + raise ValueError(f"Invalid tab index: {tab}") + if current_tab is not new_tab: + # We do this rather than just use setParent on current_window + # so that the moved window is put in a reasonable place + # in the new tab (i.e. not on top of another viewer), + # because there may be another viewer in the new tab + # with the same position + current_tab.removeSubWindow(current_window) + current_window.setWidget(None) + current_window.close() + + self.add_widget(viewer, tab=tab) + def add_datasets(self, *args, **kwargs): result = super(GlueApplication, self).add_datasets(*args, **kwargs) run_autolinker(self.data_collection) From 82721762a36f8a3abd3491a3e1badae7e3fbb946 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Thu, 13 Apr 2023 18:19:15 -0400 Subject: [PATCH 2/7] Add test for moving viewer between tabs. --- glue/app/qt/tests/test_application.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/glue/app/qt/tests/test_application.py b/glue/app/qt/tests/test_application.py index a026fde02..3d89425e1 100644 --- a/glue/app/qt/tests/test_application.py +++ b/glue/app/qt/tests/test_application.py @@ -235,6 +235,31 @@ def test_subset_facet(self): with patch('glue.dialogs.subset_facet.qt.SubsetFacetDialog.exec_'): act._do_action() + def test_move_viewer_to_tab(self): + + # Create a viewer in the first tab + viewer = self.app.new_data_viewer(ScatterViewer) + assert viewer.parent().mdiArea() is self.app.tab(0) + + # Move it to a new tab + self.app.new_tab() + self.app.move_viewer_to_tab(viewer, 1) + assert viewer.parent().mdiArea() is self.app.tab(1) + assert set(self.app.tab(0).subWindowList()) == {self.app._terminal} + assert set(self.app.tab(1).subWindowList()) == {viewer.parent()} + + # Move it back to the first tab + self.app.move_viewer_to_tab(viewer, 0) + assert viewer.parent().mdiArea() is self.app.tab(0) + assert set(self.app.tab(0).subWindowList()) == {self.app._terminal, viewer.parent()} + assert len(self.app.tab(1).subWindowList()) == 0 + + # Check that we do nothing if the given tab is the same as the + # viewer's current tab + parent = viewer.parent() + self.app.move_viewer_to_tab(viewer, 0) + assert parent is viewer.parent() + # FIXME: The following test fails and causes subsequent issues if run with # # pytest -s -v -x glue From 34ffdbe0be6f0ceabf5816d9eab49836e17c3dd3 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Thu, 13 Apr 2023 18:38:28 -0400 Subject: [PATCH 3/7] Add moving functionality to UI as a subtool of a new 'window' tool menu. --- glue/icons/windows.png | Bin 0 -> 19803 bytes glue/viewers/common/qt/__init__.py | 1 + glue/viewers/common/qt/data_viewer.py | 7 ++++-- glue/viewers/common/qt/tools.py | 32 ++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 glue/icons/windows.png create mode 100644 glue/viewers/common/qt/tools.py diff --git a/glue/icons/windows.png b/glue/icons/windows.png new file mode 100644 index 0000000000000000000000000000000000000000..d46a22eff4675536a1a2ef4d902205c522e4184d GIT binary patch literal 19803 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 literal 0 HcmV?d00001 diff --git a/glue/viewers/common/qt/__init__.py b/glue/viewers/common/qt/__init__.py index e69de29bb..678c40862 100644 --- a/glue/viewers/common/qt/__init__.py +++ b/glue/viewers/common/qt/__init__.py @@ -0,0 +1 @@ +from . import tools # noqa diff --git a/glue/viewers/common/qt/data_viewer.py b/glue/viewers/common/qt/data_viewer.py index 97ddfaa6d..7a0df1004 100644 --- a/glue/viewers/common/qt/data_viewer.py +++ b/glue/viewers/common/qt/data_viewer.py @@ -65,8 +65,11 @@ class DataViewer(Viewer, BaseQtViewerWidget, _default_mouse_mode_cls = None inherit_tools = True - tools = ['save'] - subtools = {'save': []} + tools = ['save', 'window'] + subtools = { + 'save': [], + 'window': ['window:movetab'] + } _close_on_last_layer_removed = True diff --git a/glue/viewers/common/qt/tools.py b/glue/viewers/common/qt/tools.py new file mode 100644 index 000000000..cacb11c2c --- /dev/null +++ b/glue/viewers/common/qt/tools.py @@ -0,0 +1,32 @@ +from glue.config import viewer_tool +from glue.utils.qt import pick_item +from glue.viewers.common.tool import Tool, SimpleToolMenu + +__all__ = ['MoveTabTool', 'WindowTool'] + + +@viewer_tool +class WindowTool(SimpleToolMenu): + """ + A generic "window operations" tool that the Qt app and plugins + can register tools for windowing operations with. + """ + + tool_id = 'window' + icon = 'windows' + tool_tip = 'Modify the viewer window' + + +@viewer_tool +class MoveTabTool(Tool): + + icon = 'windows' + tool_id = 'window:movetab' + action_text = 'Move to another tab' + tool_tip = 'Move viewer to another tab' + + def activate(self): + app = self.viewer.session.application + tab = pick_item(range(app.tab_count), app.tab_names, title="Move Viewer", label="Select a tab") + if tab is not None: + app.move_viewer_to_tab(self.viewer, tab) From 9c1c4fe8e9f32e22ee93c47ac1d9f3ed0a1f9fa8 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Thu, 27 Apr 2023 09:22:47 -0400 Subject: [PATCH 4/7] Test that viewer state is preserved when moving between tabs. --- glue/app/qt/tests/test_application.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/glue/app/qt/tests/test_application.py b/glue/app/qt/tests/test_application.py index 3d89425e1..c9e798874 100644 --- a/glue/app/qt/tests/test_application.py +++ b/glue/app/qt/tests/test_application.py @@ -257,8 +257,10 @@ def test_move_viewer_to_tab(self): # Check that we do nothing if the given tab is the same as the # viewer's current tab parent = viewer.parent() + viewer_state = viewer.state self.app.move_viewer_to_tab(viewer, 0) assert parent is viewer.parent() + assert viewer_state is viewer.state # FIXME: The following test fails and causes subsequent issues if run with # From d703d7ca5056ec906a2f469c838684f1746df206 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Fri, 12 May 2023 09:46:53 -0400 Subject: [PATCH 5/7] If there are multiple tabs, have default move tool choice not be current tab. --- glue/viewers/common/qt/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/glue/viewers/common/qt/tools.py b/glue/viewers/common/qt/tools.py index cacb11c2c..a8c120186 100644 --- a/glue/viewers/common/qt/tools.py +++ b/glue/viewers/common/qt/tools.py @@ -27,6 +27,7 @@ class MoveTabTool(Tool): def activate(self): app = self.viewer.session.application - tab = pick_item(range(app.tab_count), app.tab_names, title="Move Viewer", label="Select a tab") + default = 1 if (app.tab_count > 0 and app.current_tab == app.tab(0)) else 0 + tab = pick_item(range(app.tab_count), app.tab_names, title="Move Viewer", label="Select a tab", default=default) if tab is not None: app.move_viewer_to_tab(self.viewer, tab) From 02289c85e28515f10855609a513152367f93aa45 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Fri, 12 May 2023 10:45:59 -0400 Subject: [PATCH 6/7] Prevent moved viewers from getting progressively smaller. --- glue/app/qt/application.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/glue/app/qt/application.py b/glue/app/qt/application.py index d2ac6ea71..ed3ef86b7 100644 --- a/glue/app/qt/application.py +++ b/glue/app/qt/application.py @@ -1422,12 +1422,18 @@ def move_viewer_to_tab(self, viewer, tab): if new_tab is None: raise ValueError(f"Invalid tab index: {tab}") if current_tab is not new_tab: + # We do this rather than just use setParent on current_window # so that the moved window is put in a reasonable place # in the new tab (i.e. not on top of another viewer), # because there may be another viewer in the new tab - # with the same position + # with the same position. + # Also, if we don't resize, moved windows will get progressively + # smaller. This is because the new MDI window will be sized + # according to the size of the old viewer, which is slightly + # smaller than the parent window. current_tab.removeSubWindow(current_window) + viewer.resize(current_window.size()) current_window.setWidget(None) current_window.close() From fde4e3b4afc786bae6bd0e303d6845aebdd255e5 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Fri, 12 May 2023 10:48:44 -0400 Subject: [PATCH 7/7] Fix a typo in the tab count check. --- glue/viewers/common/qt/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glue/viewers/common/qt/tools.py b/glue/viewers/common/qt/tools.py index a8c120186..afe92f06f 100644 --- a/glue/viewers/common/qt/tools.py +++ b/glue/viewers/common/qt/tools.py @@ -27,7 +27,7 @@ class MoveTabTool(Tool): def activate(self): app = self.viewer.session.application - default = 1 if (app.tab_count > 0 and app.current_tab == app.tab(0)) else 0 + default = 1 if (app.tab_count > 1 and app.current_tab == app.tab(0)) else 0 tab = pick_item(range(app.tab_count), app.tab_names, title="Move Viewer", label="Select a tab", default=default) if tab is not None: app.move_viewer_to_tab(self.viewer, tab)