Skip to content

Commit 5013794

Browse files
committed
UX: Implement on-boarding tour and revamp the notification system
- Implement on-boarding callouts - 'Tip of the day': make the reel of hints aware of stream mode - Coalescent the different non-modal, 'non-urgent' dialogs (floating dialogs, temp Msg, 'toasts'; etc.) into a single queued notification center - Fix inconsistencies between temp messages, floating toasts, notifications, and errors - While at it fix Show/hide Path Manager/Fill Manager buttons that for 10+ years have been of the wrong type!
1 parent b093e1d commit 5013794

23 files changed

Lines changed: 2762 additions & 320 deletions

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ private JPopupMenu importMenu() {
922922
recordCmd("load(rm.getRoisAsArray())");
923923
});
924924
menu.addSeparator();
925-
jmi = new JMenuItem("From Workspace...", IconFactory.menuIcon('\ue066', true));
925+
jmi = new JMenuItem("From Workspace...", IconFactory.menuIcon(IconFactory.GLYPH.HOUSE_LAPTOP));
926926
menu.add(jmi);
927927
jmi.addActionListener(e -> {
928928
final File workspaceDir = sntui.getOrPromptForWorkspace();
@@ -996,7 +996,7 @@ private JPopupMenu exportMenu() {
996996
recordCmd("toRoiManager()");
997997
});
998998
menu.addSeparator();
999-
jmi = new JMenuItem("To Workspace...", IconFactory.menuIcon('\ue066', true));
999+
jmi = new JMenuItem("To Workspace...", IconFactory.menuIcon(IconFactory.GLYPH.HOUSE_LAPTOP));
10001000
menu.add(jmi);
10011001
jmi.addActionListener(e -> {
10021002
final File workspaceDir = sntui.getOrPromptForWorkspace();

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1277,10 +1277,17 @@ protected void done() {
12771277
} else {
12781278
sntui.showStatus(String.format("Full scan completed: %d issue(s) found", warnings.size())
12791279
+ outOfCropNote, true);
1280+
// A full scan can run for a while (network/disk-bound image checks) and, when
1281+
// triggered via calibrateFromTrees() after autotracing, without any direct click
1282+
GuiUtils.queueNotice(String.format("<HTML><b>Full scan completed: %d issue(s) found.</b><br>" +
1283+
"Click here to review them.", warnings.size()),
1284+
null, () -> sntui.selectTab("Assistant"));
12801285
}
12811286
} catch (final Exception ex) {
12821287
SNTUtils.log("Full scan failed: " + ex.getMessage());
12831288
sntui.showStatus("Full scan failed. See log.", true);
1289+
GuiUtils.queueNotice("<HTML><b>Full scan failed.</b><br>Click here for the Assistant tab.",
1290+
null, () -> sntui.selectTab("Assistant"));
12841291
}
12851292
}
12861293
}.execute();
@@ -1368,6 +1375,10 @@ protected void done() {
13681375
final PlausibilityCalibrator.CalibrationResult result = get();
13691376
if (result == null) {
13701377
sntui.showStatus("Auto-calibration failed.", true);
1378+
// This chain runs programmatically after autotracing, with no direct click behind it
1379+
GuiUtils.queueNotice("<HTML><b>Auto-calibration (after autotracing) failed.</b><br>" +
1380+
"None of the freshly traced data yielded a valid calibration.",
1381+
null, () -> sntui.selectTab("Assistant"));
13711382
return;
13721383
}
13731384
SNTUtils.log("PlausibilityCalibrator (auto):\n" + result.toTable());
@@ -1378,11 +1389,14 @@ protected void done() {
13781389
sntui.selectTab("Assistant");
13791390
sntui.showStatus(String.format("Calibrated from %d tree(s). Scanning...", result.getTreeCount()), true);
13801391
// Chain directly into a full scan: all validation guards (parameters selected, paths
1381-
// exist, image loaded) should pass since we just calibrated from freshly traced data
1392+
// exist, image loaded) should pass since we just calibrated from freshly traced data.
1393+
// Its own done() queues a notice of the scan's outcome, so nothing further is needed here
13821394
runOnDemandAsync();
13831395
} catch (final Exception ex) {
13841396
SNTUtils.log("Auto-calibration failed: " + ex.getMessage());
13851397
sntui.showStatus("Auto-calibration failed. See log.", true);
1398+
GuiUtils.queueNotice("<HTML><b>Auto-calibration (after autotracing) failed.</b><br>See log.",
1399+
null, () -> sntui.selectTab("Assistant"));
13861400
}
13871401
}
13881402
}.execute();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,7 @@ private JButton colorButton() {
15231523
private void updateWidget() {
15241524
clearButton.setEnabled(roi != null);
15251525
revealButton.setEnabled(roi != null && roi != DUMMY_ROI);
1526-
final Color color = (roi == null) ? IconFactory.defaultColor() : IconFactory.selectedColor();
1526+
final Color color = (roi == null) ? IconFactory.secondaryColor() : IconFactory.selectedColor();
15271527
assignButton.setIcon(IconFactory.buttonIcon('\uf359', true, color));
15281528
}
15291529

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3172,7 +3172,7 @@ private void checkForAppropriateImageDimensions() {
31723172
if (!pluginBoundingBox.contains(boundingBox)) {
31733173
plugin.getPrefs().setTemp(SNTPrefs.RESIZE_REQUIRED, true);
31743174
SNTUtils.warn("Some nodes lay outside the image volume: you may need to "
3175-
+ "adjust import options or resize current image canvas");
3175+
+ "adjust import options, scale paths, or check for mismatched files");
31763176
}
31773177
}
31783178
}

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

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ public PathManagerUI(final SNT plugin) {
150150
tree.setDoubleBuffered(true);
151151
tree.addTreeSelectionListener(this);
152152
SNTUI.InternalUtils.addHoldToToggleKeyListener(tree, plugin);
153+
153154
proofReadingToolBar = new ProofReadingTagsToolBar();
154155
add(proofReadingToolBar, BorderLayout.PAGE_START);
155156

@@ -473,7 +474,6 @@ public PathManagerUI(final SNT plugin) {
473474
jmi.addActionListener(multiPathListener);
474475
advanced.add(jmi);
475476

476-
477477
final JPopupMenu popup = new JPopupMenu();
478478
popup.putClientProperty("owner", this); // see SNTCommandFinder#revealMenuItem()
479479
popup.add(getDeleteMenuItem(multiPathListener));
@@ -542,6 +542,27 @@ public void windowClosing(final WindowEvent ignored) {
542542
}
543543
});
544544
pack();
545+
addCallOuts();
546+
}
547+
548+
private void addCallOuts() { // group callouts by "plugin" (similarly to SNTUI), see CalloutManager#groupFor
549+
final String group = CalloutManager.groupFor(plugin);
550+
int n = CalloutManager.size() + 1;
551+
CalloutManager.add(this, CalloutManager.AUTO,
552+
"Traced paths are listed here. The Path Manager allows you<br>" +
553+
"to select, tag, measure, and analyze groups of paths.", group, n++);
554+
CalloutManager.add(menuBar, CalloutManager.AUTO,
555+
"These menu commands apply to selected<br>" +
556+
"paths or <i>all</i> paths if none are selected.", group, n++);
557+
CalloutManager.add(proofReadingToolBar, CalloutManager.AUTO,
558+
"The <i>Proofreading Toolbar</i> allows you<br>" +
559+
"to tag paths by completion level.", group, n++);
560+
CalloutManager.add(navToolbar, CalloutManager.AUTO,
561+
"The <i>Navigation Toolbar</i> allows you to select<br>" +
562+
"structures and zoom into path selections.", group, n++);
563+
CalloutManager.add(searchableBar, CalloutManager.AUTO,
564+
"The <i>Filtering Toolbar</i> allows you to select paths based<br>" +
565+
"on morphological, locational, or annotation criteria.", group, n);
545566
}
546567

