Skip to content

Commit 8a5ee9e

Browse files
committed
Stream mode: Use spacebar to toggle tracing...
In detail: - Press spacebar to toggle between tracing and navigation - Hold spacebar down to temporarily switch to navigation, then release to resume tracing
1 parent 5ab5c64 commit 8a5ee9e

5 files changed

Lines changed: 173 additions & 17 deletions

File tree

‎src/main/java/sc/fiji/snt/gui/IconFactory.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public enum GLYPH {
8282
CHILD('\uf1ae', true), //
8383
CHILDREN('\ue4e1', true), //
8484
CIRCLE('\uf192', false), //
85+
CIRCLE_CHEVRON_RIGHT('\uf138', true),//
8586
CIRCLE_RIGHT('\uf35a', true), //
8687
CIRCLE_XMARK('\uf057', false), //
8788
CLOCK_ROTATE_LEFT('\uf1da', true), //

‎src/main/java/sc/fiji/snt/viewer/AbstractBigViewer.java‎

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ public abstract class AbstractBigViewer {
154154
protected JProgressBar tracingStatusBar;
155155
protected JButton tracingCancelButton;
156156
protected JButton tracingUndoButton;
157+
protected JToggleButton tracingExtendButton;
157158
protected JToggleButton secondaryLayerIndicator; // Persistent indicator of SNT#isTracingOnSecondaryImageActive()
158159

159160
protected AbstractBigViewer() {
@@ -1058,6 +1059,14 @@ protected JComponent tracingStatusRow(final Actions actions, final AbstractTrace
10581059
IconFactory.assignIcon(tracingUndoButton, IconFactory.GLYPH.UNDO, (Color) null, .9f);
10591060
tracingUndoButton.setToolTipText("<HTML>Tracing: Undo last segment (or press Z)");
10601061
tracer.updateUndoButtonState();
1062+
tracingExtendButton = new JToggleButton(tracer.getExtendSelectedPathAction());
1063+
tracingExtendButton.setText(null);
1064+
IconFactory.assignIcon(tracingExtendButton, IconFactory.GLYPH.CIRCLE_CHEVRON_RIGHT, IconFactory.GLYPH.CIRCLE_CHEVRON_RIGHT);
1065+
tracingExtendButton.setToolTipText("Continue extending the selected path");
1066+
// Same reasoning as b0/b1/b2 in Bvv#sntToolbar/Bdv#sntAnnotationsCard: non-focusable so
1067+
// Space (hold-to-toggle-tracing) isn't also captured as this button's own activation key
1068+
tracingExtendButton.setFocusable(false);
1069+
tracer.installExtendPathButton(tracingExtendButton);
10611070
secondaryLayerIndicator = new JToggleButton();
10621071
secondaryLayerIndicator.setEnabled(snt != null);
10631072
secondaryLayerIndicator.setSelected(snt != null && snt.isTracingOnSecondaryImageActive());
@@ -1068,6 +1077,8 @@ protected JComponent tracingStatusRow(final Actions actions, final AbstractTrace
10681077
row.setFloatable(false);
10691078
row.add(tracingUndoButton);
10701079
row.addSeparator();
1080+
row.add(tracingExtendButton);
1081+
row.addSeparator();
10711082
row.add(secondaryLayerIndicator);
10721083
row.addSeparator();
10731084
row.add(tracingStatusBar);
@@ -2056,12 +2067,31 @@ protected abstract class AbstractTracer extends MouseAdapter {
20562067
// #installExtendPathButton
20572068
private JToggleButton extendPathButton;
20582069

2059-
// Whether a global SNT_PAUSED/TRACING_PAUSED state is currently forcing "No tracing" here (see
2060-
// #setLockedByPause). While true, manualTracingButton/interactiveTracingButton are disabled
2061-
// outright - not just deselected - so the toolbar can't show tracing as active while every click
2062-
// is actually a no-op (see #handleClick's own busy/paused early-return, which bails on the same
2063-
// two states).
2070+
// Whether a global SNT_PAUSED/TRACING_PAUSED state is currently forcing "No tracing" here (see #setLockedByPause).
2071+
// While true, manualTracingButton/interactiveTracingButton are disabled - not just deselected - so the toolbar
2072+
// can't show tracing as active while every click is actually a no-op (see #handleClick's busy/paused early-return,
2073+
// which bails on the same two states)
20642074
private boolean lockedByPause;
2075+
// Space key tap-vs-hold state (see #getToggleTracingHoldPressAction()/ #getToggleTracingHoldReleaseAction()): a
2076+
// quick press+release commits a permanent flip of tracingEnabled (like clicking the button); a press held past
2077+
// SPACE_HOLD_THRESHOLD_MS previews "No tracing" for as long as it's held and snaps back on release. All 4 fields
2078+
// below are only ever touched on the EDT, so no synchronization is needed despite the Timer involved
2079+
private static final int SPACE_HOLD_THRESHOLD_MS = 200;
2080+
// True from the first "pressed" KeyStroke event until the matching "released" one is processed. Guards against
2081+
// OS key-repeat re-firing the press handler while the key stays down, and against a stray release (e.g. focus
2082+
// lost mid-press) being processed twice
2083+
private boolean spaceKeyDown;
2084+
// Set once SPACE_HOLD_THRESHOLD_MS has elapsed while the key is still down: distinguishes a genuine hold from a
2085+
// quick tap at release time, regardless of whether the hold actually changed anything (see #spaceHoldEngaged)
2086+
private boolean spaceHoldThresholdReached;
2087+
// Set only if the hold threshold was reached AND tracing was actually on at that point (so the preview actually
2088+
// suspended it). Holding while already "No tracing" reaches the threshold but never sets this, matching the
2089+
// pre-tap/hold behavior of that combination being a no-op either way (tap or hold)
2090+
private boolean spaceHoldEngaged;
2091+
// tracingEnabled's value at the moment the hold threshold was reached, so release can restore it exactly once
2092+
// the preview ends
2093+
private boolean tracingEnabledBeforeHold;
2094+
private javax.swing.Timer spaceHoldThresholdTimer;
20652095
// The Future for the A* search currently active (if any), so a Cancel button (if the viewer has
20662096
// one; see Bvv#tracingStatusRow()) can stop it. Null when idle, or during manual tracing
20672097
private volatile Future<?> currentSearchFuture;
@@ -2159,6 +2189,7 @@ private void handleClick(final MouseEvent e) {
21592189
// its workers are reading this SNT instance's shared image-data fields concurrently.
21602190
// Refuse to start a new path on a *different* channel/frame than the batch is using.
21612191
// That would trigger the resync below and race with it
2192+
assert snt != null;
21622193
final int[] batchLock = snt.getBatchRetraceChannelFrame();
21632194
if (batchLock != null) {
21642195
final int[] candidate = peekActiveChannelFrame();
@@ -2864,6 +2895,110 @@ private void disableTracing() {
28642895
showViewerMessage("Tracing disabled");
28652896
}
28662897

2898+
/**
2899+
* @return the action bound to Space's key-down in Bdv/Bvv. Doesn't act immediately: starts a
2900+
* {@value #SPACE_HOLD_THRESHOLD_MS}ms timer to tell a quick tap from a genuine hold
2901+
* (decided at key-up by {@link #getToggleTracingHoldReleaseAction()}) - a tap commits
2902+
* a permanent flip of {@code tracingEnabled} (like clicking the button), a hold past
2903+
* the threshold previews "No tracing" for as long as it's held (mirrors the classic
2904+
* canvas's own hold-Space-to-pan convention, see {@code
2905+
* InteractiveTracerCanvas#mousePressed}) and snaps back on release. No-op (repeated
2906+
* OS key-repeat "pressed" events while already down are ignored) if a press is
2907+
* already in progress.
2908+
*/
2909+
protected AbstractAction getToggleTracingHoldPressAction() {
2910+
return new AbstractAction("Toggle/suspend tracing (Space)") {
2911+
@Override
2912+
public void actionPerformed(final java.awt.event.ActionEvent e) {
2913+
if (spaceKeyDown) return; // OS key-repeat while already held; ignore
2914+
spaceKeyDown = true;
2915+
spaceHoldThresholdReached = false;
2916+
spaceHoldEngaged = false;
2917+
SNTUtils.log("Space pressed (tracingEnabled=" + tracingEnabled + ")");
2918+
spaceHoldThresholdTimer = new javax.swing.Timer(SPACE_HOLD_THRESHOLD_MS, ev -> {
2919+
spaceHoldThresholdReached = true;
2920+
SNTUtils.log("Space hold threshold reached (tracingEnabled=" + tracingEnabled + ")");
2921+
if (tracingEnabled) {
2922+
// Only a hold starting from a tracing mode has anything to preview; a
2923+
// hold starting from "No tracing" is left a no-op, same as before tap
2924+
// detection existed
2925+
spaceHoldEngaged = true;
2926+
tracingEnabledBeforeHold = true;
2927+
setTracingActiveForHold(false);
2928+
}
2929+
});
2930+
spaceHoldThresholdTimer.setRepeats(false);
2931+
spaceHoldThresholdTimer.start();
2932+
}
2933+
};
2934+
}
2935+
2936+
/**
2937+
* @return the action bound to Space's key-up. Stops the pending threshold timer (see {@link
2938+
* #getToggleTracingHoldPressAction()}) and either restores the pre-hold state (if the
2939+
* threshold was reached and actually engaged a preview), does nothing (threshold
2940+
* reached but nothing was previewed - a hold that started from "No tracing"), or, for
2941+
* a quick tap (threshold never reached), commits a permanent flip of {@code
2942+
* tracingEnabled}. The permanent-flip branch is a no-op (with a status message,
2943+
* instead of {@link #getToggleAction(boolean)}'s blocking error dialog) if turning
2944+
* tracing on isn't currently possible. No-op overall if no press was seen (e.g. focus
2945+
* was lost mid-press, so this release has no matching press).
2946+
*/
2947+
protected AbstractAction getToggleTracingHoldReleaseAction() {
2948+
return new AbstractAction("Commit/resume tracing state (Space)") {
2949+
@Override
2950+
public void actionPerformed(final java.awt.event.ActionEvent e) {
2951+
if (!spaceKeyDown) return;
2952+
spaceKeyDown = false;
2953+
if (spaceHoldThresholdTimer != null) {
2954+
spaceHoldThresholdTimer.stop();
2955+
spaceHoldThresholdTimer = null;
2956+
}
2957+
if (spaceHoldThresholdReached) {
2958+
SNTUtils.log("Space released after a hold (engaged=" + spaceHoldEngaged + ")");
2959+
if (spaceHoldEngaged) {
2960+
spaceHoldEngaged = false;
2961+
setTracingActiveForHold(tracingEnabledBeforeHold);
2962+
}
2963+
// else: was held while already "No tracing" - stays a no-op
2964+
return;
2965+
}
2966+
// Quick tap: commit a permanent flip
2967+
SNTUtils.log("Space released as a tap (tracingEnabled=" + tracingEnabled + " -> " + !tracingEnabled + ")");
2968+
if (!tracingEnabled) {
2969+
final boolean sntAware = snt != null && snt.getPathAndFillManager() != null;
2970+
final boolean tracingPossible = manualTrace || (sntAware && snt.accessToValidImageData());
2971+
if (!tracingPossible) {
2972+
showViewerMessage("Tracing not available");
2973+
return;
2974+
}
2975+
}
2976+
setTracingActiveForHold(!tracingEnabled);
2977+
}
2978+
};
2979+
}
2980+
2981+
/**
2982+
* Shared by {@link #getToggleTracingHoldPressAction()}/{@link
2983+
* #getToggleTracingHoldReleaseAction()}: applies {@code active} to {@link #tracingEnabled}
2984+
* ({@code AbstractTracer}'s own field, not the button-driven flow), the extend-path button,
2985+
* the tracing-mode {@link ButtonGroup}'s selection, and a status message - without any of
2986+
* {@link #getToggleAction(boolean)}/{@link #disableTracing()}'s prompting or {@link #exit()}
2987+
* bookkeeping, since both the tap and the hold-preview paths bypass that heavier flow.
2988+
*/
2989+
private void setTracingActiveForHold(final boolean active) {
2990+
tracingEnabled = active;
2991+
if (extendPathButton != null) extendPathButton.setEnabled(active);
2992+
if (active) {
2993+
final JToggleButton modeButton = (manualTrace) ? manualTracingButton : interactiveTracingButton;
2994+
if (modeButton != null) modeButton.setSelected(true);
2995+
showViewerMessage("Tracing mode active");
2996+
} else {
2997+
if (noTracingButton != null) noTracingButton.setSelected(true);
2998+
showViewerMessage("Panning mode active");
2999+
}
3000+
}
3001+
28673002
/**
28683003
* Wires the three mutually-exclusive tracing-mode buttons built by the viewer's own toolbar code
28693004
* (e.g. {@code Bvv#sntToolbar}/{@code Bdv#sntAnnotationsCard}) into this tracer, so

‎src/main/java/sc/fiji/snt/viewer/Bdv.java‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,16 @@ public void actionPerformed(final java.awt.event.ActionEvent e) {
911911
sntAMap.put("snt-discard-path", tracer.getDiscardPathAction());
912912
sntIMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0), "snt-undo-segment"); // see Tracer#undoLastSegment
913913
sntAMap.put("snt-undo-segment", tracer.getUndoSegmentAction());
914+
// Space: a quick tap flips tracing on/off permanently (like clicking No tracing/ Manual/Interactive);
915+
// holding it past a threshold instead changes to "No tracing" for as long as it's held and snaps back
916+
// on release (mirrors InteractiveTracerCanvas#mousePressed hold-Space-to-pan convention). See
917+
// AbstractTracer#getToggleTracingHoldPressAction/ReleaseAction for the tap/hold logic. Like G above,
918+
// this intentionally shadows BDV's own native Space binding, if any, since both maps share this same
919+
// "snt" input map
920+
sntIMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), "snt-toggle-tracing-hold-press");
921+
sntIMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), "snt-toggle-tracing-hold-release");
922+
sntAMap.put("snt-toggle-tracing-hold-press", tracer.getToggleTracingHoldPressAction());
923+
sntAMap.put("snt-toggle-tracing-hold-release", tracer.getToggleTracingHoldReleaseAction());
914924
}
915925
// Command palette shortcut: wired unconditionally (not just in Stream mode), since the BDV
916926
// window can be opened in classic mode too, and its own keybindings layer needs this regardless
@@ -960,16 +970,17 @@ private JComponent sntAnnotationsCard(final BdvActions actions) {
960970
bg1.add(b0);
961971
bg1.add(b1);
962972
bg1.add(b2);
973+
// Non-focusable: Space is also the native Swing "activate the focused button" key, so a toggle button left
974+
// focused after being clicked would double-fire alongside the hold-to-toggle-tracing Space binding above
975+
// (see getToggleTracingHoldPressAction/ReleaseAction) instead of leaving Space to that binding alone.
976+
b0.setFocusable(false);
977+
b1.setFocusable(false);
978+
b2.setFocusable(false);
963979
b0.setSelected(true); // matches tracingEnabled's initial false
964980
tracer.installTracingModeButtons(b0, b1, b2);
965981
bar.add(b0);
966982
bar.add(b1);
967983
bar.add(b2);
968-
final JToggleButton extendButton = GuiUtils.Buttons.toolbarToggleButton(
969-
tracer.getExtendSelectedPathAction(), "Continue extending the selected path",
970-
IconFactory.GLYPH.TAPE, IconFactory.GLYPH.TAPE);
971-
bar.add(extendButton);
972-
tracer.installExtendPathButton(extendButton);
973984
bar.addSeparator();
974985
bar.add(Box.createHorizontalGlue());
975986
bar.addSeparator();

‎src/main/java/sc/fiji/snt/viewer/Bvv.java‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,11 @@ private void attachControlPanel(final BvvSource source) {
14351435
sntAMap.put("snt-discard-path", tracer.getDiscardPathAction());
14361436
sntIMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Z, 0), "snt-undo-segment"); //See Tracer#undoLastSegment
14371437
sntAMap.put("snt-undo-segment", tracer.getUndoSegmentAction());
1438+
// Space: a quick tap flips tracing on/off permanently: see comments on mirrored code in Bdv
1439+
sntIMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_SPACE, 0, false), "snt-toggle-tracing-hold-press");
1440+
sntIMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_SPACE, 0, true), "snt-toggle-tracing-hold-release");
1441+
sntAMap.put("snt-toggle-tracing-hold-press", tracer.getToggleTracingHoldPressAction());
1442+
sntAMap.put("snt-toggle-tracing-hold-release", tracer.getToggleTracingHoldReleaseAction());
14381443
}
14391444
sntIMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_K, 0), "snt-capture-keyframe");
14401445
sntAMap.put("snt-capture-keyframe", new AbstractAction() {
@@ -1793,15 +1798,15 @@ private JComponent sntToolbar(final BvvActions actions) {
17931798
bg1.add(b0);
17941799
bg1.add(b1);
17951800
bg1.add(b2);
1801+
// Non-focusable: See comments on mirrored code in Bdv
1802+
b0.setFocusable(false);
1803+
b1.setFocusable(false);
1804+
b2.setFocusable(false);
17961805
b0.setSelected(true); // matches tracingEnabled's initial false
17971806
tracer.installTracingModeButtons(b0, b1, b2);
17981807
toolbar.add(b0);
17991808
toolbar.add(b1);
18001809
toolbar.add(b2);
1801-
final JToggleButton extendButton = scaledToggleButton(tracer.getExtendSelectedPathAction(),
1802-
IconFactory.GLYPH.TAPE, "Continue extending the selected path");
1803-
toolbar.add(extendButton);
1804-
tracer.installExtendPathButton(extendButton);
18051810

18061811
// "Center scene strategy on click" options button (see AbstractBigViewer.RecenterStrategy)
18071812
final JPopupMenu popupMenu = new JPopupMenu();
@@ -1901,13 +1906,16 @@ public void componentHidden(java.awt.event.ComponentEvent e) {
19011906
}
19021907

19031908
private JToggleButton scaledToggleButton(final Action action, final IconFactory.GLYPH glyph, final String tooltipText) {
1909+
return scaledToggleButton(action, glyph, null, tooltipText);
1910+
}
1911+
1912+
private JToggleButton scaledToggleButton(final Action action, final IconFactory.GLYPH glyph, final Color color, final String tooltipText) {
19041913
final JToggleButton button = new JToggleButton(action);
19051914
button.setText(null);
1906-
IconFactory.assignIcon(button, glyph, (Color) null, 1.1f);
1915+
IconFactory.assignIcon(button, glyph, color, 1.1f);
19071916
button.setToolTipText(tooltipText);
19081917
return button;
1909-
}
1910-
1918+
}
19111919

19121920
/**
19131921
* Script friendly method to add a supported object ({@link Tree},

‎src/main/resources/gui/hints-stream.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ Use BVV's slab controls to restrict path visibility to a Z-slab around the curre
1010
Take a BVV snapshot with Shift+S
1111
BVV can subtract (un-mix) channels for cleaner multi-channel visualization
1212
When a dataset exceeds the GPU's max texture size, BVV will offer automatic N5 conversion
13+
Press Space to toggle between tracing and navigation. Hold it down to temporarily switch to navigation, then release to resume tracing

0 commit comments

Comments
 (0)