Skip to content

Commit 5bab7c4

Browse files
committed
mra/plots/ConfigIniPlot: Dropdown box to visualize long-ish configurations.
1 parent c959153 commit 5bab7c4

File tree

1 file changed

+190
-6
lines changed

1 file changed

+190
-6
lines changed

src/java/pt/lsts/neptus/mra/plots/ConfigIniPlot.java

Lines changed: 190 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,42 @@
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
*/
3333
package pt.lsts.neptus.mra.plots;
3434

3535
import com.l2fprod.common.propertysheet.DefaultProperty;
3636
import com.l2fprod.common.propertysheet.Property;
3737
import com.l2fprod.common.propertysheet.PropertySheet;
3838
import com.l2fprod.common.propertysheet.PropertySheetPanel;
39+
import com.l2fprod.common.propertysheet.PropertySheetTable;
3940
import com.l2fprod.common.propertysheet.PropertySheetTableModel.Item;
4041
import net.miginfocom.swing.MigLayout;
4142
import pt.lsts.neptus.mra.MRAPanel;
4243
import pt.lsts.neptus.mra.importers.IMraLogGroup;
4344
import pt.lsts.neptus.mra.visualizations.SimpleMRAVisualization;
4445
import pt.lsts.neptus.plugins.PluginDescription;
4546

47+
import javax.swing.BorderFactory;
4648
import javax.swing.JPanel;
49+
import javax.swing.JPopupMenu;
4750
import javax.swing.JScrollPane;
4851
import javax.swing.JLabel;
4952
import javax.swing.JComponent;
5053
import 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;
5267
import java.io.File;
5368
import java.io.IOException;
5469
import java.nio.charset.StandardCharsets;
@@ -64,13 +79,13 @@
6479
* @author jcordeiro
6580
*
6681
*/
67-
@PluginDescription(author = "jcordeiro", name = "Vehicle Configuration")
82+
@PluginDescription(author = "jcordeiro", name = "Vehicle Configuration", icon="images/settings2.png")
6883
public 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

Comments
 (0)