Skip to content

Commit dc82852

Browse files
committed
Improve buttons
Now, starting up the buttons DOESN'T block until ImageJ is ready! Also, we switch to icons only since it's more napari-like
1 parent 1f8a7c7 commit dc82852

File tree

4 files changed

+81
-42
lines changed

4 files changed

+81
-42
lines changed

resources/long_left_arrow.svg

Lines changed: 7 additions & 0 deletions
Loading

resources/long_right_arrow.svg

Lines changed: 7 additions & 0 deletions
Loading

src/napari_imagej/widget_IJ2.py

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,61 @@
11
from enum import Enum
2+
from threading import Thread
23
from typing import List
34

45
from magicgui.widgets import request_values
56
from napari import Viewer
7+
from napari._qt.qt_resources import QColoredSVGIcon
68
from napari.layers import Layer
79
from qtpy.QtCore import Qt
810
from qtpy.QtGui import QIcon, QPixmap
9-
from qtpy.QtWidgets import QMessageBox, QPushButton, QVBoxLayout, QWidget
11+
from qtpy.QtWidgets import QHBoxLayout, QMessageBox, QPushButton, QWidget
1012

1113
from napari_imagej._module_utils import _get_layers_hack
12-
from napari_imagej.setup_imagej import ij, jc, running_headless
14+
from napari_imagej.setup_imagej import ensure_jvm_started, ij, jc, running_headless
1315

1416

1517
class GUIWidget(QWidget):
1618
def __init__(self, viewer: Viewer):
1719
super().__init__()
18-
self.setLayout(QVBoxLayout())
20+
self.setLayout(QHBoxLayout())
21+
22+
self.from_ij: FromIJButton = FromIJButton(viewer)
23+
self.layout().addWidget(self.from_ij)
24+
25+
self.to_ij: ToIJButton = ToIJButton(viewer)
26+
self.layout().addWidget(self.to_ij)
1927

2028
self.gui_button: GUIButton = GUIButton()
2129
self.layout().addWidget(self.gui_button)
2230

23-
self.to_ij: ToIJButton = ToIJButton()
24-
self.layout().addWidget(self.to_ij)
31+
if running_headless():
32+
self.gui_button.clicked.connect(self.gui_button.disable_popup)
33+
else:
34+
self.gui_button.clicked.connect(self._showUI)
35+
self.gui_button.clicked.connect(lambda: self.to_ij.setEnabled(True))
36+
self.gui_button.clicked.connect(lambda: self.from_ij.setEnabled(True))
2537

26-
self.from_ij: FromIJButton = FromIJButton(viewer)
27-
self.layout().addWidget(self.from_ij)
28-
if not running_headless():
29-
self.gui_button.clicked.connect(lambda: self.to_ij.setHidden(False))
30-
self.gui_button.clicked.connect(lambda: self.from_ij.setHidden(False))
38+
def _showUI(self):
39+
"""
40+
NB: This must be its own function to prevent premature calling of ij()
41+
"""
42+
ensure_jvm_started()
43+
ij().ui().showUI()
3144

3245

3346
class ToIJButton(QPushButton):
34-
def __init__(self):
47+
def __init__(self, viewer):
3548
super().__init__()
36-
self.setHidden(True)
37-
self.setText("Send layers to ImageJ2")
49+
self.setEnabled(False)
50+
icon = QColoredSVGIcon.from_resources("long_right_arrow")
51+
self.setIcon(icon.colored(theme=viewer.theme))
52+
self.setToolTip("Send layers to ImageJ2")
3853
self.clicked.connect(self.send_layers)
3954

