Skip to content

Commit cf18fe7

Browse files
committed
Allow bookmarks to be sorted by position
1 parent d64d131 commit cf18fe7

1 file changed

Lines changed: 75 additions & 40 deletions

File tree

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

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import javax.swing.*;
4141
import javax.swing.table.AbstractTableModel;
4242
import javax.swing.table.DefaultTableCellRenderer;
43+
import javax.swing.table.TableRowSorter;
4344
import java.awt.*;
4445
import java.awt.event.MouseAdapter;
4546
import java.awt.event.MouseEvent;
@@ -357,9 +358,10 @@ private JPopupMenu assembleTablePopupMenu(final BookmarkTable table) {
357358
if (rows.length == 0) {
358359
guiUtils.error("No bookmark selected.");
359360
} else if (rows.length == 1) {
361+
final int modelCol = table.convertColumnIndexToView(1);
360362
if (table.getRowCount() > 10)
361-
table.scrollRectToVisible(new Rectangle(table.getCellRect(rows[0], 0, true)));
362-
table.editCellAt(rows[0], 1); // Column 1 is now Label
363+
table.scrollRectToVisible(new Rectangle(table.getCellRect(rows[0], modelCol, true)));
364+
table.editCellAt(rows[0], modelCol);
363365
} else {
364366
final String seed = guiUtils.getString(
365367
"Common label to be applied to " + rows.length + " bookmarks:", // msg
@@ -378,8 +380,7 @@ private JPopupMenu assembleTablePopupMenu(final BookmarkTable table) {
378380
// Color tags submenu
379381
final JMenu tagMenu = GuiUtils.MenuItems.colorTagMenu(sntui, color -> {
380382
if (noBookmarksError()) return;
381-
for (final int viewRow : getSelectedRowsAllIfNone()) {
382-
final int modelRow = table.convertRowIndexToModel(viewRow);
383+
for (final int modelRow : getSelectedModelRowsAllIfNone()) {
383384
model.setValueAt(color, modelRow, 0); // if color is null user chose "Remove Tag"
384385
}
385386
if (highlightToggle != null && highlightToggle.isSelected()) showHighlights();
@@ -390,11 +391,10 @@ private JPopupMenu assembleTablePopupMenu(final BookmarkTable table) {
390391
mi = new JMenuItem("Distinct Tags", IconFactory.menuIcon(IconFactory.GLYPH.SHUFFLE));
391392
mi.addActionListener(e -> {
392393
if (noBookmarksError()) return;
393-
final int[] rows = getSelectedRowsAllIfNone();
394-
final Color[] distinctColors = ColorMaps.glasbeyColorsAWT(rows.length);
394+
final int[] modelRows = getSelectedModelRowsAllIfNone();
395+
final Color[] distinctColors = ColorMaps.glasbeyColorsAWT(modelRows.length);
395396
int colorIdx = 0;
396-
for (final int viewRow : rows) {
397-
final int modelRow = table.convertRowIndexToModel(viewRow);
397+
for (final int modelRow : modelRows) {
398398
model.setValueAt(distinctColors[colorIdx++], modelRow, 0);
399399
}
400400
if (highlightToggle.isSelected()) showHighlights();
@@ -406,15 +406,16 @@ private JPopupMenu assembleTablePopupMenu(final BookmarkTable table) {
406406
mi = new JMenuItem("Size...", IconFactory.menuIcon(IconFactory.GLYPH.CIRCLE));
407407
mi.addActionListener(e -> {
408408
if (noBookmarksError()) return;
409-
final int[] rows = getSelectedRowsAllIfNone();
410-
final int lastCol = model.getColumnCount() - 1;
409+
final int[] modelRows = getSelectedModelRowsAllIfNone();
411410
final Double size = guiUtils.getDouble("Marker size (in calibrated units):",
412-
"Marker Size", (float)model.getValueAt(rows[rows.length-1], lastCol));
413-
if (size != null) {
414-
for (final int viewRow : rows) {
415-
final int modelRow = table.convertRowIndexToModel(viewRow);
416-
model.setValueAt(size, modelRow, lastCol);
417-
}
411+
"Marker Size", model.getDataList().get(modelRows[modelRows.length-1]).size);
412+
if (size == null) return;
413+
if (size.isNaN() || size < 0) {
414+
guiUtils.error("Invalid value: Size must be a non-negative value.");
415+
} else {
416+
for (final int modelRow : modelRows)
417+
model.getDataList().get(modelRow).size = size.floatValue();
418+
model.fireTableRowsUpdated(modelRows[0], modelRows[modelRows.length-1]);
418419
}
419420
});
420421
pMenu.add(mi);
@@ -425,28 +426,28 @@ private JPopupMenu assembleTablePopupMenu(final BookmarkTable table) {
425426
mi.setToolTipText("Matches bookmarks across channels within a distance threshold, replacing them with centroids");
426427
mi.addActionListener(e -> colocalizeBookmarks());
427428
pMenu.add(mi);
428-
mi = new JMenuItem("Merge...", IconFactory.menuIcon(IconFactory.GLYPH.ARROWS_TO_CIRCLE));
429-
mi.setToolTipText("Merges nearby bookmarks within each channel, replacing them with centroids");
430-
mi.addActionListener(e -> mergeBookmarks());
431-
pMenu.add(mi);
432-
pMenu.addSeparator();
433429
}
434430

431+
mi = new JMenuItem("Merge...", IconFactory.menuIcon(IconFactory.GLYPH.ARROWS_TO_CIRCLE));
432+
mi.setToolTipText("Merges nearby bookmarks within each channel, replacing them with centroids");
433+
mi.addActionListener(e -> mergeBookmarks());
434+
pMenu.add(mi);
435+
pMenu.add(sortByDistanceMenu());
436+
pMenu.addSeparator();
437+
435438
mi = new JMenuItem("Delete...", IconFactory.menuIcon(IconFactory.GLYPH.TRASH));
436439
mi.addActionListener(e -> {
437440
if (noBookmarksError()) return;
438-
final int[] viewRows = getSelectedRowsAllIfNone();
439-
if (viewRows.length == table.getRowCount()) {
441+
int[] modelRows = getSelectedModelRowsAllIfNone();
442+
if (modelRows.length == table.getRowCount()) {
440443
if (!guiUtils.getConfirmation("Delete all bookmarks?", "Delete All?")) {
441444
return;
442445
}
443446
reset();
444447
recordCmd("reset()");
445448
return; // Don't continue to delete rows that no longer exist
446449
}
447-
// Convert view indices to model indices (handles sorted table)
448-
final int[] modelRows = Arrays.stream(viewRows)
449-
.map(table::convertRowIndexToModel)
450+
modelRows = Arrays.stream(modelRows)
450451
.boxed()
451452
.sorted(Comparator.reverseOrder()) // Delete from end to preserve indices
452453
.mapToInt(Integer::intValue)
@@ -467,11 +468,14 @@ private JPopupMenu assembleTablePopupMenu(final BookmarkTable table) {
467468
return pMenu;
468469
}
469470

470-
private int[] getSelectedRowsAllIfNone() {
471+
private int[] getSelectedModelRowsAllIfNone() {
471472
int[] viewRows = table.getSelectedRows();
472473
if (viewRows.length == 0) {
473474
viewRows = IntStream.range(0, table.getRowCount()).toArray();
474475
}
476+
for (int i = 0; i < viewRows.length; i++) {
477+
viewRows[i] = table.convertRowIndexToModel(viewRows[i]);
478+
}
475479
return viewRows;
476480
}
477481

@@ -507,10 +511,6 @@ private void colocalizeBookmarks() {
507511

508512
private void mergeBookmarks() {
509513
if (noBookmarksError()) return;
510-
if (viewer != null) {
511-
guiUtils.error("Merge is not available in BVV mode.");
512-
return;
513-
}
514514
final List<Bookmark> candidates = getSelectedBookmarks();
515515
if (candidates.size() < 2) {
516516
guiUtils.error("At least 2 bookmarks are required for merging.");
@@ -543,14 +543,49 @@ private void mergeBookmarks() {
543543
}
544544

545545
private List<Bookmark> getSelectedBookmarks() {
546-
final int[] viewRows = getSelectedRowsAllIfNone();
547-
final List<Bookmark> candidates = new ArrayList<>();
548-
for (final int viewRow : viewRows) {
549-
candidates.add(model.getDataList().get(table.convertRowIndexToModel(viewRow)));
546+
final int[] modelRows = getSelectedModelRowsAllIfNone();
547+
final List<Bookmark> candidates = new ArrayList<>(modelRows.length);
548+
for (final int modelRow : modelRows) {
549+
candidates.add(model.getDataList().get(modelRow));
550550
}
551551
return candidates;
552552
}
553553

554+
private JMenu sortByDistanceMenu() {
555+
final JMenu menu = new JMenu("Sort by Distance");
556+
menu.setIcon(IconFactory.menuIcon(IconFactory.GLYPH.SORT));
557+
JMenuItem item = new JMenuItem("To Reference Location...", IconFactory.menuIcon(IconFactory.GLYPH.CROSSHAIR));
558+
item.addActionListener(e -> {
559+
if (noBookmarksError()) return;
560+
final SNTPoint input = SNTPoint.average(getSelectedBookmarks());
561+
final SNTPoint ref = guiUtils.getCoordinates("", "Reference Point (Calibrated Distances)",
562+
input, 2);
563+
if (ref != null) sortByPosition(new Bookmark("reference", ref.getX(), ref.getY(), ref.getZ(), 1, 1));
564+
});
565+
menu.add(item);
566+
item = new JMenuItem("To Selected Row Location", IconFactory.menuIcon(IconFactory.GLYPH.POINTER));
567+
item.addActionListener(e -> {
568+
if (noBookmarksError()) return;
569+
final List<Bookmark> selection = getSelectedBookmarks();
570+
if (selection.size() > 1) {
571+
guiUtils.error("Select a single row to be used as reference location and re-run.");
572+
} else {
573+
sortByPosition(selection.getFirst());
574+
}
575+
});
576+
menu.add(item);
577+
return menu;
578+
}
579+
580+
private void sortByPosition(final Bookmark origin) {
581+
if (table.isEditing()) table.getCellEditor().stopCellEditing();
582+
// Sort the backing list directly by 3D distance to origin
583+
model.getDataList().sort(Comparator.comparingDouble(b -> b.distanceSquaredTo(origin)));
584+
// Clear active column sort keys so the sorter does not re-order our result
585+
if (table.getRowSorter() instanceof TableRowSorter<?> sorter) sorter.setSortKeys(null);
586+
model.fireTableDataChanged();
587+
}
588+
554589
/**
555590
* Greedy nearest-match merge. For each seed, finds the closest unconsumed
556591
* bookmark in each target list within the threshold. Groups with at least
@@ -806,10 +841,9 @@ private void showHighlights() {
806841
}
807842
// Remove any existing highlights first (so re-clicking refreshes against the current selection)
808843
removeHighlightROIs(overlay);
809-
final int[] rows = getSelectedRowsAllIfNone();
844+
final int[] modelRows = getSelectedModelRowsAllIfNone();
810845
int idx = 0;
811-
for (final int viewRow : rows) {
812-
final int modelRow = table.convertRowIndexToModel(viewRow);
846+
for (final int modelRow : modelRows) {
813847
final Bookmark b = model.getDataList().get(modelRow);
814848
final PointRoi roi = b.toRoi();
815849
roi.setName(HIGHLIGHT_PREFIX + idx++);
@@ -820,7 +854,7 @@ private void showHighlights() {
820854
overlay.add(roi);
821855
}
822856
imp.updateAndDraw();
823-
sntui.showStatus(rows.length + " bookmark(s) highlighted on overlay", true);
857+
sntui.showStatus(modelRows.length + " bookmark(s) highlighted on overlay", true);
824858
syncHighlightToggle(true);
825859
}
826860

@@ -966,7 +1000,8 @@ private void bigViewerMarkerHelp(final AbstractBigViewer viewer) {
9661000
"to have the marker highlighted in the viewer." +
9671001
"<h3>Navigation</h3>" +
9681002
"Double-click a row to fly to that marker. Use the <b>&uarr;</b> / <b>&darr;</b> " +
969-
"buttons/keys to step through markers sequentially. Use the contextual menu for positional sorting." +
1003+
"buttons/keys to step through markers sequentially. Use the contextual menu for positional sorting, " +
1004+
"size/color adjustments, etc." +
9701005
"</body></html>";
9711006
new GuiUtils((table==null) ? null : table.getParent())
9721007
.showHTMLDialog(MARKER_HELP_MSG, "About " + viewerType + " Markers", false);

0 commit comments

Comments
 (0)