Skip to content

Commit 18b351d

Browse files
committed
Fix bookmark highlighting
1 parent 9d975ff commit 18b351d

2 files changed

Lines changed: 87 additions & 21 deletions

File tree

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

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public class BookmarkManager {
6363

6464
private final SNTUI sntui;
6565
private final Bvv bvv;
66+
private JToggleButton highlightToggle;
6667
/** BVV-injected toolbar components (e.g. slab toggle). Added via {@link #addBvvToolbarButton}. */
6768
private final java.util.List<javax.swing.JComponent> bvvToolbarButtons = new java.util.ArrayList<>();
6869
private final GuiUtils guiUtils;
@@ -195,10 +196,9 @@ This pane stores image locations that you can quickly (re)visit while \
195196
panel.add(assembleHighlightToolbar(), gbc);
196197
gbc.gridy++;
197198
gbc.weighty = 0.95;
198-
// Use the cached scroll pane (created in assembleTable) so the
199-
// DetachableTable helper can move it in and out of a floating dialog.
200-
// Capture the constraints used here so redockTableScroll() can
201-
// restore the pane to the same grid cell after a detach.
199+
// Use the cached scroll pane (created in assembleTable) so the DetachableTable helper can move it in and out
200+
// of a floating dialog. Capture the constraints used here so redockTableScroll() can restore the pane to the
201+
// same grid cell after a detach.
202202
tableScrollGbc = (GridBagConstraints) gbc.clone();
203203
panel.add(tableScroll, tableScrollGbc);
204204
gbc.gridy++;
@@ -226,6 +226,17 @@ private BookmarkTable assembleTable(final BookmarkModel model) {
226226
final BookmarkTable table = new BookmarkTable(model);
227227
final JPopupMenu pMenu = assembleTablePopupMenu(table);
228228
table.setComponentPopupMenu(pMenu);
229+
// Live-sync the overlay highlights with the table selection when the
230+
// toggle is on. valueIsAdjusting is filtered so click-and-drag selections
231+
// only repaint once at the end, not on every intermediate index. Empty
232+
// model is guarded here to avoid showHighlights() popping an error
233+
// dialog during table-clear operations.
234+
table.getSelectionModel().addListSelectionListener(e -> {
235+
if (e.getValueIsAdjusting()) return;
236+
if (highlightToggle == null || !highlightToggle.isSelected()) return;
237+
if (model.getRowCount() == 0) return;
238+
showHighlights();
239+
});
229240
// Cache the scroll pane so the detacher can move it between dock and
230241
// floating dialog. BookmarkTable.getContainer() copies the table's
231242
// popup onto the scroll pane, so we must set the popup BEFORE this.
@@ -622,6 +633,7 @@ private JPopupMenu importMenu() {
622633
load(imp.getOverlay().toArray());
623634
sntui.showStatus(model.getDataList().size() + " listed bookmarks ", true);
624635
recordCmd("load(snt.getInstance().getImagePlus().getOverlay().toArray())");
636+
clearImportedOverlay(imp);
625637
});
626638
jmi = new JMenuItem("From ROI Manager", IconFactory.menuIcon(IconFactory.GLYPH.LIST_ALT));
627639
menu.add(jmi);
@@ -651,6 +663,32 @@ private JPopupMenu importMenu() {
651663
return menu;
652664
}
653665

666+
private void clearImportedOverlay(final ImagePlus imp) {
667+
final Overlay ov = imp.getOverlay();
668+
if (ov == null || ov.size() == 0) return;
669+
final boolean nag = sntui.plugin.getPrefs().getTemp("clear-imported-overlay-nag", true);
670+
boolean wipe = sntui.plugin.getPrefs().getTemp("clear-imported-overlay", true);
671+
if (nag) {
672+
final boolean[] options = guiUtils.getPersistentConfirmation(
673+
"Now that ROIs have been imported as bookmarks, remove them from the image overlay? "
674+
+ "(This clears all ROIs from the overlay, including any drawn by other tools.)",
675+
"Remove Imported Overlay?");
676+
sntui.plugin.getPrefs().setTemp("clear-imported-overlay", wipe = options[0]);
677+
sntui.plugin.getPrefs().setTemp("clear-imported-overlay-nag", !options[1]);
678+
}
679+
if (wipe) {
680+
// Preserve our own highlight ROIs (named with HIGHLIGHT_PREFIX) so the
681+
// toggle doesn't desync when the user happens to clear during a highlight
682+
final Roi[] all = ov.toArray();
683+
for (int i = all.length - 1; i >= 0; i--) {
684+
final String name = all[i].getName();
685+
if (name != null && name.startsWith(HIGHLIGHT_PREFIX)) continue;
686+
ov.remove(i);
687+
}
688+
imp.updateAndDraw();
689+
}
690+
}
691+
654692
private JPopupMenu exportMenu() {
655693
final JPopupMenu menu = new JPopupMenu();
656694
GuiUtils.addSeparator(menu, "Export:");
@@ -713,19 +751,30 @@ private void saveToUserChosenFile(final File file) {
713751
}
714752
}
715753

716-
private void highlightBookmarks() {
717-
if (noBookmarksError()) return;
754+
/**
755+
* Adds (or refreshes) overlay highlights for the currently selected
756+
* bookmarks. Re-invoking re-evaluates the current selection (used by
757+
* the table's ListSelectionListener to keep highlights in sync). Sole
758+
* entry point for showing highlights; syncs {@link #highlightToggle}
759+
* state.
760+
*/
761+
private void showHighlights() {
762+
if (noBookmarksError()) {
763+
syncHighlightToggle(false);
764+
return;
765+
}
718766
final ImagePlus imp = sntui.plugin.getImagePlus();
719767
if (imp == null) {
720768
sntui.guiUtils.error("No image is currently open.");
769+
syncHighlightToggle(false);
721770
return;
722771
}
723772
Overlay overlay = imp.getOverlay();
724773
if (overlay == null) {
725774
overlay = new Overlay();
726775
imp.setOverlay(overlay);
727776
}
728-
// Remove any existing highlights first
777+
// Remove any existing highlights first (so re-clicking refreshes against the current selection)
729778
removeHighlightROIs(overlay);
730779
final int[] rows = getSelectedRowsAllIfNone();
731780
int idx = 0;
@@ -734,22 +783,38 @@ private void highlightBookmarks() {
734783
final Bookmark b = model.getDataList().get(modelRow);
735784
final PointRoi roi = b.toRoi();
736785
roi.setName(HIGHLIGHT_PREFIX + idx++);
737-
roi.setPointType(PointRoi.CIRCLE);
786+
roi.setPointType(PointRoi.DOT);
787+
roi.setSize(3); // mid size: 0=tiny, 6=XXXL
738788
if (roi.getStrokeColor() == null)
739789
roi.setStrokeColor(Color.CYAN);
740790
overlay.add(roi);
741791
}
742792
imp.updateAndDraw();
743793
sntui.showStatus(rows.length + " bookmark(s) highlighted on overlay", true);
794+
syncHighlightToggle(true);
744795
}
745796

746-
private void clearHighlights() {
797+
/**
798+
* Removes highlight ROIs from the overlay. Sole entry point for hiding
799+
* highlights; syncs {@link #highlightToggle} state.
800+
*/
801+
private void hideHighlights() {
747802
final ImagePlus imp = (sntui == null) ? null : sntui.plugin.getImagePlus();
748-
if (imp == null || imp.getOverlay() == null) return;
749-
if (removeHighlightROIs(imp.getOverlay())) {
803+
if (imp != null && imp.getOverlay() != null && removeHighlightROIs(imp.getOverlay())) {
750804
imp.updateAndDraw();
751-
sntui.showStatus("Highlights cleared", true);
805+
if (sntui != null) sntui.showStatus("Highlights cleared", true);
752806
}
807+
syncHighlightToggle(false);
808+
}
809+
810+
/** Updates the toggle without retriggering its action listener. */
811+
private void syncHighlightToggle(final boolean selected) {
812+
if (highlightToggle == null || highlightToggle.isSelected() == selected) return;
813+
// Detach listeners briefly so toggling state programmatically doesn't loop back into show/hide
814+
final java.awt.event.ActionListener[] ls = highlightToggle.getActionListeners();
815+
for (final java.awt.event.ActionListener l : ls) highlightToggle.removeActionListener(l);
816+
highlightToggle.setSelected(selected);
817+
for (final java.awt.event.ActionListener l : ls) highlightToggle.addActionListener(l);
753818
}
754819

755820
/**
@@ -778,14 +843,15 @@ private JToolBar assembleHighlightToolbar() {
778843
tb.add(Box.createHorizontalGlue());
779844
if (sntui != null) { // no functionality in BVV Markers table
780845
tb.addSeparator();
781-
final JButton highlightButton = new JButton(IconFactory.buttonIcon('\uf0eb', false));
782-
highlightButton.setToolTipText("Highlight selected bookmarks on the image (all if none selected)");
783-
highlightButton.addActionListener(e -> highlightBookmarks());
784-
final JButton clearHighlightButton = GuiUtils.Buttons.undo();
785-
clearHighlightButton.setToolTipText("Remove bookmark highlights from the image");
786-
clearHighlightButton.addActionListener(e -> clearHighlights());
787-
tb.add(highlightButton);
788-
tb.add(clearHighlightButton);
846+
highlightToggle = new JToggleButton(IconFactory.buttonIcon('\uf0eb', false));
847+
highlightToggle.setToolTipText("<html>Show bookmark locations on the image. With nothing selected,<br>"
848+
+ "all bookmarks are highlighted; otherwise only the selected ones.<br>"
849+
+ "Click again to toggle.");
850+
highlightToggle.addActionListener(e -> {
851+
if (highlightToggle.isSelected()) showHighlights();
852+
else hideHighlights();
853+
});
854+
tb.add(highlightToggle);
789855
}
790856
return tb;
791857
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ private void buildPopupMenu() {
239239

240240
private JMenuItem getSelectRoiMenuItem() {
241241
final JMenuItem selectByRoi = new JMenuItem(AListener.SELECT_BY_ROI);
242-
IconFactory.assignIcon(selectByRoi, '\uf248', false, IconFactory.defaultColor());
242+
IconFactory.assignIcon(selectByRoi, '\uf5ee', true, IconFactory.defaultColor());
243243
selectByRoi.addActionListener( e -> {
244244
if (pathAndFillManager.size() == 0) {
245245
getGuiUtils().error("There are no traced paths.", "Nothing to Select");

0 commit comments

Comments
 (0)