547568
private JMenu getSpineUtilsMenu(final MultiPathActionListener multiPathListener) {
@@ -2750,7 +2771,7 @@ public void actionPerformed(final ActionEvent e) {
27502771
if (s.trim().isEmpty()) {
27512772
p.setName("");
27522773
} else if (getPathAndFillManager().getPathFromName(s, false) != null) {
2753-
displayTmpMsg("There is already a path named:\n('" + s + "')");
2774+
guiUtils.error("There is already a path named:\n('" + s + "')");
27542775
return;
27552776
} else {// Otherwise this is OK, change the name:
27562777
p.setName(s);
@@ -4209,7 +4230,7 @@ public void execute(List<Path> selectedPaths, String cmd) {
42094230
if (replacements > 0) {
42104231
refreshManager(false, false, selectedPaths);
42114232
plugin.setUnsavedChanges(true);
4212-
guiUtils.floatingMsg(String.format("%d/%d path(s) renamed.", replacements, selectedPaths.size()), true);
4233+
displayTmpMsg(String.format("%d/%d path(s) renamed.", replacements, selectedPaths.size()));
42134234
} else {
42144235
guiUtils.error(String.format("None of the %d paths matched the specified pattern.", selectedPaths.size()));
42154236
}
@@ -4311,7 +4332,7 @@ public void execute(final List<Path> selectedPaths, String cmd) {
43114332
navToolbar.restoreFullModelState();
43124333
}
43134334
applyActiveTags(selectedPaths);
4314-
guiUtils.tempMsg("Command finished.");
4335+
displayTmpMsg("Command finished.");
43154336
plugin.updateAllViewers();
43164337
plugin.setUnsavedChanges(true);
43174338
}
@@ -4353,7 +4374,7 @@ public void execute(List<Path> selectedPaths, String cmd) {
43534374
if (!p.isFittedVersionOfAnotherPath()) p.setRadius(userRad);
43544375
});
43554376
removeOrReapplyDefaultTag(selectedPaths, MultiPathActionListener.MEAN_RADIUS_TAG_CMD, !noRadius, false);
4356-
guiUtils.tempMsg("Command finished. Fitted path(s) ignored.");
4377+
displayTmpMsg("Command finished. Fitted path(s) ignored.");
43574378
plugin.updateAllViewers();
43584379
plugin.setUnsavedChanges(true);
43594380
}
@@ -5245,9 +5266,9 @@ private void applyZCorrection(final List<Path> selectedPaths) {
52455266
if (factor == 1d) return;
52465267

52475268
// Warn if corrected Z values would exceed image bounds
5269+
int oobNodes = 0;
52485270
if (plugin.accessToValidImageData()) {
52495271
final double maxZ = plugin.depth - 1;
5250-
int oobNodes = 0;
52515272
for (final Path p : selectedPaths) {
52525273
final Path pathToUse = p.getUseFitted() ? p.getFitted() : p;
52535274
for (int node = 0; node < pathToUse.size(); node++) {
@@ -5270,7 +5291,14 @@ private void applyZCorrection(final List<Path> selectedPaths) {
52705291
}
52715292
plugin.updateAllViewers();
52725293
plugin.setUnsavedChanges(true);
5273-
guiUtils.tempMsg("Z-correction applied (×" + SNTUtils.formatDouble(factor, 3) + ")");
5294+
if (oobNodes > 0) {
5295+
// Mirror the same handling used by PathAndFillManager#checkForAppropriateImageDimensions():
5296+
// flag the canvas as needing a resize, and log/queue a WARN notice
5297+
plugin.getPrefs().setTemp(SNTPrefs.RESIZE_REQUIRED, true);
5298+
SNTUtils.warn(oobNodes + " node(s) now lay outside the image volume after Z-correction (×"
5299+
+ SNTUtils.formatDouble(factor, 3) + ")");
5300+
}
5301+
displayTmpMsg("Z-correction applied (×" + SNTUtils.formatDouble(factor, 3) + ")");
52745302
}
52755303

52765304
private class ResetFitsCommand implements PathCommand {

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public class SNTPrefs { // TODO: Adopt PrefService
109109
private static final String WORKSPACE_KEY = "tracing.snt.workspace";
110110
private static final String NEXT_IMG_FILTER_KEY = "tracing.snt.nextimg";
111111
private static final String FAST_CROP_MATERIALIZATION_KEY = "tracing.snt.fastcropmat";
112+
private static final String FIRST_RUN_KEY = "tracing.snt.firstrun";
112113

113114
/** recent directory */
114115
private static File recentDir;
@@ -466,6 +467,18 @@ public static boolean firstRunAfterUpdate() {
466467
return lastVersion == null || VersionUtils.compare(SNTUtils.VERSION, lastVersion) > 0;
467468
}
468469

470+
/**
471+
* Checks if SNT has ever been run before on this system, consuming the check in the process: the very first
472+
* call ever made to this method returns {@code true}; every call after that returns {@code false}.
473+
*
474+
* @return true only the very first time this is ever called (or after preferences have been cleared)
475+
*/
476+
public static boolean firstRun() {
477+
final boolean first = !Prefs.get(FIRST_RUN_KEY, false);
478+
if (first) Prefs.set(FIRST_RUN_KEY, true);
479+
return first;
480+
}
481+
469482
/**
470483
* Sets the number of threads to use for multi-threaded operations.
471484
*
@@ -551,6 +564,7 @@ public static void clearAll() {
551564
Prefs.set(FILTERED_IMG_PATH, null);
552565
Prefs.set(NEXT_IMG_FILTER_KEY, null);
553566
Prefs.set(WORKSPACE_KEY, null);
567+
Prefs.set(FIRST_RUN_KEY, null);
554568
setLookAndFeel(null);
555569
setThreads(0);
556570
wipeSessionPrefs();

0 commit comments

Comments
 (0)