2222
2323package sc .fiji .snt ;
2424
25+ import ij .ImagePlus ;
2526import sc .fiji .snt .gui .GuiUtils ;
2627import sc .fiji .snt .gui .IconFactory ;
2728import sc .fiji .snt .gui .SNTEditorPane ;
29+ import sc .fiji .snt .viewer .AbstractBigViewer ;
2830
2931import javax .swing .*;
3032import java .awt .*;
33+ import java .awt .geom .Rectangle2D ;
3134import java .io .File ;
3235import java .io .FileWriter ;
3336import java .io .IOException ;
37+ import java .util .Collection ;
3438
3539/**
3640 * Implements the <i>Notepad</i> pane.
3943 */
4044public class NotesUI {
4145
46+ static { net .imagej .patcher .LegacyInjector .preinit (); } // required for _every_ class that imports ij. classes
47+
4248 private final SNTUI sntui ;
4349 private final SNTEditorPane editor ;
4450
@@ -121,11 +127,13 @@ private JComponent getToolBar() {
121127 final String cheatsheet = markDownOverview ();
122128 if (editor .getText ().contains (cheatsheet )) {
123129 editor .setText (editor .getText ().replace (cheatsheet , "" ));
130+ editor .requestFocusInWindow ();
124131 } else {
132+ final int cheatsheetStart = editor .getDocument ().getLength () + 1 ;
125133 editor .append (cheatsheet );
126- editor .setCaretPosition (editor .getDocument ().getLength ()-cheatsheet .length ());
134+ editor .setCaretPosition (cheatsheetStart );
135+ scrollToOffsetIfNotVisible (cheatsheetStart + cheatsheet .length ()/2 ); // scroll to middle
127136 }
128- editor .requestFocusInWindow ();
129137 });
130138 final JToolBar toolbar = new JToolBar ();
131139 toolbar .add (open );
@@ -137,24 +145,29 @@ private JComponent getToolBar() {
137145 toolbar .addSeparator ();
138146 toolbar .add (editor .timeStampButton (e -> {
139147 editor .appendTimeStamp ("*" , "*\n " );
140- editor .requestFocusInWindow ( );
148+ scrollToOffsetIfNotVisible ( editor .getDocument (). getLength () );
141149 }));
142150 final JButton settingsStamp = new JButton (IconFactory .buttonIcon ('\uf2db' , true , IconFactory .defaultColor ()));
143151 settingsStamp .setToolTipText ("Insert computation settings" );
144152 settingsStamp .addActionListener (e -> {
145153 editor .append ("**Computation Settings:**" );
146154 editor .append ("\n ```\n " + computationSettings () +"\n ```\n " );
147- editor .requestFocusInWindow ( );
155+ scrollToOffsetIfNotVisible ( editor .getDocument (). getLength () );
148156 });
149157 toolbar .add (settingsStamp );
150158 final JButton imgTitleStamp = new JButton (IconFactory .buttonIcon ('\uf03e' , true , IconFactory .defaultColor ()));
151- imgTitleStamp .setToolTipText ("Insert name of image being traced" );
159+ imgTitleStamp .setToolTipText ("Insert details on image being traced" );
152160 imgTitleStamp .addActionListener (e -> {
153161 if (!sntui .plugin .accessToValidImageData ()) {
154162 sntui .noValidImageDataError ();
155163 } else {
156- editor .append ("`" + sntui .plugin .getImagePlus ().getTitle () + "`\n " );
157- editor .requestFocusInWindow ();
164+ if (sntui .plugin .getImagePlus () != null )
165+ editor .append ("`" + standardModeImageDetails (sntui .plugin .getImagePlus ()) + "`\n " );
166+ else if (sntui .plugin .isStreamMode ())
167+ editor .append ("`" + streamModeImageDetails () + "`\n " );
168+ else
169+ editor .append ("`unknown image title`\n " );
170+ scrollToOffsetIfNotVisible (editor .getDocument ().getLength ());
158171 }
159172 });
160173 toolbar .add (imgTitleStamp );
@@ -166,7 +179,7 @@ private JComponent getToolBar() {
166179 sntui .error ("Current tracings do not seem to be associated with a TRACES file." );
167180 } else {
168181 editor .append ("`" + file .getName () + "`\n " );
169- editor .requestFocusInWindow ( );
182+ scrollToOffsetIfNotVisible ( editor .getDocument (). getLength () );
170183 }
171184 });
172185 toolbar .add (filenameStamp );
@@ -208,6 +221,61 @@ public String computationSettings() {
208221 return sntui .geSettingsString ();
209222 }
210223
224+ private String streamModeImageDetails () {
225+ final SNT p = sntui .plugin ;
226+ final StringBuilder sb = new StringBuilder ("Streamed data" );
227+ if (p .getWidth () > 0 && p .getHeight () > 0 && p .getDepth () > 0 ) {
228+ sb .append (String .format (" (%dx%dx%d px" , p .getWidth (), p .getHeight (), p .getDepth ()));
229+ if (p .getPixelWidth () > 0 && p .getPixelHeight () > 0 && p .getPixelDepth () > 0 ) {
230+ sb .append (String .format ("; %.3fx%.3fx%.3f %s/px" , p .getPixelWidth (), p .getPixelHeight (),
231+ p .getPixelDepth (), p .getSpacingUnits ()));
232+ }
233+ sb .append (")" );
234+ }
235+ if (p .isMaterializedCrop ()) sb .append (" [materialized crop]" );
236+ final AbstractBigViewer viewer = sntui .getActiveBigViewer ();
237+ if (viewer != null ) {
238+ final String sourcePath = viewer .getPrimarySourcePath ();
239+ if (sourcePath != null && !sourcePath .isBlank ()) sb .append (", source: " ).append (sourcePath );
240+ if (!viewer .getRenderedTrees ().isEmpty ())
241+ sb .append (", " ).append (viewer .getRenderedTrees ().size ()).append (" tree(s)" );
242+ if (viewer .hasMarkerManager () && viewer .getMarkerManager ().hasBookmarks ())
243+ sb .append (", " ).append (viewer .getMarkerManager ().getCount ()).append (" marker(s)" );
244+ }
245+ return sb .toString ();
246+ }
247+
248+ private String standardModeImageDetails (final ImagePlus imp ) {
249+ assert imp != null ;
250+ final StringBuilder sb = new StringBuilder (imp .getTitle ());
251+ sb .append (String .format (" (%dx%dx%d px" , imp .getWidth (), imp .getHeight (), imp .getNSlices ()));
252+ final ij .measure .Calibration cal = imp .getCalibration ();
253+ if (cal != null && cal .pixelWidth > 0 && cal .pixelHeight > 0 ) {
254+ sb .append (String .format ("; %.3fx%.3fx%.3f %s/px" , cal .pixelWidth , cal .pixelHeight ,
255+ (cal .pixelDepth > 0 ) ? cal .pixelDepth : 1d , cal .getUnit ()));
256+ }
257+ sb .append (")" );
258+ if (imp .getNChannels () > 1 ) sb .append (", " ).append (imp .getNChannels ()).append (" channels" );
259+ if (imp .getNFrames () > 1 ) sb .append (", " ).append (imp .getNFrames ()).append (" frames" );
260+ final String sourcePath = standardModeSourcePath (imp );
261+ if (sourcePath != null ) sb .append (", source: " ).append (sourcePath );
262+ final Collection <Tree > trees = sntui .plugin .getPathAndFillManager ().getTrees ();
263+ if (trees != null && !trees .isEmpty ()) sb .append (", " ).append (trees .size ()).append (" tree(s)" );
264+ if (sntui .getBookmarkManager ().hasBookmarks ())
265+ sb .append (", " ).append (sntui .getBookmarkManager ().getCount ()).append (" marker(s)" );
266+ return sb .toString ();
267+ }
268+
269+ /** Best-effort original file path/URL for {@code imp}, or null if unknown. */
270+ private String standardModeSourcePath (final ImagePlus imp ) {
271+ final ij .io .FileInfo fi = imp .getOriginalFileInfo ();
272+ if (fi == null ) return null ;
273+ if (fi .url != null && !fi .url .isBlank ()) return fi .url ;
274+ if (fi .directory != null && fi .fileName != null && !fi .fileName .isBlank ())
275+ return fi .directory + fi .fileName ;
276+ return null ;
277+ }
278+
211279 private void loadNotesFromFile (final File file ) {
212280 try {
213281 editor .open (file );
@@ -232,6 +300,23 @@ private boolean noNotesError() {
232300 return blank ;
233301 }
234302
303+ private void scrollToOffsetIfNotVisible (final int offset ) {
304+ SwingUtilities .invokeLater (() -> {
305+ try {
306+ final Rectangle2D r2d = editor .modelToView2D (offset );
307+ if (r2d == null ) return ;
308+ final Rectangle rect = r2d .getBounds ();
309+ if (!editor .getVisibleRect ().contains (rect )) {
310+ editor .scrollRectToVisible (rect );
311+ }
312+ } catch (final javax .swing .text .BadLocationException ignored ) {
313+ // offset no longer valid (e.g., editor content changed in the meantime): nothing to scroll to
314+ } finally {
315+ editor .requestFocusInWindow ();
316+ }
317+ });
318+ }
319+
235320 private void noRecordComment () {
236321 if (null != sntui .getRecorder (false ))
237322 sntui .getRecorder (false ).recordComment ("Notepad actions are currently not recorded" );
0 commit comments