4040import javax .swing .*;
4141import javax .swing .table .AbstractTableModel ;
4242import javax .swing .table .DefaultTableCellRenderer ;
43+ import javax .swing .table .TableRowSorter ;
4344import java .awt .*;
4445import java .awt .event .MouseAdapter ;
4546import 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>↑</b> / <b>↓</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