diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index f42b3c968..65080c7f0 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -614,6 +614,7 @@ simpanel.dlg.lbl.DeleteSim1 = Delete the selected simulations?
simpanel.dlg.lbl.DeleteSim2 = This operation cannot be undone.
simpanel.dlg.lbl.DeleteSim3 = Delete simulations
simpanel.col.Status = Status
+simpanel.col.Warnings = Warnings
simpanel.col.Name = Name
simpanel.col.Motors = Motors
simpanel.col.Configuration = Configuration
diff --git a/core/resources/pix/icons/warning_high.png b/core/resources/pix/icons/warning_high.png
new file mode 100644
index 000000000..c37bd062e
Binary files /dev/null and b/core/resources/pix/icons/warning_high.png differ
diff --git a/core/resources/pix/icons/warning_low.png b/core/resources/pix/icons/warning_low.png
new file mode 100644
index 000000000..12cd1aef9
Binary files /dev/null and b/core/resources/pix/icons/warning_low.png differ
diff --git a/core/resources/pix/icons/warning_normal.png b/core/resources/pix/icons/warning_normal.png
new file mode 100644
index 000000000..628cf2dae
Binary files /dev/null and b/core/resources/pix/icons/warning_normal.png differ
diff --git a/core/src/net/sf/openrocket/logging/MessageSet.java b/core/src/net/sf/openrocket/logging/MessageSet.java
index adf490edb..3e7ace376 100644
--- a/core/src/net/sf/openrocket/logging/MessageSet.java
+++ b/core/src/net/sf/openrocket/logging/MessageSet.java
@@ -116,6 +116,20 @@ public abstract class MessageSet extends AbstractSet imple
return messages.size();
}
+ /**
+ * Returns the number of messages with the specified priority.
+ * @param priority the priority
+ * @return the number of messages with the specified priority.
+ */
+ public int getNrOfMessagesWithPriority(MessagePriority priority) {
+ int count = 0;
+ for (E m : messages) {
+ if (m.getPriority() == priority) {
+ count++;
+ }
+ }
+ return count;
+ }
public void immute() {
mutable.immute();
diff --git a/core/src/net/sf/openrocket/logging/WarningSet.java b/core/src/net/sf/openrocket/logging/WarningSet.java
index d93a20d0e..e4688f1c0 100644
--- a/core/src/net/sf/openrocket/logging/WarningSet.java
+++ b/core/src/net/sf/openrocket/logging/WarningSet.java
@@ -27,6 +27,18 @@ public class WarningSet extends MessageSet {
return add(Warning.fromString(s));
}
+ public int getNrOfCriticalWarnings() {
+ return getNrOfMessagesWithPriority(MessagePriority.HIGH);
+ }
+
+ public int getNrOfNormalWarnings() {
+ return getNrOfMessagesWithPriority(MessagePriority.NORMAL);
+ }
+
+ public int getNrOfInformativeWarnings() {
+ return getNrOfMessagesWithPriority(MessagePriority.LOW);
+ }
+
@Override
public WarningSet clone() {
try {
diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
index b5e248cba..682b4d4f5 100644
--- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
+++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
@@ -1,7 +1,7 @@
package net.sf.openrocket.gui.main;
-import java.awt.Color;
+import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
@@ -21,13 +21,14 @@ import java.io.Serial;
import java.util.Comparator;
import javax.swing.AbstractAction;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
-import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
@@ -94,13 +95,6 @@ public class SimulationPanel extends JPanel {
private static final Translator trans = Application.getTranslator();
- private static final Color WARNING_COLOR = Color.RED;
- private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark
-
- private static final Color OK_COLOR = new Color(60, 150, 0);
- private static final String OK_TEXT = "\u2714"; // Heavy check mark
-
-
private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class);
@@ -195,6 +189,7 @@ public class SimulationPanel extends JPanel {
simulationTable.setRowSorter(simulationTableSorter);
simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
+ simulationTable.setDefaultRenderer(WarningsBox.class, new WarningsBoxRenderer());
simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
simulationTable.setFillsViewportHeight(true);
@@ -239,6 +234,7 @@ public class SimulationPanel extends JPanel {
if (e.getButton() == MouseEvent.BUTTON1) {
if (e.getClickCount() == 1) {
+ // Rerun the simulation
if (column == 0) {
int selected = simulationTable.convertRowIndexToModel(selectedRow);
Simulation sim = document.getSimulations().get(selected);
@@ -248,14 +244,15 @@ public class SimulationPanel extends JPanel {
runSimulation();
}
}
- } else if (e.getClickCount() == 2) {
- int selected = simulationTable.convertRowIndexToModel(selectedRow);
// Show the warnings for the simulation
- if (column == 0) {
+ else if (column == 1) {
+ int selected = simulationTable.convertRowIndexToModel(selectedRow);
SimulationWarningDialog.showWarningDialog(SimulationPanel.this, document.getSimulations().get(selected));
}
+ } else if (e.getClickCount() == 2) {
+ int selected = simulationTable.convertRowIndexToModel(selectedRow);
// Edit the simulation or plot/export
- else {
+ if (column > 1) {
simulationTable.clearSelection();
simulationTable.addRowSelectionInterval(selectedRow, selectedRow);
@@ -756,67 +753,83 @@ public class SimulationPanel extends JPanel {
return simulationTable.getSelectionModel();
}
- private String getSimulationToolTip(Simulation sim, boolean includeSimName) {
- String tip;
+ private static String getSimulationStatusToolTip(Simulation sim, boolean includeSimName) {
+ StringBuilder tip;
FlightData data = sim.getSimulatedData();
- tip = "";
+ tip = new StringBuilder("");
if (includeSimName) {
- tip += "" + sim.getName() + "
";
+ tip.append("").append(sim.getName()).append("
");
}
switch (sim.getStatus()) {
case CANT_RUN:
- tip += trans.get("simpanel.ttip.noData")+"
";
+ tip.append(trans.get("simpanel.ttip.noData")).append("
");
break;
case LOADED:
- tip += trans.get("simpanel.ttip.loaded") + "
";
+ tip.append(trans.get("simpanel.ttip.loaded")).append("
");
break;
case UPTODATE:
- tip += trans.get("simpanel.ttip.uptodate") + "
";
+ tip.append(trans.get("simpanel.ttip.uptodate")).append("
");
break;
case OUTDATED:
- tip += trans.get("simpanel.ttip.outdated") + "
";
+ tip.append(trans.get("simpanel.ttip.outdated")).append("
");
break;
case EXTERNAL:
- tip += trans.get("simpanel.ttip.external") + "
";
- return tip;
+ tip.append(trans.get("simpanel.ttip.external")).append("
");
+ return tip.toString();
case NOT_SIMULATED:
- tip += trans.get("simpanel.ttip.notSimulated");
- return tip;
+ tip.append(trans.get("simpanel.ttip.notSimulated"));
+ return tip.toString();
}
if (data == null) {
- tip += trans.get("simpanel.ttip.noData");
- return tip;
+ tip.append(trans.get("simpanel.ttip.noData"));
+ return tip.toString();
}
for (int b = 0; b < data.getBranchCount(); b++) {
FlightEvent abortEvent = data.getBranch(b).getFirstEvent(FlightEvent.Type.SIM_ABORT);
- if ( abortEvent != null) {
- tip += "" + trans.get("simpanel.ttip.simAbort") + ": " +
- ((SimulationAbort)(abortEvent.getData())).toString() + "
";
+ if (abortEvent != null) {
+ tip.append("").append(trans.get("simpanel.ttip.simAbort")).append(": ").append((abortEvent.getData()).toString()).append("
");
}
}
-
- WarningSet warnings = data.getWarningSet();
- if (warnings.isEmpty()) {
- tip += trans.get("simpanel.ttip.noWarnings");
- return tip;
- }
- tip += trans.get("simpanel.ttip.warnings");
- for (Warning w : warnings) {
- tip += "
" + w.toString();
- }
-
- return tip;
+ return tip.toString();
}
- private String getSimulationToolTip(Simulation sim) {
- return getSimulationToolTip(sim, true);
+ private static String getSimulationWarningsToolTip(Simulation sim, boolean includeSimName) {
+ StringBuilder tip;
+ FlightData data = sim.getSimulatedData();
+
+ tip = new StringBuilder("");
+ if (includeSimName) {
+ tip.append("").append(sim.getName()).append("
");
+ }
+
+ if (data == null) {
+ tip.append(trans.get("simpanel.ttip.noData"));
+ return tip.toString();
+ }
+
+ WarningSet warnings = data.getWarningSet();
+ if (warnings.isEmpty()) {
+ tip.append(trans.get("simpanel.ttip.noWarnings"));
+ return tip.toString();
+ }
+
+ tip.append(trans.get("simpanel.ttip.warnings"));
+ for (Warning w : warnings) {
+ tip.append("
").append(w.toString());
+ }
+
+ return tip.toString();
+ }
+
+ private String getSimulationStatusToolTip(Simulation sim) {
+ return getSimulationStatusToolTip(sim, true);
}
private void openDialog(boolean plotMode, boolean isNewSimulation, final Simulation... sims) {
@@ -1138,7 +1151,7 @@ public class SimulationPanel extends JPanel {
label.setBackground(table.getBackground());
label.setOpaque(true);
- label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
+ label.setToolTipText(getSimulationStatusToolTip(document.getSimulation(row)));
return label;
}
@@ -1146,7 +1159,7 @@ public class SimulationPanel extends JPanel {
isSelected, hasFocus, row, column);
if (component instanceof JComponent) {
- ((JComponent) component).setToolTipText(getSimulationToolTip(
+ ((JComponent) component).setToolTipText(getSimulationStatusToolTip(
document.getSimulation(row)));
}
return component;
@@ -1167,17 +1180,97 @@ public class SimulationPanel extends JPanel {
@Override
public String toString() {
- String text = getSimulationToolTip(simulation, false);
+ String text = getSimulationStatusToolTip(simulation, false);
return text.replace("
", "-").replaceAll("<[^>]*>","");
}
}
+ private static class WarningsBox extends Box {
+ private Simulation simulation;
+
+ public WarningsBox(Simulation simulation) {
+ super(BoxLayout.X_AXIS); // Horizontal box
+ this.simulation = simulation;
+ updateContent();
+ }
+
+ public void replaceSimulation(Simulation simulation) {
+ this.simulation = simulation;
+ updateContent();
+ }
+
+ private void updateContent() {
+ removeAll(); // Clear existing content before update
+ setToolTipText("");
+
+ if (simulation == null) {
+ revalidate();
+ return;
+ }
+
+ // Update the tooltip text
+ String ttip = getSimulationWarningsToolTip(simulation, true);
+ setToolTipText(ttip);
+
+ WarningSet warnings = simulation.getSimulatedWarnings();
+
+ if (warnings == null || warnings.isEmpty()) {
+ revalidate();
+ return;
+ }
+
+ int nrOfCriticalWarnings = warnings.getNrOfCriticalWarnings();
+ int nrOfNormalWarnings = warnings.getNrOfNormalWarnings();
+ int nrOfInfoWarnings = warnings.getNrOfInformativeWarnings();
+
+ if (nrOfCriticalWarnings > 0) {
+ add(new JLabel(nrOfCriticalWarnings + " "));
+ add(new JLabel(Icons.WARNING_HIGH));
+ }
+
+ if (nrOfCriticalWarnings > 0 && nrOfNormalWarnings > 0) {
+ add(new JLabel(", "));
+ }
+
+ if (nrOfNormalWarnings > 0) {
+ add(new JLabel(nrOfNormalWarnings + " "));
+ add(new JLabel(Icons.WARNING_NORMAL));
+ }
+
+ if ((nrOfCriticalWarnings > 0 || nrOfNormalWarnings > 0) && nrOfInfoWarnings > 0) {
+ add(new JLabel(", "));
+ }
+
+ if (nrOfInfoWarnings > 0) {
+ add(new JLabel(nrOfInfoWarnings + " "));
+ add(new JLabel(Icons.WARNING_LOW));
+ }
+
+ revalidate(); // Notify layout manager of changes
+ }
+ }
+
+ public static class WarningsBoxRenderer extends DefaultTableCellRenderer {
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+ if (value instanceof WarningsBox box) {
+ // Wrap the box in a panel with BorderLayout to allow alignment
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setToolTipText(box.getToolTipText());
+ panel.add(box, BorderLayout.EAST); // Align to the right within the panel
+
+ return panel;
+ }
+ return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ }
+ }
+
private class SimulationTableModel extends ColumnTableModel {
private static final long serialVersionUID = 8686456963492628476L;
public SimulationTableModel() {
super(
- //// Status and warning column
+ //// Status column
new Column("") {
private StatusLabel label = null;
@@ -1201,31 +1294,12 @@ public class SimulationPanel extends JPanel {
Simulation.Status status = simulation.getStatus();
label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
-
- // Set warning marker
- if (status == Simulation.Status.NOT_SIMULATED ||
- status == Simulation.Status.EXTERNAL) {
- label.setText("");
- } else {
-
- WarningSet w = document.getSimulation(row).getSimulatedWarnings();
- if (w == null) {
- label.setText("");
- } else if (w.isEmpty()) {
- label.setForeground(OK_COLOR);
- label.setText(OK_TEXT);
- } else {
- label.setForeground(WARNING_COLOR);
- label.setText(WARNING_TEXT);
- }
- }
-
return label;
}
@Override
public int getExactWidth() {
- return 36;
+ return 26;
}
@Override
@@ -1234,6 +1308,38 @@ public class SimulationPanel extends JPanel {
}
},
+ //// Warnings column
+ new Column(trans.get("simpanel.col.Warnings")) {
+ private WarningsBox box = null;
+
+ @Override
+ public Object getValueAt(int row) {
+ if (row < 0 || row >= document.getSimulationCount())
+ return null;
+
+ Simulation simulation = document.getSimulation(row);
+
+ // Initialize the box
+ if (box == null) {
+ box = new WarningsBox(simulation);
+ } else {
+ box.replaceSimulation(simulation);
+ }
+
+ return box;
+ }
+
+ @Override
+ public int getDefaultWidth() {
+ return 70;
+ }
+
+ @Override
+ public Class> getColumnClass() {
+ return WarningsBox.class;
+ }
+ },
+
//// Simulation name
//// Name
new Column(trans.get("simpanel.col.Name")) {
diff --git a/swing/src/net/sf/openrocket/gui/util/Icons.java b/swing/src/net/sf/openrocket/gui/util/Icons.java
index cdc7c997e..afc53f907 100644
--- a/swing/src/net/sf/openrocket/gui/util/Icons.java
+++ b/swing/src/net/sf/openrocket/gui/util/Icons.java
@@ -99,6 +99,10 @@ public class Icons {
public static final Icon NOT_FAVORITE = loadImageIcon("pix/icons/star_silver.png", "Not favorite");
public static final Icon FAVORITE = loadImageIcon("pix/icons/star_gold.png", "Favorite");
+ public static final Icon WARNING_LOW = loadImageIcon("pix/icons/warning_low.png", "Informative Warning");
+ public static final Icon WARNING_NORMAL = loadImageIcon("pix/icons/warning_normal.png", "Warning");
+ public static final Icon WARNING_HIGH = loadImageIcon("pix/icons/warning_high.png", "Critical Warning");
+
public static final Icon MASS_OVERRIDE_LIGHT = loadImageIcon("pix/icons/mass-override_light.png", "Mass Override");
public static final Icon MASS_OVERRIDE_DARK = loadImageIcon("pix/icons/mass-override_dark.png", "Mass Override");
public static final Icon MASS_OVERRIDE_SUBCOMPONENT_LIGHT = loadImageIcon("pix/icons/mass-override-subcomponent_light.png", "Mass Override Subcomponent");
@@ -143,17 +147,6 @@ public class Icons {
return new ImageIcon(url, name);
}
- /**
- * Loads an ImageIcon with a new name.
- *
- * @param icon the original ImageIcon to load.
- * @param newName the new name for the ImageIcon.
- * @return the loaded ImageIcon with the new name.
- */
- public static ImageIcon loadImageIconWithNewName(ImageIcon icon, String newName) {
- return new ImageIcon(icon.getImage(), newName);
- }
-
/**
* Scales an ImageIcon to the specified scale.
* @param icon icon to scale