diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java b/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java index 2aff94005..fd19e0f9f 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicForces.java @@ -234,6 +234,10 @@ public class AerodynamicForces implements Cloneable, Monitorable { modID = new ModID(); } + /** + * Get the drag coefficient <b>per instance</b>. + * @return The drag coefficient. + */ public double getCD() { if (component == null) return CD; @@ -245,6 +249,10 @@ public class AerodynamicForces implements Cloneable, Monitorable { return CD; } + public double getCDTotal() { + return getCD() * component.getInstanceCount(); + } + public void setPressureCD(double pressureCD) { if (this.pressureCD == pressureCD) return; diff --git a/core/src/main/java/info/openrocket/core/componentanalysis/CADataType.java b/core/src/main/java/info/openrocket/core/componentanalysis/CADataType.java index c18005a17..d6639f05e 100644 --- a/core/src/main/java/info/openrocket/core/componentanalysis/CADataType.java +++ b/core/src/main/java/info/openrocket/core/componentanalysis/CADataType.java @@ -39,8 +39,10 @@ public class CADataType implements Comparable<CADataType>, Groupable<CADataTypeG "CD,base", UnitGroup.UNITS_NONE, CADataTypeGroup.DRAG, 21); public static final CADataType FRICTION_CD = new CADataType(trans.get("ComponentAnalysisGeneralTab.dragTableModel.Col.friction"), "CD,friction", UnitGroup.UNITS_NONE, CADataTypeGroup.DRAG, 22); + public static final CADataType PER_INSTANCE_CD = new CADataType(trans.get("ComponentAnalysisGeneralTab.dragTableModel.Col.perInstance"), + "CD,instance", UnitGroup.UNITS_NONE, CADataTypeGroup.DRAG, 23); public static final CADataType TOTAL_CD = new CADataType(trans.get("ComponentAnalysisGeneralTab.dragTableModel.Col.total"), - "CD,total", UnitGroup.UNITS_NONE, CADataTypeGroup.DRAG, 23); + "CD,total", UnitGroup.UNITS_NONE, CADataTypeGroup.DRAG, 24); //// Roll public static final CADataType ROLL_FORCING_COEFFICIENT = new CADataType(trans.get("ComponentAnalysisGeneralTab.rollTableModel.Col.rollforc"), @@ -53,7 +55,7 @@ public class CADataType implements Comparable<CADataType>, Groupable<CADataTypeG public static final CADataType[] ALL_TYPES = { CP_X, CNa, - PRESSURE_CD, BASE_CD, FRICTION_CD, TOTAL_CD, + PRESSURE_CD, BASE_CD, FRICTION_CD, PER_INSTANCE_CD, TOTAL_CD, ROLL_FORCING_COEFFICIENT, ROLL_DAMPING_COEFFICIENT, TOTAL_ROLL_COEFFICIENT }; @@ -124,9 +126,14 @@ public class CADataType implements Comparable<CADataType>, Groupable<CADataTypeG return false; } + // Doesn't make sense to calculate per-instance drag for rockets + if (component instanceof Rocket && type.equals(CADataType.PER_INSTANCE_CD)) { + return false; + } + if (type.equals(CADataType.CP_X) || type.equals(CADataType.CNa) || type.equals(CADataType.PRESSURE_CD) || type.equals(CADataType.BASE_CD) || - type.equals(CADataType.FRICTION_CD) || type.equals(CADataType.TOTAL_CD)) { + type.equals(CADataType.FRICTION_CD) || type.equals(CADataType.PER_INSTANCE_CD) || type.equals(CADataType.TOTAL_CD)) { return true; } else if (type.equals(CADataType.ROLL_FORCING_COEFFICIENT) || type.equals(CADataType.ROLL_DAMPING_COEFFICIENT) || type.equals(CADataType.TOTAL_ROLL_COEFFICIENT)) { diff --git a/core/src/main/java/info/openrocket/core/componentanalysis/CAParameterSweep.java b/core/src/main/java/info/openrocket/core/componentanalysis/CAParameterSweep.java index 24ed95f18..13c956fe3 100644 --- a/core/src/main/java/info/openrocket/core/componentanalysis/CAParameterSweep.java +++ b/core/src/main/java/info/openrocket/core/componentanalysis/CAParameterSweep.java @@ -129,7 +129,8 @@ public class CAParameterSweep { dataBranch.setValue(CADataType.PRESSURE_CD, component, forces.getPressureCD()); dataBranch.setValue(CADataType.BASE_CD, component, forces.getBaseCD()); dataBranch.setValue(CADataType.FRICTION_CD, component, forces.getFrictionCD()); - dataBranch.setValue(CADataType.TOTAL_CD, component, forces.getCD()); + dataBranch.setValue(CADataType.PER_INSTANCE_CD, component, forces.getCD()); + dataBranch.setValue(CADataType.TOTAL_CD, component, forces.getCDTotal()); } if (component instanceof FinSet) { @@ -154,7 +155,8 @@ public class CAParameterSweep { dataBranch.setValue(CADataType.PRESSURE_CD, rocket, totalForces.getPressureCD()); dataBranch.setValue(CADataType.BASE_CD, rocket, totalForces.getBaseCD()); dataBranch.setValue(CADataType.FRICTION_CD, rocket, totalForces.getFrictionCD()); - dataBranch.setValue(CADataType.TOTAL_CD, rocket, totalForces.getCD()); + dataBranch.setValue(CADataType.PER_INSTANCE_CD, rocket, totalForces.getCD()); + dataBranch.setValue(CADataType.TOTAL_CD, rocket, totalForces.getCDTotal()); } } diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index b684653bc..41229c7c3 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -940,6 +940,7 @@ ComponentAnalysisGeneralTab.dragTableModel.Col.Component = Component ComponentAnalysisGeneralTab.dragTableModel.Col.Pressure = <html>Pressure C<sub>D</sub> ComponentAnalysisGeneralTab.dragTableModel.Col.Base = <html>Base C<sub>D</sub> ComponentAnalysisGeneralTab.dragTableModel.Col.friction = <html>Friction C<sub>D</sub> +ComponentAnalysisGeneralTab.dragTableModel.Col.perInstance = <html>Per instance C<sub>D</sub> ComponentAnalysisGeneralTab.dragTableModel.Col.total = <html>Total C<sub>D</sub> ComponentAnalysisGeneralTab.dragTabchar = Drag characteristics ComponentAnalysisGeneralTab.dragTabchar.ttip = Drag characteristics diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotTypeSelector.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotTypeSelector.java index 9eb4c1bf6..f860750e9 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotTypeSelector.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotTypeSelector.java @@ -33,7 +33,7 @@ public class CAPlotTypeSelector extends PlotTypeSelector<CADataType, CADataTypeG componentSelector = new JComboBox<>(componentsForType.toArray(new RocketComponent[0])); componentSelector.setSelectedItem(selectedComponent); configuration.setPlotDataComponent(plotIndex, selectedComponent); - this.add(componentSelector, "gapright para"); + this.add(componentSelector, "growx, gapright para"); addRemoveButton(); diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisGeneralPanel.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisGeneralPanel.java index 0687eeb28..0e7feb1da 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisGeneralPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisGeneralPanel.java @@ -38,6 +38,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.BorderFactory; +import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; @@ -50,11 +51,19 @@ import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.plaf.LabelUI; +import javax.swing.plaf.basic.BasicLabelUI; import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableModel; +import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -112,8 +121,6 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange this.theta = new DoubleModel(parameters, "Theta", UnitGroup.UNITS_ANGLE, 0, 2 * Math.PI); this.roll = new DoubleModel(parameters, "RollRate", UnitGroup.UNITS_ROLL); - JTable table; - //// Wind direction: this.add(new JLabel(trans.get("ComponentAnalysisGeneralTab.lbl.winddir"))); EditableSpinner spinner = new EditableSpinner(theta.getSpinnerModel()); @@ -200,7 +207,7 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange // Create the Longitudinal Stability (CM vs CP) data table - this.longitudeStabilityTableModel = new ColumnTableModel( + this.longitudeStabilityTableModel = new CAColumnTableModel( //// Component new Column(trans.get("ComponentAnalysisGeneralTab.TabStability.Col.Component")) { @@ -267,15 +274,24 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange public int getRowCount() { return stabData.size(); } + + @Override + public RocketComponent getComponentForRow(int row) { + if (row < 0 || row >= getRowCount()) { + return null; + } + Object source = stabData.get(row).source; + return source instanceof RocketComponent ? (RocketComponent) source : null; + } }; - table = new ColumnTable(longitudeStabilityTableModel); - table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - this.longitudeStabilityTableModel.setColumnWidths(table.getColumnModel()); + final JTable stabilityTable = new ColumnTable(longitudeStabilityTableModel); + stabilityTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + this.longitudeStabilityTableModel.setColumnWidths(stabilityTable.getColumnModel()); - table.setDefaultRenderer(Object.class, new StabilityCellRenderer()); + stabilityTable.setDefaultRenderer(Object.class, new StabilityCellRenderer()); - JScrollPane scrollpane = new JScrollPane(table); + JScrollPane scrollpane = new JScrollPane(stabilityTable); scrollpane.setPreferredSize(new Dimension(600, 200)); //// Stability and Stability information @@ -285,7 +301,7 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange // Create the drag data table - this.dragTableModel = new ColumnTableModel( + this.dragTableModel = new CAColumnTableModel( //// Component new Column(trans.get("ComponentAnalysisGeneralTab.dragTableModel.Col.Component")) { @Override @@ -323,11 +339,18 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange return dragData.get(row).getFrictionCD(); } }, + //// <html>Per instance C<sub>D</sub> + new Column(trans.get("ComponentAnalysisGeneralTab.dragTableModel.Col.perInstance")) { + @Override + public Object getValueAt(int row) { + return dragData.get(row).getCD(); + } + }, //// <html>Total C<sub>D</sub> new Column(trans.get("ComponentAnalysisGeneralTab.dragTableModel.Col.total")) { @Override public Object getValueAt(int row) { - return dragData.get(row).getCD(); + return dragData.get(row).getCDTotal(); } } ) { @@ -340,16 +363,24 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange public int getRowCount() { return dragData.size(); } + + @Override + public RocketComponent getComponentForRow(int row) { + if (row < 0 || row >= getRowCount()) { + return null; + } + return dragData.get(row).getComponent(); + } }; - table = new JTable(dragTableModel); - table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - this.dragTableModel.setColumnWidths(table.getColumnModel()); + final JTable dragTable = new JTable(dragTableModel); + dragTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + this.dragTableModel.setColumnWidths(dragTable.getColumnModel()); - table.setDefaultRenderer(Object.class, new DragCellRenderer()); + dragTable.setDefaultRenderer(Object.class, new DragCellRenderer()); - scrollpane = new JScrollPane(table); + scrollpane = new JScrollPane(dragTable); scrollpane.setPreferredSize(new Dimension(600, 200)); //// Drag characteristics and Drag characteristics tooltip @@ -360,7 +391,7 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange // Create the roll data table - this.rollTableModel = new ColumnTableModel( + this.rollTableModel = new CAColumnTableModel( //// Component new Column(trans.get("ComponentAnalysisGeneralTab.rollTableModel.Col.component")) { @Override @@ -403,14 +434,22 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange public int getRowCount() { return rollData.size(); } + + @Override + public RocketComponent getComponentForRow(int row) { + if (row < 0 || row >= getRowCount()) { + return null; + } + return rollData.get(row).getComponent(); + } }; - table = new JTable(rollTableModel); - table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - table.setDefaultRenderer(Object.class, new RollDynamicsCellRenderer()); + final JTable rollTable = new JTable(rollTableModel); + rollTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + rollTable.setDefaultRenderer(Object.class, new RollDynamicsCellRenderer()); - scrollpane = new JScrollPane(table); + scrollpane = new JScrollPane(rollTable); scrollpane.setPreferredSize(new Dimension(600, 200)); //// Roll dynamics and Roll dynamics tooltip @@ -459,6 +498,11 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange this.roll.addChangeListener(this); this.stateChanged(null); + // Add listeners to highlight the selected component in the rocket panel + stabilityTable.getSelectionModel().addListSelectionListener(new CAListSelectionListener(rocketPanel, stabilityTable)); + dragTable.getSelectionModel().addListSelectionListener(new CAListSelectionListener(rocketPanel, dragTable)); + rollTable.getSelectionModel().addListSelectionListener(new CAListSelectionListener(rocketPanel, rollTable)); + // Remove listeners when closing window parent.addWindowListener(new WindowAdapter() { @Override @@ -635,7 +679,8 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange private final List<?> data; protected final int decimalPlaces; - private static Color backgroundColor; + protected static Color backgroundColor; + protected static Color foregroundColor; static { initColors(); @@ -656,6 +701,7 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange private static void updateColors() { backgroundColor = GUIUtil.getUITheme().getBackgroundColor(); + foregroundColor = GUIUtil.getUITheme().getTextColor(); } @Override @@ -717,9 +763,13 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange private class DragCellRenderer extends CustomCellRenderer { private static final long serialVersionUID = 1L; + private final StripedLabelUI stripedUI; + private final LabelUI defaultUI; public DragCellRenderer() { super(dragData, 3); + this.stripedUI = new StripedLabelUI(1, 12); + this.defaultUI = new BasicLabelUI(); } @Override @@ -727,22 +777,53 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange boolean isSelected, boolean hasFocus, int row, int column) { JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (!isSelected && (value instanceof Double)) { - double cd = (Double) value; - float r = (float) (cd / 1.5); + // Reset to default UI + label.setUI(defaultUI); - float hue = MathUtil.clamp(0.3333f * (1 - 2.0f * r), 0, 0.3333f); - float sat = MathUtil.clamp(0.8f * r + 0.1f * (1 - r), 0, 1); - float val = 1.0f; + // Handle selection coloring + if (isSelected) { + label.setBackground(table.getSelectionBackground()); + label.setForeground(table.getSelectionForeground()); + label.setOpaque(true); + } else { + // Non-selected styling + if (value instanceof Double) { + double cd = (Double) value; + float r = (float) (cd / 1.5); - label.setBackground(Color.getHSBColor(hue, sat, val)); - label.setForeground(Color.BLACK); + float hue = MathUtil.clamp(0.3333f * (1 - 2.0f * r), 0, 0.3333f); + float sat = MathUtil.clamp(0.8f * r + 0.1f * (1 - r), 0, 1); + float val = 1.0f; + + label.setBackground(Color.getHSBColor(hue, sat, val)); + label.setForeground(Color.BLACK); + } else { + label.setBackground(table.getBackground()); + label.setForeground(table.getForeground()); + } + label.setOpaque(true); } - if ((row < 0) || (row >= dragData.size())) - return label; + // Special handling for column 4 + if (column == 4) { + if (row == 0) { + label.setText(""); + label.setUI(stripedUI); + if (!isSelected) { + label.setBackground(table.getBackground()); + label.setForeground(table.getForeground()); + } + } else { + label.setText(decimalFormat(dragData.get(row).getCD())); + } + } - if ((dragData.get(row).getComponent() instanceof Rocket) || (column == 4)) { + // Set font + if ((row < 0) || (row >= dragData.size())) { + return label; + } + + if ((dragData.get(row).getComponent() instanceof Rocket) || (column == 5)) { label.setFont(boldFont); } else { label.setFont(normalFont); @@ -755,10 +836,46 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange protected String formatDouble(Double cd) { final double totalCD = dragData.get(0).getCD(); - DecimalFormat df = new DecimalFormat("0." + "#".repeat(Math.max(0, decimalPlaces))); - String cdFormatted = df.format(cd); + String cdFormatted = decimalFormat(cd); return String.format(cdFormatted + " (%.0f%%)", 100 * cd / totalCD); } + + private String decimalFormat(Double cd) { + DecimalFormat df = new DecimalFormat("0." + "#".repeat(Math.max(0, decimalPlaces))); + return df.format(cd); + } + + private static class StripedLabelUI extends BasicLabelUI { + private final int lineWidth; + private final int lineSpacing; + + public StripedLabelUI(int lineWidth, int lineSpacing) { + this.lineWidth = lineWidth; + this.lineSpacing = lineSpacing; + } + + @Override + public void paint(Graphics g, JComponent c) { + Graphics2D g2d = (Graphics2D) g.create(); + int width = c.getWidth(); + int height = c.getHeight(); + + // Paint background + g2d.setColor(c.getBackground()); + g2d.fillRect(0, 0, width, height); + + // Paint thin lines + g2d.setColor(c.getForeground()); + g2d.setStroke(new BasicStroke(lineWidth)); + for (int x = -height; x < width; x += (lineWidth + lineSpacing)) { + g2d.drawLine(x, height, x + height, 0); + } + + g2d.dispose(); + + super.paint(g, c); + } + } } private class RollDynamicsCellRenderer extends CustomCellRenderer { @@ -767,8 +884,7 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange } } - private class LongitudinalStabilityRow { - + private static class LongitudinalStabilityRow { public String name; public Object source; public double eachMass; @@ -784,6 +900,34 @@ public class ComponentAnalysisGeneralPanel extends JPanel implements StateChange cpx = Double.NaN; cna = Double.NaN; } + } + private abstract static class CAColumnTableModel extends ColumnTableModel { + public CAColumnTableModel(Column... columns) { + super(columns); + } + + public RocketComponent getComponentForRow(int row) { + throw new RuntimeException("Not implemented"); + } + } + + private static class CAListSelectionListener implements ListSelectionListener { + private final RocketPanel rocketPanel; + private final JTable table; + + public CAListSelectionListener(RocketPanel rocketPanel, JTable table) { + this.rocketPanel = rocketPanel; + this.table = table; + } + + @Override + public void valueChanged(ListSelectionEvent e) { + TableModel model = table.getModel(); + if (model instanceof CAColumnTableModel) { + RocketComponent component = ((CAColumnTableModel) model).getComponentForRow(table.getSelectedRow()); + rocketPanel.setSelectedComponent(component); + } + } } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java index 92a2f49db..cde27850a 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java @@ -541,6 +541,15 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change valueChanged((TreeSelectionEvent) null); // updates FigureParameters } + public void setSelectedComponent(RocketComponent component) { + if (component == null) { + selectionModel.setSelectionPath(null); + return; + } + TreePath path = ComponentTreeModel.makeTreePath(component); + selectionModel.setSelectionPath(path); + } + /** * Return the angle of attack used in CP calculation. NaN signifies the default value * of zero. @@ -672,8 +681,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change if (newClick) { for (RocketComponent rocketComponent : clicked) { if (!selectedComponents.contains(rocketComponent)) { - TreePath path = ComponentTreeModel.makeTreePath(rocketComponent); - selectionModel.setSelectionPath(path); + setSelectedComponent(rocketComponent); } } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/widgets/GroupableAndSearchableComboBox.java b/swing/src/main/java/info/openrocket/swing/gui/widgets/GroupableAndSearchableComboBox.java index 66c74cc3f..59a5a66b0 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/widgets/GroupableAndSearchableComboBox.java +++ b/swing/src/main/java/info/openrocket/swing/gui/widgets/GroupableAndSearchableComboBox.java @@ -130,20 +130,34 @@ public class GroupableAndSearchableComboBox<G extends Group, T extends Groupable } private Map<G, List<T>> constructItemGroupMapFromList(List<T> items) { - Map<G, List<T>> itemGroupMap = new TreeMap<>(new Comparator<G>() { - @Override - public int compare(G g1, G g2) { - return Integer.compare(g1.getPriority(), g2.getPriority()); - } - }); + Map<G, List<T>> itemGroupMap = new TreeMap<>(Comparator.comparing(Group::getPriority)); for (T item : items) { G group = item.getGroup(); itemGroupMap.computeIfAbsent(group, k -> new ArrayList<>()).add(item); } + + // Sort items within each group + for (List<T> groupItems : itemGroupMap.values()) { + groupItems.sort(new ItemComparator()); + } + return itemGroupMap; } + private class ItemComparator implements Comparator<T> { + @Override + @SuppressWarnings("unchecked") + public int compare(T item1, T item2) { + if (item1 instanceof Comparable && item2 instanceof Comparable) { + return ((Comparable<T>) item1).compareTo(item2); + } else { + // Fall back to alphabetical sorting using the display string + return getDisplayString(item1).compareToIgnoreCase(getDisplayString(item2)); + } + } + } + public void setupMainRenderer() { setRenderer(new DefaultListCellRenderer() { @Override