From 51f40e277aa20f71adc26951fde0503afd942a92 Mon Sep 17 00:00:00 2001 From: fjebaker Date: Tue, 3 Feb 2026 00:37:31 +0000 Subject: [PATCH 1/3] topcat: ellipsis in middle of JTable cells When a cell in a JTable is truncated, the last few characters are cut off and an ellipsis is inserted at the end. Often the most important bits of information are at the beginning and the end of the string, as e.g. the file extensions or table names in a DataLink URL are present only at the end of the string. An extreme example: topcat/src/main/uk/ac/st... topcat/src/main/uk/ac/st... This adds a custom LabelUI which modifies how all labels in Swing are drawn, so that if an ellipsis is inserted, it is put in the middle of the string instead of at the end. The above becomes: topcat/src/m...striver.java topcat/src/m...atModel.java --- .../uk/ac/starlink/topcat/CustomLabelUI.java | 103 ++++++++++++++++++ .../main/uk/ac/starlink/topcat/Driver.java | 5 + 2 files changed, 108 insertions(+) create mode 100644 topcat/src/main/uk/ac/starlink/topcat/CustomLabelUI.java diff --git a/topcat/src/main/uk/ac/starlink/topcat/CustomLabelUI.java b/topcat/src/main/uk/ac/starlink/topcat/CustomLabelUI.java new file mode 100644 index 000000000..c660535a4 --- /dev/null +++ b/topcat/src/main/uk/ac/starlink/topcat/CustomLabelUI.java @@ -0,0 +1,103 @@ +package uk.ac.starlink.topcat; + +import java.awt.FontMetrics; +import java.awt.Rectangle; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.plaf.basic.BasicLabelUI; + +/** + * An implementation of the (Basic)LabelUI for overriding JLabel formatting in + * the cells of JTables. + * + * This is used to change the location of the ellipsis when a string is + * rendered in a cell that cannot fit. By default, the ellipsis is always at + * the end of the string, but this class instead places the ellipsis in the + * middle of the string. See + * + * https://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/basic/BasicLabelUI.html + * + * @author Fergus Baker + * @since 03 Feb 2026 + */ +public class CustomLabelUI extends BasicLabelUI { + private static final String ELLIPSES = "..."; + private static final int ELLIPSES_LEN = ELLIPSES.length(); + private String displayedText; + + /** + * Implicit constructor, called by Swing with a particular component in + * order to have something for drawing labels to the screen. + */ + public static CustomLabelUI createUI(JComponent c) { + return new CustomLabelUI(); + } + + /** + * Returns the text displayed in the label. + * + * @return String + */ + public String getText() { + return displayedText; + } + + private int findTruncationSplit(int maxWidth, FontMetrics fontMetrics, + String text) { + int textLength = text.length(); + for (int i = 1; i <= textLength; i++) { + int subTextWidth = fontMetrics.stringWidth( + text.substring( textLength - i ) + ); + if (subTextWidth > maxWidth) { + return textLength - i + 1; + } + } + return 0; + } + + @Override + protected String layoutCL(JLabel label, FontMetrics fontMetrics, + String text, Icon icon, Rectangle viewR, + Rectangle iconR, Rectangle textR) { + /* Draw the text with the usual layout for a compound label. */ + displayedText = super.layoutCL(label, fontMetrics, text, icon, viewR, + iconR, textR); + + /* Sometimes the font metrics is null if the text is being displayed + * before the UI is ready. In such a case, there is nothing that this + * class needs to do. */ + if (fontMetrics == null) { + return displayedText; + } + + int displayedLength = displayedText.length(); + + /* Check if a truncation occurred, and if the text is long enough. If + * the text is too short, there is no point in truncating differently. + * */; + if (!displayedText.equals(text) && displayedLength > 2 * ELLIPSES_LEN) { + int splitLength = (displayedLength + ELLIPSES_LEN) / 2 - ELLIPSES_LEN; + String beforeEllipses = displayedText.substring(0, splitLength); + /* Determine the maximum width (in pixels) that the remaining text + * should fit into. */ + int displayedTextWidth = fontMetrics.stringWidth(displayedText); + int maxWidth = ( + displayedTextWidth + - fontMetrics.stringWidth(ELLIPSES) + - fontMetrics.stringWidth(beforeEllipses) + ); + + int splitIndex = findTruncationSplit( + maxWidth, fontMetrics, text + ); + + displayedText = ( + beforeEllipses + ELLIPSES + text.substring(splitIndex) + ); + } + + return displayedText; + } +} diff --git a/topcat/src/main/uk/ac/starlink/topcat/Driver.java b/topcat/src/main/uk/ac/starlink/topcat/Driver.java index c6965daeb..c6a147dcb 100644 --- a/topcat/src/main/uk/ac/starlink/topcat/Driver.java +++ b/topcat/src/main/uk/ac/starlink/topcat/Driver.java @@ -11,6 +11,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; +import javax.swing.UIManager; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.JSamp; import org.astrogrid.samp.Response; @@ -35,6 +36,7 @@ import uk.ac.starlink.table.jdbc.TextModelsAuthenticator; import uk.ac.starlink.task.InvokeUtils; import uk.ac.starlink.topcat.interop.TopcatCommunicator; +import uk.ac.starlink.topcat.CustomLabelUI; import uk.ac.starlink.ttools.Stilts; import uk.ac.starlink.util.gui.ErrorDialog; import uk.ac.starlink.util.DataSource; @@ -126,6 +128,9 @@ private static void runMain( String[] args ) // never mind cmdname = null; } + /* Register custom LabelUI for drawing cells in JTable. This has to + * happen before any of the UI is initialised. */ + UIManager.put("LabelUI", CustomLabelUI.class.getName()); Loader.tweakGuiForMac(); Loader.setHttpAgent( TopcatUtils.getHttpUserAgent() ); Loader.setDefaultProperty( "java.awt.Window.locationByPlatform", From 7aaa4244c0e8f7f55a1be414bd4204344f41a356 Mon Sep 17 00:00:00 2001 From: Mark Taylor Date: Wed, 4 Feb 2026 13:33:01 +0000 Subject: [PATCH 2/3] topcat: reduce scope of CustomLabelUI The LabelUI that inserts ellipses inside overlong strings is now used only for suitable datatypes rendered in a StarTableColumn (within a JTable) rather than all instances of JLabel. --- .../src/main/uk/ac/starlink/table/gui}/CustomLabelUI.java | 2 +- .../main/uk/ac/starlink/table/gui/ValueInfoCellRenderer.java | 3 +++ topcat/src/docs/sun253.xml | 4 ++++ topcat/src/main/uk/ac/starlink/topcat/Driver.java | 5 ----- ttools/src/docs/sun256.xml | 4 ++++ 5 files changed, 12 insertions(+), 6 deletions(-) rename {topcat/src/main/uk/ac/starlink/topcat => table/src/main/uk/ac/starlink/table/gui}/CustomLabelUI.java (99%) diff --git a/topcat/src/main/uk/ac/starlink/topcat/CustomLabelUI.java b/table/src/main/uk/ac/starlink/table/gui/CustomLabelUI.java similarity index 99% rename from topcat/src/main/uk/ac/starlink/topcat/CustomLabelUI.java rename to table/src/main/uk/ac/starlink/table/gui/CustomLabelUI.java index c660535a4..9838ae960 100644 --- a/topcat/src/main/uk/ac/starlink/topcat/CustomLabelUI.java +++ b/table/src/main/uk/ac/starlink/table/gui/CustomLabelUI.java @@ -1,4 +1,4 @@ -package uk.ac.starlink.topcat; +package uk.ac.starlink.table.gui; import java.awt.FontMetrics; import java.awt.Rectangle; diff --git a/table/src/main/uk/ac/starlink/table/gui/ValueInfoCellRenderer.java b/table/src/main/uk/ac/starlink/table/gui/ValueInfoCellRenderer.java index e6213ff97..50befc66e 100644 --- a/table/src/main/uk/ac/starlink/table/gui/ValueInfoCellRenderer.java +++ b/table/src/main/uk/ac/starlink/table/gui/ValueInfoCellRenderer.java @@ -24,6 +24,9 @@ public class ValueInfoCellRenderer extends DefaultTableCellRenderer { */ public ValueInfoCellRenderer( ValueInfo vinfo ) { this.vinfo = vinfo; + setUI( new CustomLabelUI() ); + setForeground( null ); + setBackground( null ); } /** diff --git a/topcat/src/docs/sun253.xml b/topcat/src/docs/sun253.xml index cecbb27d0..769446bea 100644 --- a/topcat/src/docs/sun253.xml +++ b/topcat/src/docs/sun253.xml @@ -34922,6 +34922,10 @@ introduced since the last version: in an illegal order.
  • Substantial performance improvements (a few tens of percent) when writing ASCII and CSV files.
  • +
  • Overlong text and array fields in the + Data Window + are now displayed with an ellipsis (...) in the middle + rather than at the end.
  • diff --git a/topcat/src/main/uk/ac/starlink/topcat/Driver.java b/topcat/src/main/uk/ac/starlink/topcat/Driver.java index c6a147dcb..c6965daeb 100644 --- a/topcat/src/main/uk/ac/starlink/topcat/Driver.java +++ b/topcat/src/main/uk/ac/starlink/topcat/Driver.java @@ -11,7 +11,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; -import javax.swing.UIManager; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.JSamp; import org.astrogrid.samp.Response; @@ -36,7 +35,6 @@ import uk.ac.starlink.table.jdbc.TextModelsAuthenticator; import uk.ac.starlink.task.InvokeUtils; import uk.ac.starlink.topcat.interop.TopcatCommunicator; -import uk.ac.starlink.topcat.CustomLabelUI; import uk.ac.starlink.ttools.Stilts; import uk.ac.starlink.util.gui.ErrorDialog; import uk.ac.starlink.util.DataSource; @@ -128,9 +126,6 @@ private static void runMain( String[] args ) // never mind cmdname = null; } - /* Register custom LabelUI for drawing cells in JTable. This has to - * happen before any of the UI is initialised. */ - UIManager.put("LabelUI", CustomLabelUI.class.getName()); Loader.tweakGuiForMac(); Loader.setHttpAgent( TopcatUtils.getHttpUserAgent() ); Loader.setDefaultProperty( "java.awt.Window.locationByPlatform", diff --git a/ttools/src/docs/sun256.xml b/ttools/src/docs/sun256.xml index 048e4b0e1..eae1dbda2 100644 --- a/ttools/src/docs/sun256.xml +++ b/ttools/src/docs/sun256.xml @@ -15946,6 +15946,10 @@ eds. C. Gabriel et al., ASP Conf. Ser. 351, p. 666 (2006) in an illegal order.
  • Substantial performance improvements (a few tens of percent) when writing ASCII and CSV files.
  • +
  • Overlong text and array fields in the + gui output mode table + window are now displayed with an ellipsis (...) in the middle + rather than at the end.
  • From b2a05cb4ea5cd0ceeed21f53601834536e2b10aa Mon Sep 17 00:00:00 2001 From: Mark Taylor Date: Wed, 4 Feb 2026 14:25:01 +0000 Subject: [PATCH 3/3] table: CustomLabelUI source file cosmetics Apply house style to contributed CustomLabelUI file. Mostly whitespace jiggling. --- .../ac/starlink/table/gui/CustomLabelUI.java | 74 +++++++++---------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/table/src/main/uk/ac/starlink/table/gui/CustomLabelUI.java b/table/src/main/uk/ac/starlink/table/gui/CustomLabelUI.java index 9838ae960..e7f69eeb2 100644 --- a/table/src/main/uk/ac/starlink/table/gui/CustomLabelUI.java +++ b/table/src/main/uk/ac/starlink/table/gui/CustomLabelUI.java @@ -14,17 +14,16 @@ * This is used to change the location of the ellipsis when a string is * rendered in a cell that cannot fit. By default, the ellipsis is always at * the end of the string, but this class instead places the ellipsis in the - * middle of the string. See - * - * https://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/basic/BasicLabelUI.html + * middle of the string. * * @author Fergus Baker * @since 03 Feb 2026 + * @see javax.swing.plaf.basic.BasicLabelUI */ public class CustomLabelUI extends BasicLabelUI { private static final String ELLIPSES = "..."; private static final int ELLIPSES_LEN = ELLIPSES.length(); - private String displayedText; + private String displayedText_; /** * Implicit constructor, called by Swing with a particular component in @@ -40,17 +39,16 @@ public static CustomLabelUI createUI(JComponent c) { * @return String */ public String getText() { - return displayedText; + return displayedText_; } - private int findTruncationSplit(int maxWidth, FontMetrics fontMetrics, - String text) { + private int findTruncationSplit( int maxWidth, FontMetrics fontMetrics, + String text ) { int textLength = text.length(); - for (int i = 1; i <= textLength; i++) { - int subTextWidth = fontMetrics.stringWidth( - text.substring( textLength - i ) - ); - if (subTextWidth > maxWidth) { + for ( int i = 1; i <= textLength; i++ ) { + int subTextWidth = + fontMetrics.stringWidth( text.substring( textLength - i ) ); + if ( subTextWidth > maxWidth ) { return textLength - i + 1; } } @@ -58,46 +56,42 @@ private int findTruncationSplit(int maxWidth, FontMetrics fontMetrics, } @Override - protected String layoutCL(JLabel label, FontMetrics fontMetrics, - String text, Icon icon, Rectangle viewR, - Rectangle iconR, Rectangle textR) { + protected String layoutCL( JLabel label, FontMetrics fontMetrics, + String text, Icon icon, Rectangle viewR, + Rectangle iconR, Rectangle textR ) { + /* Draw the text with the usual layout for a compound label. */ - displayedText = super.layoutCL(label, fontMetrics, text, icon, viewR, - iconR, textR); + displayedText_ = super.layoutCL( label, fontMetrics, text, icon, viewR, + iconR, textR ); /* Sometimes the font metrics is null if the text is being displayed * before the UI is ready. In such a case, there is nothing that this * class needs to do. */ - if (fontMetrics == null) { - return displayedText; + if ( fontMetrics == null ) { + return displayedText_; } - int displayedLength = displayedText.length(); /* Check if a truncation occurred, and if the text is long enough. If - * the text is too short, there is no point in truncating differently. - * */; - if (!displayedText.equals(text) && displayedLength > 2 * ELLIPSES_LEN) { - int splitLength = (displayedLength + ELLIPSES_LEN) / 2 - ELLIPSES_LEN; - String beforeEllipses = displayedText.substring(0, splitLength); + * the text is too short, there is no point in truncating differently.*/ + int displayedLength = displayedText_.length(); + if ( !displayedText_.equals( text ) && + displayedLength > 2 * ELLIPSES_LEN ) { + int splitLength = + ( displayedLength + ELLIPSES_LEN ) / 2 - ELLIPSES_LEN; + String beforeEllipses = displayedText_.substring( 0, splitLength ); + /* Determine the maximum width (in pixels) that the remaining text * should fit into. */ - int displayedTextWidth = fontMetrics.stringWidth(displayedText); - int maxWidth = ( - displayedTextWidth - - fontMetrics.stringWidth(ELLIPSES) - - fontMetrics.stringWidth(beforeEllipses) - ); - - int splitIndex = findTruncationSplit( - maxWidth, fontMetrics, text - ); - - displayedText = ( - beforeEllipses + ELLIPSES + text.substring(splitIndex) - ); + int displayedTextWidth = fontMetrics.stringWidth( displayedText_ ); + int maxWidth = displayedTextWidth + - fontMetrics.stringWidth( ELLIPSES ) + - fontMetrics.stringWidth( beforeEllipses ); + int splitIndex = findTruncationSplit( maxWidth, fontMetrics, text ); + displayedText_ = + beforeEllipses + ELLIPSES + text.substring( splitIndex ); } - return displayedText; + return displayedText_; } }