2828 * For more information please see <http://lsts.fe.up.pt/neptus>.
2929 *
3030 * Author: jcordeiro
31- * November 20 , 2025
31+ * November 25 , 2025
3232 */
3333package pt .lsts .neptus .mra .plots ;
3434
3535import com .l2fprod .common .propertysheet .DefaultProperty ;
3636import com .l2fprod .common .propertysheet .Property ;
3737import com .l2fprod .common .propertysheet .PropertySheet ;
3838import com .l2fprod .common .propertysheet .PropertySheetPanel ;
39+ import com .l2fprod .common .propertysheet .PropertySheetTable ;
3940import com .l2fprod .common .propertysheet .PropertySheetTableModel .Item ;
4041import net .miginfocom .swing .MigLayout ;
4142import pt .lsts .neptus .mra .MRAPanel ;
4243import pt .lsts .neptus .mra .importers .IMraLogGroup ;
4344import pt .lsts .neptus .mra .visualizations .SimpleMRAVisualization ;
4445import pt .lsts .neptus .plugins .PluginDescription ;
4546
47+ import javax .swing .BorderFactory ;
4648import javax .swing .JPanel ;
49+ import javax .swing .JPopupMenu ;
4750import javax .swing .JScrollPane ;
4851import javax .swing .JLabel ;
4952import javax .swing .JComponent ;
5053import javax .swing .JButton ;
51-
54+ import javax .swing .JTable ;
55+ import javax .swing .JTextArea ;
56+ import javax .swing .SwingUtilities ;
57+ import javax .swing .UIManager ;
58+
59+ import java .awt .BorderLayout ;
60+ import java .awt .Dimension ;
61+ import java .awt .FontMetrics ;
62+ import java .awt .Point ;
63+ import java .awt .Rectangle ;
64+ import java .awt .Window ;
65+ import java .awt .event .MouseAdapter ;
66+ import java .awt .event .MouseEvent ;
5267import java .io .File ;
5368import java .io .IOException ;
5469import java .nio .charset .StandardCharsets ;
6479 * @author jcordeiro
6580 *
6681 */
67- @ PluginDescription (author = "jcordeiro" , name = "Vehicle Configuration" )
82+ @ PluginDescription (author = "jcordeiro" , name = "Vehicle Configuration" , icon = "images/settings2.png" )
6883public class ConfigIniPlot extends SimpleMRAVisualization {
6984
7085 private File iniFile ;
7186 private final Map <String , List <Property >> sectionMap = new LinkedHashMap <>();
7287 PropertySheetPanel sheet = new PropertySheetPanel ();
73-
88+ private JPopupMenu currentlyOpenDropdown = null ;
7489
7590 public ConfigIniPlot (MRAPanel panel ) {
7691 super (panel );
@@ -106,7 +121,7 @@ public JComponent getVisualization(IMraLogGroup source, double timestep) {
106121
107122 JLabel titleLabel = new JLabel ("<html><h2>Vehicle Configuration</h2></html>" );
108123 mainPanel .add (titleLabel , "w 100%, wrap" );
109-
124+
110125 // expand/collapse buttons
111126 JButton collapseButton = new JButton ("Collapse All" );
112127 collapseButton .addActionListener (e -> collapseAllCategories ());
@@ -130,7 +145,6 @@ public JComponent getVisualization(IMraLogGroup source, double timestep) {
130145
131146 for (Map .Entry <String , List <Property >> entry : sections .entrySet ()) {
132147
133-
134148 for (Property p : entry .getValue ()) {
135149 sheet .addProperty (p );
136150 }
@@ -141,6 +155,11 @@ public JComponent getVisualization(IMraLogGroup source, double timestep) {
141155 }
142156 }
143157
158+ SwingUtilities .invokeLater (() -> {
159+ collapseAllCategories ();
160+ installDropdownOnTable (sheet );
161+ });
162+
144163 JScrollPane scroll = new JScrollPane (sheet );
145164 scroll .setBorder (null );
146165 mainPanel .add (scroll , "grow, push" );
@@ -234,5 +253,170 @@ private void expandAllCategories() {
234253 }
235254 }
236255 }
256+
257+ private void installDropdownOnTable (PropertySheetPanel sheet ) {
258+ PropertySheetTable table = (PropertySheetTable ) sheet .getTable ();
259+
260+ table .addMouseListener (new MouseAdapter () {
261+ @ Override
262+ public void mouseClicked (MouseEvent e ) {
263+ // close if already open
264+ if (currentlyOpenDropdown != null ) {
265+ currentlyOpenDropdown .setVisible (false );
266+ currentlyOpenDropdown = null ;
267+ return ;
268+ }
269+
270+ int row = table .rowAtPoint (e .getPoint ());
271+ int col = table .columnAtPoint (e .getPoint ());
272+
273+ if (row < 0 || col != 1 ) {
274+ return ;
275+ }
276+
277+ Object o = table .getSheetModel ().getObject (row );
278+ if (!(o instanceof Item )) {
279+ return ;
280+ }
281+
282+ Item item = (Item ) o ;
283+
284+ // ignore if category
285+ if (item .hasToggle ()) {
286+ return ;
287+ }
288+
289+ Property property = item .getProperty ();
290+ if (property == null ) {
291+ return ;
292+ }
293+
294+ Object val = property .getValue ();
295+ if (val == null ) {
296+ return ;
297+ }
298+
299+ String full = val .toString ().trim ();
300+ if (full .isEmpty ()) {
301+ return ;
302+ }
303+
304+ // show dropdown only if the content is long-ish
305+ FontMetrics fm = table .getFontMetrics (table .getFont ());
306+ int colWidth = table .getColumnModel ().getColumn (col ).getWidth ();
307+ int textWidth = SwingUtilities .computeStringWidth (fm , full );
308+
309+ if (textWidth <= colWidth - 10 ) {
310+ return ;
311+ }
312+
313+ // split by comma and trim, each value per line
314+ String [] values = full .split ("\\ s*,\\ s*" );
315+
316+ // build multiline text
317+ StringBuilder multiline = new StringBuilder ();
318+ for (int i = 0 ; i < values .length ; i ++) {
319+ multiline .append (values [i ]);
320+ if (i < values .length - 1 )
321+ multiline .append ("\n " );
322+ }
323+ String text = multiline .toString ();
324+
325+ // create popup
326+ JPopupMenu dropdown = new JPopupMenu ();
327+ dropdown .setLayout (new BorderLayout ());
328+
329+ // selectable text area
330+ JTextArea textArea = new JTextArea (text );
331+ textArea .setEditable (false );
332+ textArea .setLineWrap (false );
333+ textArea .setWrapStyleWord (false );
334+ textArea .setBorder (BorderFactory .createEmptyBorder (5 , 5 , 5 , 5 ));
335+ textArea .setBackground (UIManager .getColor ("Panel.background" ));
336+ textArea .setFont (table .getFont ());
337+ textArea .setCaretPosition (0 );
338+
339+ // select whole line with one click
340+ textArea .addMouseListener (new MouseAdapter () {
341+ @ Override
342+ public void mouseClicked (MouseEvent me ) {
343+ int pos = textArea .viewToModel2D (me .getPoint ());
344+ if (pos >= 0 ) {
345+ try {
346+ int line = textArea .getLineOfOffset (pos );
347+ int start = textArea .getLineStartOffset (line );
348+ int end = textArea .getLineEndOffset (line );
349+ // trim trailing newline from selection
350+ if (end > start && textArea .getText (end - 1 , 1 ).equals ("\n " )) {
351+ end = end - 1 ;
352+ }
353+ textArea .requestFocusInWindow ();
354+ textArea .select (start , end );
355+ } catch (Exception ex ) {
356+ // ignore
357+ }
358+ }
359+ }
360+ });
361+
362+ JScrollPane scroll = new JScrollPane (textArea );
363+ scroll .setBorder (null );
364+ scroll .setHorizontalScrollBarPolicy (JScrollPane .HORIZONTAL_SCROLLBAR_NEVER );
365+
366+ int maxWidth = 200 ;
367+ for (String v : values ) {
368+ maxWidth = Math .max (maxWidth , SwingUtilities .computeStringWidth (fm , v ) + 40 );
369+ }
370+ maxWidth = Math .min (800 , maxWidth );
371+
372+ configureScrollSize (scroll , fm , values .length , maxWidth );
373+
374+ dropdown .add (scroll , BorderLayout .CENTER );
375+
376+ positionDropdown (dropdown , table , row , col );
377+ showDropdown (dropdown , table );
378+
379+ currentlyOpenDropdown = dropdown ;
380+ }
381+ });
382+ }
383+
384+ private void configureScrollSize (JScrollPane scroll , FontMetrics fm , int lineCount , int maxWidth ) {
385+ int lineHeight = fm .getHeight ();
386+ int visibleLines = Math .min (lineCount , 6 );
387+ int prefHeight = visibleLines * lineHeight + 10 ;
388+ scroll .setPreferredSize (new Dimension (maxWidth , prefHeight ));
389+ }
390+
391+ private void positionDropdown (JPopupMenu dropdown , JTable table , int row , int col ) {
392+ Rectangle rect = table .getCellRect (row , col , true );
393+ Point p = new Point (rect .x , rect .y + rect .height );
394+ SwingUtilities .convertPointToScreen (p , table );
395+
396+ Window parentWindow = SwingUtilities .getWindowAncestor (table );
397+ if (parentWindow != null ) {
398+ Rectangle parentBounds = parentWindow .getBounds ();
399+ Dimension dropdownSize = dropdown .getPreferredSize ();
400+
401+ if (p .y + dropdownSize .height > parentBounds .y + parentBounds .height ) {
402+ p .y = parentBounds .y + parentBounds .height - dropdownSize .height ;
403+ }
404+
405+ if (p .x + dropdownSize .width > parentBounds .x + parentBounds .width ) {
406+ p .x = parentBounds .x + parentBounds .width - dropdownSize .width ;
407+ }
408+
409+ p .y = Math .max (p .y , parentBounds .y );
410+ p .x = Math .max (p .x , parentBounds .x );
411+ }
412+
413+ dropdown .setLocation (p );
414+ }
415+
416+ private void showDropdown (JPopupMenu dropdown , JTable table ) {
417+ dropdown .setInvoker (table );
418+ dropdown .setSize (dropdown .getPreferredSize ());
419+ dropdown .setVisible (true );
420+ }
237421}
238422
0 commit comments