Skip to content

Commit 00ab360

Browse files
committed
UX adjustments for stream mode
- Flyto path when double-clicking on it in PathManagerUI - Warn when imported files don't match image dimensions
1 parent 20b9606 commit 00ab360

3 files changed

Lines changed: 98 additions & 15 deletions

File tree

src/main/java/sc/fiji/snt/PathManagerUI.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,22 @@ public void mousePressed(final MouseEvent me) {
508508
handleMouseEvent(me);
509509
}
510510

511+
@Override
512+
public void mouseClicked(final MouseEvent me) {
513+
if (me.getClickCount() != 2 || !SwingUtilities.isLeftMouseButton(me)) return;
514+
final TreePath tp = tree.getPathForLocation(me.getX(), me.getY());
515+
if (tp == null) return;
516+
final DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
517+
if (node.isRoot()) return;
518+
if (plugin.isStreamMode()) {
519+
navToolbar.zoomToBoundingBox(List.of((Path) node.getUserObject()));
520+
} else if (tree.isExpanded(tp)) {
521+
tree.collapsePath(tp);
522+
} else {
523+
tree.expandPath(tp);
524+
}
525+
}
526+
511527
private void handleMouseEvent(final MouseEvent e) {
512528
if (!e.isConsumed() && tree.getRowForLocation(e.getX(), e.getY()) == -1) {
513529
tree.clearSelection(); // Deselect when clicking on 'empty space'
@@ -1678,6 +1694,9 @@ public HelpfulJTree() {
16781694
getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
16791695
setExpandsSelectedPaths(true);
16801696
setScrollsOnExpand(true);
1697+
// Native double-click toggle is replaced by our own handler below, so that in
1698+
// stream mode double-click can zoom to the path instead of expanding/collapsing
1699+
setToggleClickCount(0);
16811700
setRowHeight(getFontMetrics(getFont()).getHeight()); // otherwise viewport too small!?
16821701
searchable = new TreeSearchable(this);
16831702
final Timer timer = new Timer(400, ev -> getSNT().getUI().getRecorder(false)
@@ -5962,11 +5981,22 @@ private boolean ensureNodeValues(final Collection<Path> paths) {
59625981
private JButton zoomToPathsButton() {
59635982
final JButton button = new JButton(IconFactory.buttonIcon(IconFactory.GLYPH.SEARCH_PLUS, 1f));
59645983
button.setActionCommand("Zoom To Selected Paths");
5965-
button.addActionListener( e -> zoomToBoundingBox(getSelectedPathsUsingToolbarOptions(true)));
5984+
button.addActionListener( e -> {
5985+
final Collection<Path> paths = getSelectedPathsUsingToolbarOptions(true);
5986+
zoomToBoundingBox(paths);
5987+
if (paths != null && paths.size()==1) doubleClickZoomTipIfNeeded();
5988+
});
59665989
button.setToolTipText("Zoom to selected path(s)");
59675990
return button;
59685991
}
59695992

5993+
private void doubleClickZoomTipIfNeeded() {
5994+
if (!plugin.isStreamMode() || plugin.getPrefs().getTemp("stream-dclick-zoom-tip-skipnag", false)) return;
5995+
final Boolean skipNag = guiUtils.getPersistentWarning("Tip: Double-click a single path in the list "
5996+
+ "to zoom to it directly, without using this button.", "Navigation Tip");
5997+
if (skipNag != null) plugin.getPrefs().setTemp("stream-dclick-zoom-tip-skipnag", skipNag);
5998+
}
5999+
59706000
private JButton bookmarkButton() {
59716001
final ActionListener action = e -> {
59726002
final Collection<Path> paths = getSelectedPathsUsingToolbarOptions(true);

src/main/java/sc/fiji/snt/SNTUI.java

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public class SNTUI extends JDialog {
140140

141141
// UI controls promoted from Options tab
142142
private JCheckBox diametersCheckBox; // used for for quick-toggle access
143-
private JPanel optionsPanel; // Options tab group holding diametersCheckBox and its sub-panels. //
143+
private final JPanel optionsPanel; // Options tab group holding diametersCheckBox and its sub-panels. //
144144

145145
private final SNTCommandFinder commandFinder;
146146
private ActiveWorker activeWorker;
@@ -1398,7 +1398,7 @@ private void initializeStates() {
13981398
states.put(CALCULATING_HESSIAN_II,simpleState(CALCULATING_HESSIAN_II,"Calculating Hessian (II Image)..", "Computing Hessian (secondary image)...", true, null));
13991399
states.put(WAITING_FOR_SIGMA_POINT_I, simpleState(WAITING_FOR_SIGMA_POINT_I, "Click on a representative structure...", "Adjusting Hessian (main image)...", false, null));
14001400
states.put(WAITING_FOR_SIGMA_CHOICE, simpleState(WAITING_FOR_SIGMA_CHOICE, "Close 'Pick Sigma &amp; Max' to continue...", null, false, null));
1401-
states.put(LOADING, simpleState(LOADING, "Loading...", null, true, null));
1401+
states.put(LOADING, simpleState(LOADING, "Loading data...", null, true, null));
14021402
states.put(SAVING, simpleState(SAVING, "Saving...", null, true, null));
14031403
states.put(EDITING, new EditingState());
14041404
states.put(SNT_PAUSED, new SntPausedState());
@@ -2079,18 +2079,8 @@ private void validateImgDimensions() {
20792079
if (plugin.getPrefs().getTemp(SNTPrefs.RESIZE_REQUIRED, false)) {
20802080
final boolean nag = plugin.getPrefs().getTemp("canvasResize-nag", true);
20812081
if (nag) {
2082-
final StringBuilder sb = new StringBuilder("Some nodes are being displayed outside the image canvas. To visualize them you can:<ul>");
2083-
String type = "canvas";
2084-
if (plugin.accessToValidImageData()) {
2085-
type = "image";
2086-
sb.append("<li>Use IJ's command Image&rarr;Adjust&rarr;Canvas Size... and press <i>Reload</i> in the Data Source widget of the Options pane</li>");
2087-
sb.append("<li>Close the current image and create a Display Canvas using <i>Create Canvas</i> in the Options pane</li>");
2088-
}
2089-
else {
2090-
sb.append("<li>Use the <i>Create/Resize Canvas</i> commands in the Options pane</li>");
2091-
}
2092-
sb.append("<li>Replace the current ").append(type).append(" using File&rarr;Choose Tracing Image...</li>");
2093-
final Boolean userPrompt = guiUtils.getPersistentWarning(sb.toString(), "Image Needs Resizing");
2082+
final Boolean userPrompt = guiUtils.getPersistentWarning(mismatchedImageWarningMsg(),
2083+
"Mismatched Image Dimensions");
20942084
if (userPrompt != null) // do nothing if user dismissed the dialog
20952085
plugin.getPrefs().setTemp("canvasResize-nag", !userPrompt);
20962086
} else {
@@ -2099,6 +2089,24 @@ private void validateImgDimensions() {
20992089
}
21002090
}
21012091

2092+
private String mismatchedImageWarningMsg() {
2093+
if (plugin.isStreamMode()) {
2094+
return "Some nodes are being displayed outside the image volume: Perhaps reconstruction(s) have been loaded from mismatched file(s)?";
2095+
}
2096+
final StringBuilder sb = new StringBuilder("Some nodes are being displayed outside the image canvas. To visualize them you can:<ul>");
2097+
String type = "canvas";
2098+
if (plugin.accessToValidImageData()) {
2099+
type = "image";
2100+
sb.append("<li>Use IJ's command Image&rarr;Adjust&rarr;Canvas Size... and press <i>Reload</i> in the Data Source widget of the Options pane</li>");
2101+
sb.append("<li>Close the current image and create a Display Canvas using <i>Create Canvas</i> in the Options pane</li>");
2102+
}
2103+
else {
2104+
sb.append("<li>Use the <i>Create/Resize Canvas</i> commands in the Options pane</li>");
2105+
}
2106+
sb.append("<li>Replace the current ").append(type).append(" using File&rarr;Choose Tracing Image...</li>");
2107+
return sb.toString();
2108+
}
2109+
21022110
private void updateSinglePaneFlag() {
21032111
if (plugin.getImagePlus(MultiDThreePanes.XZ_PLANE) == null
21042112
&& plugin.getImagePlus(MultiDThreePanes.ZY_PLANE) == null)

src/main/java/sc/fiji/snt/gui/cmds/BigDataLoaderCmd.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import sc.fiji.snt.gui.GuiUtils;
4848
import sc.fiji.snt.gui.ScriptInstaller;
4949
import sc.fiji.snt.io.SpimDataUtils;
50+
import sc.fiji.snt.util.BoundingBox;
5051
import sc.fiji.snt.util.ImgUtils;
5152
import sc.fiji.snt.util.SNTColor;
5253
import sc.fiji.snt.viewer.AbstractBigViewer;
@@ -723,7 +724,51 @@ private void loadReconstructions(final AbstractBigViewer viewer) {
723724
if (viewer.getSNT() != null) { // tracing capabilities present
724725
final PathAndFillManager pafm = viewer.getSNT().getPathAndFillManager();
725726
trees.forEach(tree -> pafm.addTree(tree, tree.getLabel())); // registers as editable Paths
727+
// addTree() (unlike addTrees()) intentionally skips this check (see its javadoc), so trigger
728+
// it explicitly here. If a SNTUI exists, reuse its own persistent-warning dialog (same one
729+
// shown by traditional-mode reconstruction imports) instead of a one-off dialog of our own
730+
pafm.validateImageDimensions();
731+
if (viewer.getSNT().getUI() != null) {
732+
try {
733+
viewer.getSNT().getUI().runCommand("validateImgDimensions");
734+
} catch (final IllegalArgumentException ignored) {
735+
// command unavailable in the current UI state; RESIZE_REQUIRED (set above, if
736+
// applicable) remains armed and will surface on the next reconstruction import
737+
}
738+
}
739+
} else {
740+
// No SNT/PathAndFillManager in this case (plain, non-tracing viewer): compare directly
741+
// against the viewer's own loaded volume instead
742+
warnIfOutOfBounds(viewer, trees);
743+
}
744+
}
745+
746+
/**
747+
* Warns (once, with a permanent opt-out) if {@code trees} fall at least partially outside
748+
* {@code viewer}'s loaded volume -- typically a sign that the reconstruction and image files
749+
* specified are not a matching pair. Only used for the plain (non-tracing) viewer case; when
750+
* tracing capabilities are present, {@link PathAndFillManager#validateImageDimensions()} plus
751+
* {@code SNTUI}'s own dialog (see {@link #loadReconstructions}) is used instead
752+
*/
753+
private void warnIfOutOfBounds(final AbstractBigViewer viewer, final Collection<Tree> trees) {
754+
final BoundingBox volumeBox = viewer.getBoundingBox();
755+
if (volumeBox == null) return;
756+
BoundingBox treesBox = null;
757+
for (final Tree tree : trees) {
758+
final BoundingBox tb = tree.getBoundingBox(true);
759+
if (tb == null) continue;
760+
if (treesBox == null) treesBox = tb.clone();
761+
else treesBox.combine(tb);
762+
}
763+
if (treesBox == null || volumeBox.contains(treesBox)
764+
|| prefService.getBoolean(BigDataLoaderCmd.class, "oob-skipnag", false)) {
765+
return;
726766
}
767+
final Boolean skipNag = new GuiUtils(null).getPersistentWarning(
768+
"The loaded reconstruction(s) fall (at least partially) outside the loaded volume. "
769+
+ "This typically indicates the reconstruction and image are not a matching pair.",
770+
"Reconstruction Outside Image Bounds");
771+
if (skipNag != null) prefService.put(BigDataLoaderCmd.class, "oob-skipnag", skipNag);
727772
}
728773

729774
/**

0 commit comments

Comments
 (0)