Skip to content

Commit 0f3a6b9

Browse files
committed
Implement GuidedTutorial and revamp DemoRunner
1 parent e261c6a commit 0f3a6b9

12 files changed

Lines changed: 1064 additions & 109 deletions

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

Lines changed: 101 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -775,8 +775,8 @@ private void clearWarningCanvasLabel() {
775775
*
776776
* @param cmd The command to be run, exactly as listed in SNTUI's menu bar
777777
* @param args the option(s) that would fill the command's prompt. e.g.,
778-
* 'runCommand("Load Demo Dataset...", "4. Hippocampal neuron (DIC
779-
* timelapse)")'
778+
* 'runCommand("Load Demo Dataset/Tutorial...", "Hippocampal
779+
* neuron (DIC timelapse)")'
780780
* @throws IllegalArgumentException if {@code cmd} is not found or supported.
781781
*/
782782
public void runCommand(final String cmd, final String... args) throws IllegalArgumentException {
@@ -1401,9 +1401,18 @@ public void windowOpened(final WindowEvent e) {
14011401
public void windowClosing(final WindowEvent e) {
14021402
if (plugin.isStreamMode()) {
14031403
exitRequested();
1404-
} else if (bvvSNT != null && bvvSNT.getViewerFrame() != null) {
1405-
bvvSNT.getViewerFrame().getViewerPanel().stop();
1406-
bvvSNT.getViewerFrame().dispose();
1404+
return;
1405+
}
1406+
// Always stop/dispose *this* window's own viewer, regardless of whether it is still
1407+
// the instance tracked in bvvSNT: an older Bvv left orphaned by a later setBvv() call
1408+
// (e.g. a second demo/tutorial opening its own instance without closing this one first)
1409+
// must still close itself when its own close button is clicked, rather than reaching
1410+
// for whatever instance bvvSNT currently happens to hold.
1411+
if (bvv.getViewerFrame() != null) {
1412+
bvv.getViewerFrame().getViewerPanel().stop();
1413+
bvv.getViewerFrame().dispose();
1414+
}
1415+
if (bvvSNT == bvv) { // this window is (still) the tethered/active one; clear UI bookkeeping too
14071416
bvvSNT = null;
14081417
if (bvvSeedHandler != null) { bvvSeedHandler.dispose(); bvvSeedHandler = null; }
14091418
syncBookmarksTabContent();
@@ -1464,9 +1473,15 @@ public void windowOpened(final WindowEvent e) {
14641473
public void windowClosing(final WindowEvent e) {
14651474
if (plugin.isStreamMode()) {
14661475
exitRequested();
1467-
} else if (bdvSNT != null && bdvSNT.getViewerFrame() != null) {
1468-
bdvSNT.getViewerFrame().getViewerPanel().stop();
1469-
bdvSNT.getViewerFrame().dispose();
1476+
return;
1477+
}
1478+
// See the twin comment in setBvvOnEDT's windowClosing: always close *this* window's own
1479+
// viewer, regardless of whether it is still the instance tracked in bdvSNT.
1480+
if (bdv.getViewerFrame() != null) {
1481+
bdv.getViewerFrame().getViewerPanel().stop();
1482+
bdv.getViewerFrame().dispose();
1483+
}
1484+
if (bdvSNT == bdv) { // this window is (still) the tethered/active one; clear UI bookkeeping too
14701485
bdvSNT = null;
14711486
if (bdvSeedHandler != null) { bdvSeedHandler.dispose(); bdvSeedHandler = null; }
14721487
syncBookmarksTabContent();
@@ -3750,8 +3765,8 @@ private JMenu fileMenu(final ScriptInstaller installer) {
37503765
fileMenu.add(changeImpMenu);
37513766

37523767
// Load
3753-
fileMenu.addSeparator();
37543768
fileMenu.add(importSubmenu);
3769+
fileMenu.addSeparator();
37553770
final JMenuItem fromDemo = getImportActionMenuItem(ImportAction.DEMO);
37563771
fromDemo.setIcon(IconFactory.menuIcon(GLYPH.GRADUATION_CAP));
37573772
fromDemo.setToolTipText("Load sample images and/or reconstructions");
@@ -3937,7 +3952,7 @@ private JMenu fileMenu(final ScriptInstaller installer) {
39373952
urlItem.setIcon(IconFactory.menuIcon(GLYPH.GLOBE));
39383953
importSubmenu.add(urlItem);
39393954
CalloutManager.add(fileMenu, CalloutManager.AUTO,
3940-
"Use <i>File &gt; Load Demo Dataset...</i> to explore SNT<br>using sample images and reconstructions.",
3955+
"Use <i>File &gt; Load Demo Dataset/Tutorial...</i> to explore SNT<br>using sample images, reconstructions, and tutorials.",
39413956
calloutGroup(), 1);
39423957
return fileMenu;
39433958
}
@@ -4191,8 +4206,10 @@ private JMenu viewMenu() {
41914206
arrangeDialogsMenuItem.addActionListener(e -> {
41924207
final DialogLayout layout = arrangeCoreDialogs(true);
41934208
if (layout == null) return; // error already shown (corrupt prefs)
4194-
// Classic mode: unchanged behavior: nothing else to do. Arrange the BVV/BDV viewer frame:
4195-
if (plugin.isStreamMode()) arrangeStreamViewer(layout);
4209+
if (plugin.isStreamMode())
4210+
arrangeStreamViewer(layout); // display-canvas placeholder + viewer frame
4211+
else
4212+
arrangeBigViewerOnly(layout); // canvas is handled by arrangeCanvases(): just the viewer
41964213
});
41974214
viewMenu.add(arrangeDialogsMenuItem);
41984215
final JMenuItem arrangeWindowsMenuItem = new JMenuItem("Arrange Tracing Views");
@@ -5718,7 +5735,8 @@ private DialogLayout arrangeCoreDialogs(final boolean warnOnError) {
57185735
}
57195736

57205737
/**
5721-
* Stream-mode continuation of {@link #arrangeCoreDialogs(boolean)}.
5738+
* Stream-mode continuation of {@link #arrangeCoreDialogs(boolean)}: positions the display-canvas
5739+
* placeholder (if any) above the BVV/BDV viewer frame, both in the "next column" it reserved
57225740
*/
57235741
private void arrangeStreamViewer(final DialogLayout layout) {
57245742
int viewerY = layout.usableY();
@@ -5730,6 +5748,23 @@ private void arrangeStreamViewer(final DialogLayout layout) {
57305748
imp.getWindow().toFront();
57315749
viewerY += placeholderHeight + InternalUtils.MARGIN;
57325750
}
5751+
arrangeBigViewer(layout, viewerY);
5752+
}
5753+
5754+
/**
5755+
* Classic-mode counterpart of {@link #arrangeStreamViewer(DialogLayout)}: the tracing canvas is
5756+
* already handled by {@link #arrangeCanvases(boolean)}/"Arrange Tracing Views", so this only
5757+
* places an active BVV/BDV viewer frame, if any, in the "next column" {@code layout} reserved
5758+
*/
5759+
private void arrangeBigViewerOnly(final DialogLayout layout) {
5760+
arrangeBigViewer(layout, layout.usableY());
5761+
}
5762+
5763+
/**
5764+
* Positions the active BVV/BDV viewer frame, if any, in the "next column" {@code layout}
5765+
* reserved, starting at {@code viewerY} and filling the remaining usable area
5766+
*/
5767+
private void arrangeBigViewer(final DialogLayout layout, final int viewerY) {
57335768
final AbstractBigViewer viewer = getActiveBigViewer();
57345769
if (viewer == null) return;
57355770
final JFrame viewerFrame = viewer.getViewerFrame();
@@ -6144,6 +6179,54 @@ protected void reset() {
61446179
showStatus("Resetting", true);
61456180
}
61466181

6182+
/**
6183+
* Closes any tethered {@link Bvv}/{@link Bdv} viewer(s), if open. Unlike {@link #resetUI()}, this does not
6184+
* touch the loaded image or paths: closing a big-viewer *window* isn't data loss the way clearing paths is
6185+
* (the underlying {@code PathAndFillManager} is untouched), so this needs no confirmation and is safe to call
6186+
* unconditionally - e.g. from {@code DemoRunner} before loading a new demo, so a viewer left over from a
6187+
* previous one is never silently orphaned (see {@code AbstractBigViewer}/{@code setBvvOnEDT}'s
6188+
* {@code windowClosing} handler for what "orphaned" means here).
6189+
*/
6190+
public void closeBigViewers() {
6191+
closeBigViewer(bvvSNT);
6192+
closeBigViewer(bdvSNT);
6193+
}
6194+
6195+
/**
6196+
* Full reset: closes any tethered {@link Bvv}/{@link Bdv} viewer (via {@link #closeBigViewers()}) and the
6197+
* current image, and clears all paths, restoring the UI to its initial, blank-canvas state. Intended for
6198+
* callers that need a guaranteed-fresh session regardless of whatever was open before.
6199+
*
6200+
* @return true if the reset completed; false if it was aborted (e.g. the current image has unsaved changes and the
6201+
* user did not resolve ImageJ's own "Save Changes?" prompt), in which case nothing else was changed
6202+
*/
6203+
public boolean resetUI() {
6204+
abortCurrentOperation();
6205+
closeBigViewers();
6206+
if (plugin.getImagePlus() != null) {
6207+
plugin.getImagePlus().close();
6208+
if (plugin.getImagePlus() != null) // user did not resolve the 'Save Changes?' prompt
6209+
return false;
6210+
}
6211+
plugin.closeAndResetAllPanes();
6212+
plugin.getPathAndFillManager().clear();
6213+
resetState();
6214+
showStatus("Resetting", true);
6215+
return true;
6216+
}
6217+
6218+
/**
6219+
* Closes {@code viewer} (if non-null) by dispatching {@link WindowEvent#WINDOW_CLOSING} at its frame,
6220+
* rather than disposing it directly, so this runs the exact same stop()+dispose()+SNTUI-detach path
6221+
* that closing the window by hand would trigger (see {@code setBvvOnEDT}/{@code setBdvOnEDT}'s
6222+
* {@code windowClosing} handler).
6223+
*/
6224+
private void closeBigViewer(final AbstractBigViewer viewer) {
6225+
if (viewer != null && viewer.getViewerFrame() != null) {
6226+
viewer.getViewerFrame().dispatchEvent(new WindowEvent(viewer.getViewerFrame(), WindowEvent.WINDOW_CLOSING));
6227+
}
6228+
}
6229+
61476230
protected void ctPositionChanged() {
61486231
final ImagePlus imp = plugin.getImagePlus();
61496232
final JPanel newSourcePanel = sourcePanel(imp);
@@ -6908,7 +6991,7 @@ static String getImportActionName(final int type) {
69086991
case ImportAction.IMAGE -> "From File...";
69096992
case ImportAction.ANY_RECONSTRUCTION -> "Guess File Type...";
69106993
case ImportAction.JSON -> "JSON...";
6911-
case ImportAction.DEMO -> "Load Demo Dataset...";
6994+
case ImportAction.DEMO -> "Load Demo Dataset/Tutorial...";
69126995
case ImportAction.NDF -> "NDF...";
69136996
case ImportAction.NEUROLUCIDA -> "Neurolucida XML...";
69146997
case ImportAction.TRACES -> "TRACES...";
@@ -6928,7 +7011,7 @@ static int getImportActionType(final String name) {
69287011
case "From File..." -> ImportAction.IMAGE;
69297012
case "Guess File Type..." -> ImportAction.ANY_RECONSTRUCTION;
69307013
case "JSON..." -> ImportAction.JSON;
6931-
case "Load Demo Dataset..." -> ImportAction.DEMO;
7014+
case "Load Demo Dataset/Tutorial...", "Load Demo Dataset..." -> ImportAction.DEMO; // 2nd form: backwards compatibility
69327015
case "NDF..." -> ImportAction.NDF;
69337016
case "Neurolucida XML..." -> ImportAction.NEUROLUCIDA;
69347017
case "TRACES..." -> ImportAction.TRACES;
@@ -7488,10 +7571,10 @@ private void run() {
74887571
}
74897572
final DemoRunner demoRunner = new DemoRunner(SNTUI.this, plugin);
74907573
if (file != null) { // recorded command
7491-
try (final Scanner scanner = new Scanner(file.getName())) {
7492-
demoRunner.load(scanner.useDelimiter("\\D+").nextInt());
7574+
try {
7575+
demoRunner.load(file.getName()); // resolves label or legacy/bare id
74937576
return;
7494-
} catch (final NoSuchElementException | IllegalStateException | IllegalArgumentException ex) {
7577+
} catch (final IllegalArgumentException ex) {
74957578
throw new IllegalArgumentException("Invalid recorded option " + ex.getMessage());
74967579
}
74977580
}

0 commit comments

Comments
 (0)