55+
def _set_icon(self, path: str):
56+
icon: QIcon = QIcon(QPixmap(path))
57+
self.setIcon(icon)
58+
4059
def send_layers(self):
4160
layers: dict = request_values(
4261
title="Send layers to ImageJ2",
@@ -55,10 +74,16 @@ def __init__(self, viewer: Viewer):
5574
super().__init__()
5675
self.viewer = viewer
5776

58-
self.setHidden(True)
59-
self.setText("Get layers from ImageJ2")
77+
self.setEnabled(False)
78+
icon = QColoredSVGIcon.from_resources("long_left_arrow")
79+
self.setIcon(icon.colored(theme=viewer.theme))
80+
self.setToolTip("Get layers from ImageJ2")
6081
self.clicked.connect(self.get_layers)
6182

83+
def _set_icon(self, path: str):
84+
icon: QIcon = QIcon(QPixmap(path))
85+
self.setIcon(icon)
86+
6287
def _get_objects(self, t):
6388
compatibleInputs = ij().convert().getCompatibleInputs(t)
6489
compatibleInputs.addAll(ij().object().getObjects(t))
@@ -87,28 +112,30 @@ def get_layers(self) -> List[Layer]:
87112
class GUIButton(QPushButton):
88113
def __init__(self):
89114
super().__init__()
90-
self._text = "Display ImageJ2 GUI"
91-
92-
if running_headless():
93-
self._setup_headless()
94-
else:
115+
running_headful = not running_headless()
116+
self.setEnabled(False)
117+
if running_headful:
95118
self._setup_headful()
119+
else:
120+
self._setup_headless()
96121

97122
def _set_icon(self, path: str):
98123
icon: QIcon = QIcon(QPixmap(path))
99124
self.setIcon(icon)
100125

101126
def _setup_headful(self):
102-
self._set_icon("resources/16x16-flat.png")
103-
self.setText(self._text)
104-
self.setToolTip("Open ImageJ2 in a new window!")
105-
self.clicked.connect(ij().ui().showUI)
127+
self._set_icon("resources/16x16-flat-disabled.png")
128+
self.setToolTip("Display ImageJ2 GUI (loading)")
129+
def post_setup():
130+
ensure_jvm_started()
131+
self._set_icon("resources/16x16-flat.png")
132+
self.setEnabled(True)
133+
self.setToolTip("Display ImageJ2 GUI")
134+
Thread(target=post_setup).start()
106135

107136
def _setup_headless(self):
108137
self._set_icon("resources/16x16-flat-disabled.png")
109-
self.setText(self._text + " (disabled)")
110-
self.setToolTip("Not available when running PyImageJ headlessly!")
111-
self.clicked.connect(self.disable_popup)
138+
self.setToolTip("ImageJ2 GUI unavailable!")
112139

113140
def disable_popup(self):
114141
msg: QMessageBox = QMessageBox()

tests/test_IJ2GUIWidget.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from napari.viewer import current_viewer
55
from qtpy.QtCore import Qt, QTimer
66
from qtpy.QtGui import QPixmap
7-
from qtpy.QtWidgets import QApplication, QDialog, QMessageBox, QPushButton, QVBoxLayout
7+
from qtpy.QtWidgets import QApplication, QDialog, QMessageBox, QPushButton, QHBoxLayout
88

99
from napari_imagej.setup_imagej import JavaClasses, running_headless
1010
from napari_imagej.widget_IJ2 import FromIJButton, GUIButton, GUIWidget, ToIJButton
@@ -62,10 +62,10 @@ def test_widget_layout(gui_widget: GUIWidget):
6262
"""Tests the number and expected order of imagej_widget children"""
6363
subwidgets = gui_widget.children()
6464
assert len(subwidgets) == 4
65-
assert isinstance(subwidgets[0], QVBoxLayout)
66-
assert isinstance(subwidgets[1], GUIButton)
65+
assert isinstance(subwidgets[0], QHBoxLayout)
66+
assert isinstance(subwidgets[1], FromIJButton)
6767
assert isinstance(subwidgets[2], ToIJButton)
68-
assert isinstance(subwidgets[3], FromIJButton)
68+
assert isinstance(subwidgets[3], GUIButton)
6969

7070

7171
@pytest.mark.skipif(
@@ -79,10 +79,9 @@ def test_GUIButton_layout_headful(qtbot, ij, gui_widget: GUIWidget):
7979
actual: QPixmap = button.icon().pixmap(expected.size())
8080
assert expected.toImage() == actual.toImage()
8181

82-
expected_text = "Display ImageJ2 GUI"
83-
assert expected_text == button.text()
82+
assert "" == button.text()
8483

85-
expected_toolTip = "Open ImageJ2 in a new window!"
84+
expected_toolTip = "Display ImageJ2 GUI"
8685
assert expected_toolTip == button.toolTip()
8786

8887
# Test showing UI
@@ -102,11 +101,10 @@ def test_GUIButton_layout_headless(qtbot, gui_widget: GUIWidget):
102101
actual: QPixmap = button.icon().pixmap(expected.size())
103102
assert expected.toImage() == actual.toImage()
104103

105-
expected_text = "Display ImageJ2 GUI (disabled)"
106-
assert expected_text == button.text()
104+
assert "" == button.text()
107105

108-
expected_toolTip = "Not available when running PyImageJ headlessly!"
109-
assert expected_toolTip == button.toolTip()
106+
expected_text = "ImageJ2 GUI unavailable!"
107+
assert expected_text == button.toolTip()
110108

111109
# Test popup when running headlessly
112110
def handle_dialog():
@@ -137,11 +135,11 @@ def handle_dialog():
137135
)
138136
def test_data_to_ImageJ(qtbot, ij, gui_widget: GUIWidget):
139137
button: ToIJButton = gui_widget.to_ij
140-
assert button.isHidden()
138+
assert not button.isEnabled()
141139

142140
# Show the button
143141
qtbot.mouseClick(gui_widget.gui_button, Qt.LeftButton, delay=1)
144-
qtbot.waitUntil(lambda: not button.isHidden())
142+
qtbot.waitUntil(lambda: button.isEnabled())
145143

146144
# Add some data to the viewer
147145
sample_data = numpy.ones((100, 100, 3))
@@ -174,11 +172,11 @@ def handle_dialog():
174172
)
175173
def test_data_from_ImageJ(qtbot, ij, gui_widget: GUIWidget):
176174
button: FromIJButton = gui_widget.from_ij
177-
assert button.isHidden()
175+
assert not button.isEnabled()
178176

179177
# Show the button
180178
qtbot.mouseClick(gui_widget.gui_button, Qt.LeftButton, delay=1)
181-
qtbot.waitUntil(lambda: not button.isHidden())
179+
qtbot.waitUntil(lambda: button.isEnabled())
182180

183181
# Add some data to ImageJ
184182
sample_data = jc.ArrayImgs.bytes(10, 10, 10)

0 commit comments

Comments
 (0)