Skip to content

Commit

Permalink
Improve buttons
Browse files Browse the repository at this point in the history
Now, starting up the buttons DOESN'T block until ImageJ is ready!

Also, we switch to icons only since it's more napari-like
  • Loading branch information
gselzer committed Aug 15, 2022
1 parent 1f8a7c7 commit dc82852
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 42 deletions.
7 changes: 7 additions & 0 deletions resources/long_left_arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions resources/long_right_arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 54 additions & 27 deletions src/napari_imagej/widget_IJ2.py
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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))
Expand Down Expand Up @@ -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()
Expand Down
28 changes: 13 additions & 15 deletions tests/test_IJ2GUIWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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():
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit dc82852

Please sign in to comment.