diff --git a/resources/long_left_arrow.svg b/resources/long_left_arrow.svg new file mode 100644 index 00000000..4bdd4bbb --- /dev/null +++ b/resources/long_left_arrow.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/resources/long_right_arrow.svg b/resources/long_right_arrow.svg new file mode 100644 index 00000000..47b2a880 --- /dev/null +++ b/resources/long_right_arrow.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/src/napari_imagej/widget_IJ2.py b/src/napari_imagej/widget_IJ2.py index 486cc0f0..1445ca44 100644 --- a/src/napari_imagej/widget_IJ2.py +++ b/src/napari_imagej/widget_IJ2.py @@ -1,42 +1,61 @@ from enum import Enum +from threading import Thread from typing import List from magicgui.widgets import request_values from napari import Viewer +from napari._qt.qt_resources import QColoredSVGIcon from napari.layers import Layer from qtpy.QtCore import Qt from qtpy.QtGui import QIcon, QPixmap -from qtpy.QtWidgets import QMessageBox, QPushButton, QVBoxLayout, QWidget +from qtpy.QtWidgets import QHBoxLayout, QMessageBox, QPushButton, QWidget from napari_imagej._module_utils import _get_layers_hack -from napari_imagej.setup_imagej import ij, jc, running_headless +from napari_imagej.setup_imagej import ensure_jvm_started, ij, jc, running_headless class GUIWidget(QWidget): def __init__(self, viewer: Viewer): super().__init__() - self.setLayout(QVBoxLayout()) + self.setLayout(QHBoxLayout()) + + self.from_ij: FromIJButton = FromIJButton(viewer) + self.layout().addWidget(self.from_ij) + + self.to_ij: ToIJButton = ToIJButton(viewer) + self.layout().addWidget(self.to_ij) self.gui_button: GUIButton = GUIButton() self.layout().addWidget(self.gui_button) - self.to_ij: ToIJButton = ToIJButton() - self.layout().addWidget(self.to_ij) + if running_headless(): + self.gui_button.clicked.connect(self.gui_button.disable_popup) + else: + self.gui_button.clicked.connect(self._showUI) + self.gui_button.clicked.connect(lambda: self.to_ij.setEnabled(True)) + self.gui_button.clicked.connect(lambda: self.from_ij.setEnabled(True)) - self.from_ij: FromIJButton = FromIJButton(viewer) - self.layout().addWidget(self.from_ij) - if not running_headless(): - self.gui_button.clicked.connect(lambda: self.to_ij.setHidden(False)) - self.gui_button.clicked.connect(lambda: self.from_ij.setHidden(False)) + def _showUI(self): + """ + NB: This must be its own function to prevent premature calling of ij() + """ + ensure_jvm_started() + ij().ui().showUI() class ToIJButton(QPushButton): - def __init__(self): + def __init__(self, viewer): super().__init__() - self.setHidden(True) - self.setText("Send layers to ImageJ2") + self.setEnabled(False) + icon = QColoredSVGIcon.from_resources("long_right_arrow") + self.setIcon(icon.colored(theme=viewer.theme)) + self.setToolTip("Send layers to ImageJ2") self.clicked.connect(self.send_layers) + def _set_icon(self, path: str): + icon: QIcon = QIcon(QPixmap(path)) + self.setIcon(icon) + def send_layers(self): layers: dict = request_values( title="Send layers to ImageJ2", @@ -55,10 +74,16 @@ def __init__(self, viewer: Viewer): super().__init__() self.viewer = viewer - self.setHidden(True) - self.setText("Get layers from ImageJ2") + self.setEnabled(False) + icon = QColoredSVGIcon.from_resources("long_left_arrow") + self.setIcon(icon.colored(theme=viewer.theme)) + self.setToolTip("Get layers from ImageJ2") self.clicked.connect(self.get_layers) + def _set_icon(self, path: str): + icon: QIcon = QIcon(QPixmap(path)) + self.setIcon(icon) + def _get_objects(self, t): compatibleInputs = ij().convert().getCompatibleInputs(t) compatibleInputs.addAll(ij().object().getObjects(t)) @@ -87,28 +112,30 @@ def get_layers(self) -> List[Layer]: class GUIButton(QPushButton): def __init__(self): super().__init__() - self._text = "Display ImageJ2 GUI" - - if running_headless(): - self._setup_headless() - else: + running_headful = not running_headless() + self.setEnabled(False) + if running_headful: self._setup_headful() + else: + self._setup_headless() def _set_icon(self, path: str): icon: QIcon = QIcon(QPixmap(path)) self.setIcon(icon) def _setup_headful(self): - self._set_icon("resources/16x16-flat.png") - self.setText(self._text) - self.setToolTip("Open ImageJ2 in a new window!") - self.clicked.connect(ij().ui().showUI) + self._set_icon("resources/16x16-flat-disabled.png") + self.setToolTip("Display ImageJ2 GUI (loading)") + def post_setup(): + ensure_jvm_started() + self._set_icon("resources/16x16-flat.png") + self.setEnabled(True) + self.setToolTip("Display ImageJ2 GUI") + Thread(target=post_setup).start() def _setup_headless(self): self._set_icon("resources/16x16-flat-disabled.png") - self.setText(self._text + " (disabled)") - self.setToolTip("Not available when running PyImageJ headlessly!") - self.clicked.connect(self.disable_popup) + self.setToolTip("ImageJ2 GUI unavailable!") def disable_popup(self): msg: QMessageBox = QMessageBox() diff --git a/tests/test_IJ2GUIWidget.py b/tests/test_IJ2GUIWidget.py index aaa1d7e0..dd2ea523 100644 --- a/tests/test_IJ2GUIWidget.py +++ b/tests/test_IJ2GUIWidget.py @@ -4,7 +4,7 @@ from napari.viewer import current_viewer from qtpy.QtCore import Qt, QTimer from qtpy.QtGui import QPixmap -from qtpy.QtWidgets import QApplication, QDialog, QMessageBox, QPushButton, QVBoxLayout +from qtpy.QtWidgets import QApplication, QDialog, QMessageBox, QPushButton, QHBoxLayout from napari_imagej.setup_imagej import JavaClasses, running_headless from napari_imagej.widget_IJ2 import FromIJButton, GUIButton, GUIWidget, ToIJButton @@ -62,10 +62,10 @@ def test_widget_layout(gui_widget: GUIWidget): """Tests the number and expected order of imagej_widget children""" subwidgets = gui_widget.children() assert len(subwidgets) == 4 - assert isinstance(subwidgets[0], QVBoxLayout) - assert isinstance(subwidgets[1], GUIButton) + assert isinstance(subwidgets[0], QHBoxLayout) + assert isinstance(subwidgets[1], FromIJButton) assert isinstance(subwidgets[2], ToIJButton) - assert isinstance(subwidgets[3], FromIJButton) + assert isinstance(subwidgets[3], GUIButton) @pytest.mark.skipif( @@ -79,10 +79,9 @@ def test_GUIButton_layout_headful(qtbot, ij, gui_widget: GUIWidget): actual: QPixmap = button.icon().pixmap(expected.size()) assert expected.toImage() == actual.toImage() - expected_text = "Display ImageJ2 GUI" - assert expected_text == button.text() + assert "" == button.text() - expected_toolTip = "Open ImageJ2 in a new window!" + expected_toolTip = "Display ImageJ2 GUI" assert expected_toolTip == button.toolTip() # Test showing UI @@ -102,11 +101,10 @@ def test_GUIButton_layout_headless(qtbot, gui_widget: GUIWidget): actual: QPixmap = button.icon().pixmap(expected.size()) assert expected.toImage() == actual.toImage() - expected_text = "Display ImageJ2 GUI (disabled)" - assert expected_text == button.text() + assert "" == button.text() - expected_toolTip = "Not available when running PyImageJ headlessly!" - assert expected_toolTip == button.toolTip() + expected_text = "ImageJ2 GUI unavailable!" + assert expected_text == button.toolTip() # Test popup when running headlessly def handle_dialog(): @@ -137,11 +135,11 @@ def handle_dialog(): ) def test_data_to_ImageJ(qtbot, ij, gui_widget: GUIWidget): button: ToIJButton = gui_widget.to_ij - assert button.isHidden() + assert not button.isEnabled() # Show the button qtbot.mouseClick(gui_widget.gui_button, Qt.LeftButton, delay=1) - qtbot.waitUntil(lambda: not button.isHidden()) + qtbot.waitUntil(lambda: button.isEnabled()) # Add some data to the viewer sample_data = numpy.ones((100, 100, 3)) @@ -174,11 +172,11 @@ def handle_dialog(): ) def test_data_from_ImageJ(qtbot, ij, gui_widget: GUIWidget): button: FromIJButton = gui_widget.from_ij - assert button.isHidden() + assert not button.isEnabled() # Show the button qtbot.mouseClick(gui_widget.gui_button, Qt.LeftButton, delay=1) - qtbot.waitUntil(lambda: not button.isHidden()) + qtbot.waitUntil(lambda: button.isEnabled()) # Add some data to ImageJ sample_data = jc.ArrayImgs.bytes(10, 10, 10)