diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index fd8bbc143..10daf4f2e 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -406,7 +406,8 @@ PreferencesOptionPanel.checkbox.windowInfo = Export window information (position
PreferencesOptionPanel.checkbox.windowInfo.ttip = If unchecked, window information (position, size\u2026) will not be exported.
! UI Themes
-UITheme.Light = Light (Default)
+UITheme.Auto = Auto (detect)
+UITheme.Light = Light (default)
UITheme.Dark = Dark
! Welcome dialog
diff --git a/swing/.classpath b/swing/.classpath
index 5aa1c307e..763ac63b0 100644
--- a/swing/.classpath
+++ b/swing/.classpath
@@ -11,6 +11,13 @@
+
+
+
+
+
+
+
diff --git a/swing/OpenRocket Swing.iml b/swing/OpenRocket Swing.iml
index 55f1c84b1..fd79a03a1 100644
--- a/swing/OpenRocket Swing.iml
+++ b/swing/OpenRocket Swing.iml
@@ -5,6 +5,13 @@
+ $
+
+
+
+
+
+
@@ -109,9 +116,63 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/swing/build.xml b/swing/build.xml
index 80ef81afe..af8a1d3f5 100644
--- a/swing/build.xml
+++ b/swing/build.xml
@@ -116,6 +116,13 @@
+
+
+
+
+
+
+
diff --git a/swing/lib/annotations-24.0.1.jar b/swing/lib/annotations-24.0.1.jar
new file mode 100644
index 000000000..a4198f711
Binary files /dev/null and b/swing/lib/annotations-24.0.1.jar differ
diff --git a/swing/lib/jSystemThemeDetector-3.8.jar b/swing/lib/jSystemThemeDetector-3.8.jar
new file mode 100644
index 000000000..a8ac11f54
Binary files /dev/null and b/swing/lib/jSystemThemeDetector-3.8.jar differ
diff --git a/swing/lib/jfa-1.2.0.jar b/swing/lib/jfa-1.2.0.jar
new file mode 100644
index 000000000..2efd423eb
Binary files /dev/null and b/swing/lib/jfa-1.2.0.jar differ
diff --git a/swing/lib/jna-5.13.0.jar b/swing/lib/jna-5.13.0.jar
new file mode 100644
index 000000000..3d49c8188
Binary files /dev/null and b/swing/lib/jna-5.13.0.jar differ
diff --git a/swing/lib/jna-platform-5.13.0.jar b/swing/lib/jna-platform-5.13.0.jar
new file mode 100644
index 000000000..816a567cc
Binary files /dev/null and b/swing/lib/jna-platform-5.13.0.jar differ
diff --git a/swing/lib/oshi-core-6.4.4.jar b/swing/lib/oshi-core-6.4.4.jar
new file mode 100644
index 000000000..2e8b714f9
Binary files /dev/null and b/swing/lib/oshi-core-6.4.4.jar differ
diff --git a/swing/lib/versioncompare-1.4.1.jar b/swing/lib/versioncompare-1.4.1.jar
new file mode 100644
index 000000000..14ab48bd6
Binary files /dev/null and b/swing/lib/versioncompare-1.4.1.jar differ
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java
index 711dc0f1d..404b9b0fc 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java
@@ -75,9 +75,10 @@ public class AboutDialog extends JDialog {
"Simple Logging Facade for Java" + href("http://www.slf4j.org", true, true) + "
" +
"Java library for parsing and rendering CommonMark" + href("https://github.com/commonmark/commonmark-java", true, true) + "
" +
"RSyntaxTextArea" + href("http://bobbylight.github.io/RSyntaxTextArea", true, true) + "
" +
- "Darklaf (dark theme)" + href("https://github.com/weisJ/darklaf", true, true) + "
" +
+ "Darklaf (dark theme)" + href("https://github.com/weisJ/darklaf", true, true) + "
" +
+ "jSystemThemeDetector" + href("https://github.com/Dansoftowner/jSystemThemeDetector", true, true) + "
" +
"Obj" + href("https://github.com/javagl/Obj", true, true) + "
" +
- "
" +
+ "
" +
"OpenRocket gratefully acknowledges our use of the following databases:
" +
"
" +
"Rocket Motor Data" + href("https://www.thrustcurve.org", true, true) + "
" +
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java
index 49674d686..4e461b983 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java
@@ -1,5 +1,6 @@
package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
+import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
@@ -16,6 +17,7 @@ import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
+import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
@@ -144,26 +146,25 @@ public abstract class MotorFilterPanel extends JPanel {
// Manufacturer selection
JPanel sub = new JPanel(new MigLayout("fill"));
- TitledBorder border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.MANUFACTURER"));
+ Border templateBorder = GUIUtil.getUITheme().getBorder();
+ TitledBorder border = BorderFactory.createTitledBorder(templateBorder);
+ border.setTitle(trans.get("TCurveMotorCol.MANUFACTURER"));
GUIUtil.changeFontStyle(border, Font.BOLD);
sub.setBorder(border);
this.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
- List manufacturers = new ArrayList();
- for (Manufacturer m : allManufacturers) {
- manufacturers.add(m);
- }
+ List manufacturers = new ArrayList<>(allManufacturers);
- Collections.sort(manufacturers, new Comparator() {
- @Override
- public int compare(Manufacturer o1, Manufacturer o2) {
- return o1.getSimpleName().compareTo( o2.getSimpleName());
- }
+ manufacturers.sort(new Comparator() {
+ @Override
+ public int compare(Manufacturer o1, Manufacturer o2) {
+ return o1.getSimpleName().compareTo(o2.getSimpleName());
+ }
- });
+ });
- manufacturerCheckList = new CheckList.Builder().build();
+ manufacturerCheckList = new CheckList.Builder().build();
manufacturerCheckList.setData(manufacturers);
manufacturerCheckList.setUncheckedItems(unselectedManusFromPreferences);
@@ -184,7 +185,12 @@ public abstract class MotorFilterPanel extends JPanel {
}
});
- sub.add(new JScrollPane(manufacturerCheckList.getList()), "grow, pushy, wrap");
+ JScrollPane scrollPane = new JScrollPane(manufacturerCheckList.getList());
+ Border border1 = GUIUtil.getUITheme().getBorder();
+ if (border1 != null) {
+ scrollPane.setBorder(border1);
+ }
+ sub.add(scrollPane, "grow, pushy, wrap");
JButton clearMotors = new SelectColorButton(trans.get("TCMotorSelPan.btn.checkNone"));
clearMotors.addActionListener( new ActionListener() {
@@ -213,7 +219,8 @@ public abstract class MotorFilterPanel extends JPanel {
// Total Impulse selection
{
sub = new JPanel(new MigLayout("fill"));
- border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.TOTAL_IMPULSE"));
+ border = BorderFactory.createTitledBorder(templateBorder);
+ border.setTitle(trans.get("TCurveMotorCol.TOTAL_IMPULSE"));
GUIUtil.changeFontStyle(border, Font.BOLD);
sub.setBorder(border);
@@ -240,7 +247,8 @@ public abstract class MotorFilterPanel extends JPanel {
// Motor Dimensions
sub = new JPanel(new MigLayout("fill"));
- TitledBorder diameterTitleBorder = BorderFactory.createTitledBorder(trans.get("TCMotorSelPan.MotorSize"));
+ TitledBorder diameterTitleBorder = BorderFactory.createTitledBorder(templateBorder);
+ diameterTitleBorder.setTitle(trans.get("TCMotorSelPan.MotorSize"));
GUIUtil.changeFontStyle(diameterTitleBorder, Font.BOLD);
sub.setBorder(diameterTitleBorder);
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java
index de63acf0e..741b61ecd 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java
@@ -159,6 +159,7 @@ class MotorInformationPanel extends JPanel {
comment = new JTextArea(5, 5);
+ comment.setBorder(GUIUtil.getUITheme().getBorder());
GUIUtil.changeFontSize(comment, -2);
withCommentFont = comment.getFont();
noCommentFont = withCommentFont.deriveFont(Font.ITALIC);
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java
index eebbbe927..2cc104581 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java
@@ -45,6 +45,7 @@ import javax.swing.event.RowSorterListener;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
+import net.sf.openrocket.gui.util.UITheme;
import net.sf.openrocket.util.StateChangeListener;
import org.jfree.chart.ChartColor;
import org.slf4j.Logger;
@@ -332,11 +333,10 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
// Number of motors
{
- nrOfMotorsLabel = new JLabel();
+ nrOfMotorsLabel = new StyledLabel(-2f, StyledLabel.Style.ITALIC);
nrOfMotorsLabel.setToolTipText(trans.get("TCMotorSelPan.lbl.ttip.nrOfMotors"));
updateNrOfMotors();
- nrOfMotorsLabel.setForeground(Color.darkGray);
- nrOfMotorsLabel.setFont(new Font(Font.SANS_SERIF, Font.ITALIC, 11));
+ nrOfMotorsLabel.setForeground(GUIUtil.getUITheme().getDimTextColor());
panel.add(nrOfMotorsLabel, "gapleft para, spanx, wrap");
sorter.addRowSorterListener(new RowSorterListener() {
@Override
diff --git a/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java
index 41cac74ba..9396c1ec9 100644
--- a/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java
+++ b/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java
@@ -21,7 +21,6 @@ import net.sf.openrocket.util.Color;
import com.jogamp.opengl.util.texture.Texture;
public class RealisticRenderer extends RocketRenderer {
- private final float[] colorClear = { 0, 0, 0, 0 };
private final float[] colorWhite = { 1, 1, 1, 1 };
private final float[] color = new float[4];
@@ -165,7 +164,7 @@ public class RealisticRenderer extends RocketRenderer {
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, toEdgeMode(t.getEdgeMode()));
- gl.glTexParameterfv(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_BORDER_COLOR, colorClear, 0);
+ gl.glTexParameterfv(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_BORDER_COLOR, convertedColor, 0);
gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, colorWhite, 0);
gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, colorWhite, 0);
diff --git a/swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java b/swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java
index 342946b6f..f9fa90151 100644
--- a/swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java
+++ b/swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java
@@ -6,6 +6,8 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
@@ -28,6 +30,7 @@ import net.sf.openrocket.rocketcomponent.RailButton;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.MathUtil;
/**
* This is the core Swing representation of a fin marking guide. It can handle multiple fin and/or tube fin sets
@@ -239,13 +242,16 @@ public class FinMarkingGuide extends JPanel {
private void paintFinMarkingGuide(Graphics2D g2) {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
-
- g2.setColor(Color.BLACK);
+
+ final Color lineColor = Color.BLACK;
+
+ g2.setColor(lineColor);
g2.setStroke(thinStroke);
int x = MARGIN;
int y = MARGIN;
int width = (int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH);
+ int length;
int column = 0;
@@ -255,8 +261,8 @@ public class FinMarkingGuide extends JPanel {
List componentList = markingGuideItems.get(next);
//Don't draw the lug if there are no fins.
if (hasFins(componentList)) {
-
- drawMarkingGuide(g2, x, y, (int) Math.ceil(circumferenceInPoints), width);
+ length = (int) Math.ceil(circumferenceInPoints);
+ drawMarkingGuide(g2, x, y, length, width);
double radialOrigin = findRadialOrigin(componentList);
@@ -284,13 +290,121 @@ public class FinMarkingGuide extends JPanel {
while (angle > TWO_PI) {
angle -= TWO_PI;
}
-
- int offset = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints);
-
- drawDoubleArrowLine(g2, x, offset, x + width, offset);
- // if (hasMultipleComponents) {
- g2.drawString(externalComponent.getName(), x + (width / 3), offset - 2);
- // }
+
+ final int yFinCenter = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints);
+ final int yStart;
+ final int yEnd;
+
+ // Account for canted fins
+ /*
+ The arrow will be rotated around the aft base end of the fin.
+ This is because the aft end will most likely be at the aft end of the body tube.
+ If we were to rotate around the fore end, there's a good chance that the marking guide
+ extends beyond the body tube aft end and thus you cannot draw the arrow.
+ */
+ final double cantAngle = fins.getCantAngle();
+ final boolean isCanted = !MathUtil.equals(cantAngle, 0);
+ if (isCanted) {
+ // We want to end the arrow at the aft end of the fin, so we need add an offset to
+ // the end to account for the y-shift of the aft end of the fin due to the cant.
+ final double finBaseHalfWidth = PrintUnit.METERS.toPoints(fins.getLength()) / 2;
+ final int yFinForeEndOffset = - (int) Math.round(finBaseHalfWidth * Math.sin(cantAngle));
+ yStart = yFinCenter + yFinForeEndOffset;
+
+ // Calculate y offset of end point
+ int yOffset = (int) Math.round(width * Math.tan(cantAngle));
+ yEnd = yStart + yOffset;
+ } else {
+ yStart = yFinCenter;
+ yEnd = yFinCenter;
+ }
+
+ // Draw double arrow
+ drawDoubleArrowLine(g2, x, yStart, x + width, yEnd, cantAngle);
+
+ // Draw horizontal dotted line where fin aft end is, vertical dotted line where the fore end is
+ // and cross at the fin center
+ if (isCanted) {
+ //// -- Aft end dashed line
+ // Dashed stroke settings
+ float originalLineWidth = thinStroke.getLineWidth();
+ float[] dashPattern = {10, 10}; // 10 pixel dash, 10 pixel space
+ Stroke dashedStroke = new BasicStroke(
+ originalLineWidth,
+ thinStroke.getEndCap(),
+ thinStroke.getLineJoin(),
+ thinStroke.getMiterLimit(),
+ dashPattern,
+ 0
+ );
+
+ // Set color and stroke
+ g2.setColor(new Color(200, 200, 200));
+ g2.setStroke(dashedStroke);
+
+ // Draw aft end horizontal dashed line
+ // We draw from right to left to ensure that the side where the side does not touch
+ // with an arrow point (fore end) has the dashed line touching the marking guide edge
+ // (is useful for marking the fin position)
+ g2.drawLine(x + width, yStart, x, yStart);
+
+ //// -- Fore end dashed line
+ // Dashed stroke settings
+ dashPattern = new float[] {4, 6}; // 4 pixel dash, 6 pixel space
+ dashedStroke = new BasicStroke(
+ originalLineWidth * 0.7f,
+ thinStroke.getEndCap(),
+ thinStroke.getLineJoin(),
+ thinStroke.getMiterLimit(),
+ dashPattern,
+ 0
+ );
+
+ // Set color and stroke
+ g2.setColor(new Color(220, 220, 220));
+ g2.setStroke(dashedStroke);
+
+ // Draw fore end vertical dashed line
+ final int finBaseWidth = (int) PrintUnit.METERS.toPoints(fins.getLength());
+ if (finBaseWidth < width) {
+ g2.drawLine(x + finBaseWidth, y, x + finBaseWidth, y + length);
+ }
+
+ // Reset stroke
+ g2.setStroke(thinStroke);
+
+ //// -- Cross
+ final double finBaseHalfWidth = PrintUnit.METERS.toPoints(fins.getLength()) / 2;
+
+ // The cant also has an x-shift. We want the aft end to be perfectly flush with the
+ // left of the marking guide, so apply an x-shift to fin center position
+ int xFinCenter = x + (int) Math.round(finBaseHalfWidth);
+ int xFinCenterOffset = - (int) Math.round(finBaseHalfWidth * (1 - Math.cos(cantAngle)));
+ xFinCenter += xFinCenterOffset;
+
+ // Draw a cross where the center of the fin should be
+ int crossSize = 3;
+ g2.drawLine(xFinCenter-crossSize, yFinCenter-crossSize, xFinCenter+crossSize, yFinCenter+crossSize);
+ g2.drawLine(xFinCenter-crossSize, yFinCenter+crossSize, xFinCenter+crossSize, yFinCenter-crossSize);
+
+ // Reset color
+ g2.setColor(lineColor);
+ }
+
+ // Draw fin name
+ final int xText = x + (width / 3);
+ int yText = yStart - 2;
+ if (isCanted) {
+ int yTextOffset = (int) Math.round((xText - x) * Math.tan(cantAngle));
+ yText += yTextOffset;
+
+ AffineTransform orig = g2.getTransform();
+ g2.rotate(cantAngle, xText, yText); // Rotate text for canted fins
+ g2.drawString(externalComponent.getName(), xText, yText);
+ g2.setTransform(orig); // Stop rotation
+ } else {
+ g2.drawString(externalComponent.getName(), xText, yText);
+ }
}
}
// END If FinSet instance
@@ -318,9 +432,7 @@ public class FinMarkingGuide extends JPanel {
int offset = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints);
drawDoubleArrowLine(g2, x, offset, x + width, offset);
- // if (hasMultipleComponents) {
g2.drawString(externalComponent.getName(), x + (width / 3), offset - 2);
- // }
}
}
// END If TubeFinSet instance
@@ -354,9 +466,9 @@ public class FinMarkingGuide extends JPanel {
// END If RailButton instance
}
// Only if the tube has a lug or button or multiple fin and/or tube fin sets does the orientation of
- // the marking guide matter. So print 'Front'.
+ // the marking guide matter. So print 'Fore end'.
if (hasMultipleComponents) {
- drawFrontIndication(g2, x, y, 0, (int) circumferenceInPoints, width);
+ drawFrontIndication(g2, x, y + length, width);
}
// At most, two marking guides horizontally. After that, move down and back to the left margin.
@@ -505,36 +617,131 @@ public class FinMarkingGuide extends JPanel {
g2.drawLine(x + fromEdge, y + length - tickLength, x + fromEdge, y + length);
//Lower right
g2.drawLine(x + width - fromEdge, y + length - tickLength, x + width - fromEdge, y + length);
+
+ drawFrontIndication(g2, x, y + length, width);
}
/**
- * Draw a vertical string indicating the front of the rocket. This is necessary when a launch lug and/or
+ * Draw a tab indicating the fore end of the rocket. This is necessary when a launch lug and/or
* rail button exists to give proper orientation of the guide (assuming that the lug and/or button is
- * asymmetrically positioned with respect to a fin).
+ * asymmetrically positioned with respect to a fin). Also necessary for canted fins.
*
* @param g2 the graphics context
* @param x the starting x coordinate
* @param y the starting y coordinate
- * @param spacing the space between fin lines
- * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube
- * circumference
* @param width the width of the marking guide in print units; somewhat arbitrary
*/
- private void drawFrontIndication(Graphics2D g2, int x, int y, int spacing, int length, int width) {
- //The magic numbers here are fairly arbitrary. They're chosen in a manner that best positions 'Front' to be
- //readable, without going to complex string layout prediction logic.
- int rotateX = x + width - 16;
- int rotateY = y + (int) (spacing * 1.5) + 20;
- if (rotateY > y + length + 14) {
- rotateY = y + length / 2 - 10;
- }
- g2.translate(rotateX, rotateY);
- g2.rotate(Math.PI / 2);
- g2.drawString(trans.get("FinMarkingGuide.lbl.Front"), 0, 0);
- g2.rotate(-Math.PI / 2);
- g2.translate(-rotateX, -rotateY);
+ private void drawFrontIndication(Graphics2D g2, int x, int y, int width) {
+ // Draw a tab at the bottom of the marking guide to indicate the fore end of the rocket
+ int tabWidth = (int) Math.round(width * 0.8);
+ int tabSpacing = (width - tabWidth) / 2;
+ int tabHeight = 20;
+ float strokeWidth = 1.0f;
+ float strokeOffset = thinStroke.getLineWidth() / 2; // Offset to not draw over the marking guide stroke
+
+ Stroke origStroke = g2.getStroke();
+ Stroke stroke = new BasicStroke(strokeWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL);
+ g2.setStroke(stroke);
+
+ // Draw the tab outline
+ Path2D tab = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 4);
+ tab.moveTo(x, y + strokeOffset);
+ tab.lineTo(x + width, y + strokeOffset);
+ tab.lineTo(x + width - tabSpacing, y + tabHeight);
+ tab.lineTo(x + tabSpacing, y + tabHeight);
+ tab.closePath();
+ g2.draw(tab);
+
+ // Reset the stroke
+ g2.setStroke(origStroke);
+
+ // Fill in the tab
+ Color color = g2.getColor();
+ g2.setColor(new Color(220, 220, 220));
+ g2.fill(tab);
+ g2.setColor(color);
+
+ // Draw an arrow to the left and the text "Fore"
+ final int arrowXStart = x + width - tabSpacing - 5;
+ final int arrowWidth = 50;
+ final int arrowY = y + (tabHeight / 2);
+ final int textY = arrowY + g2.getFontMetrics().getHeight() / 2 - 3;
+ drawRightArrowLine(g2, arrowXStart - arrowWidth, arrowY, arrowXStart, arrowY);
+ String frontText = trans.get("FinMarkingGuide.lbl.Front");
+ final int textWidth = g2.getFontMetrics().stringWidth(frontText);
+ g2.drawString(frontText, arrowXStart - arrowWidth - textWidth - 3, textY);
}
-
+
+ /**
+ * Draw a horizontal line with arrows on only the right endpoint.
+ *
+ * @param g2 the graphics context
+ * @param x1 the starting x coordinate
+ * @param y1 the starting y coordinate
+ * @param x2 the ending x coordinate
+ * @param y2 the ending y coordinate
+ */
+ void drawRightArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2) {
+ drawArrowLine(g2, x1, y1, x2, y2, 0, false, true);
+ }
+
+ /**
+ * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment.
+ *
+ * @param g2 the graphics context
+ * @param x1 the starting x coordinate
+ * @param y1 the starting y coordinate
+ * @param x2 the ending x coordinate
+ * @param y2 the ending y coordinate
+ * @param angle angle to rotate the arrow header to
+ * @param leftArrow true if the left arrow should be drawn
+ * @param rightArrow true if the right arrow should be drawn
+ */
+ void drawArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2, double angle, boolean leftArrow, boolean rightArrow) {
+ int len = x2 - x1;
+
+ // Draw line
+ int xOffset = (int) Math.round(ARROW_SIZE * Math.abs(Math.cos(angle)));
+ int yOffset = (int) Math.round(ARROW_SIZE * Math.sin(angle));
+ g2.drawLine(x1 + xOffset, y1 + yOffset, x1 + len - xOffset, y2 - yOffset);
+
+ // Rotate for the right arrow
+ AffineTransform orig = g2.getTransform();
+ g2.rotate(angle, x1 + len, y2);
+
+ // Draw right arrow
+ if (rightArrow) {
+ g2.fillPolygon(new int[]{x1 + len, x1 + len - ARROW_SIZE, x1 + len - ARROW_SIZE, x1 + len},
+ new int[]{y2, y2 - ARROW_SIZE / 2, y2 + ARROW_SIZE / 2, y2}, 4);
+ }
+
+ // Rotate for the left arrow
+ g2.setTransform(orig);
+ g2.rotate(angle, x1, y1);
+
+ // Draw left arrow
+ if (leftArrow) {
+ g2.fillPolygon(new int[]{x1, x1 + ARROW_SIZE, x1 + ARROW_SIZE, x1},
+ new int[]{y1, y1 - ARROW_SIZE / 2, y1 + ARROW_SIZE / 2, y1}, 4);
+ }
+
+ g2.setTransform(orig);
+ }
+
+ /**
+ * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment.
+ *
+ * @param g2 the graphics context
+ * @param x1 the starting x coordinate
+ * @param y1 the starting y coordinate
+ * @param x2 the ending x coordinate
+ * @param y2 the ending y coordinate
+ * @param angle angle to rotate the arrow header to
+ */
+ void drawDoubleArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2, double angle) {
+ drawArrowLine(g2, x1, y1, x2, y2, angle, true, true);
+ }
+
/**
* Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment.
*
@@ -545,13 +752,6 @@ public class FinMarkingGuide extends JPanel {
* @param y2 the ending y coordinate
*/
void drawDoubleArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2) {
- int len = x2 - x1;
-
- g2.drawLine(x1, y1, x1 + len, y2);
- g2.fillPolygon(new int[] { x1 + len, x1 + len - ARROW_SIZE, x1 + len - ARROW_SIZE, x1 + len },
- new int[] { y2, y2 - ARROW_SIZE / 2, y2 + ARROW_SIZE / 2, y2 }, 4);
-
- g2.fillPolygon(new int[] { x1, x1 + ARROW_SIZE, x1 + ARROW_SIZE, x1 },
- new int[] { y1, y1 - ARROW_SIZE / 2, y1 + ARROW_SIZE / 2, y1 }, 4);
+ drawDoubleArrowLine(g2, x1, y1, x2, y2, 0);
}
}
diff --git a/swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java b/swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java
index d6f1600de..401efb169 100644
--- a/swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java
+++ b/swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java
@@ -33,10 +33,8 @@ package net.sf.openrocket.gui.util;
import java.awt.Color;
import java.awt.Component;
-import java.awt.Rectangle;
import java.io.Serializable;
-import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JList;
@@ -49,7 +47,7 @@ import javax.swing.border.EmptyBorder;
public class CheckListRenderer extends JCheckBox implements ListCellRenderer, Serializable {
private static final Border NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
- private static final Border SAFE_NO_FOCUS_BORDER = NO_FOCUS_BORDER; // may change in the feature
+ private static final Border SAFE_NO_FOCUS_BORDER = NO_FOCUS_BORDER; // may change in the future
/**
* Constructs a default renderer object for an item in a list.
@@ -162,66 +160,4 @@ public class CheckListRenderer extends JCheckBox implements ListCellRenderer, Se
}
}
- // Methods below are overridden for performance reasons.
-
- @Override
- public void validate() {
- }
-
- @Override
- public void invalidate() {
- }
-
- @Override
- public void repaint() {
- }
-
- @Override
- public void revalidate() {
- }
-
- @Override
- public void repaint(long tm, int x, int y, int width, int height) {
- }
-
- @Override
- public void repaint(Rectangle r) {
- }
-
- @Override
- public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {
- }
-
- @Override
- public void firePropertyChange(String propertyName, char oldValue, char newValue) {
- }
-
- @Override
- public void firePropertyChange(String propertyName, short oldValue, short newValue) {
- }
-
- @Override
- public void firePropertyChange(String propertyName, int oldValue, int newValue) {
- }
-
- @Override
- public void firePropertyChange(String propertyName, long oldValue, long newValue) {
- }
-
- @Override
- public void firePropertyChange(String propertyName, float oldValue, float newValue) {
- }
-
- @Override
- public void firePropertyChange(String propertyName, double oldValue, double newValue) {
- }
-
- @Override
- public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
- }
-
- @SuppressWarnings("serial")
- public static class UIResource extends DefaultListCellRenderer implements javax.swing.plaf.UIResource {
- }
-
}
\ No newline at end of file
diff --git a/swing/src/net/sf/openrocket/gui/util/UITheme.java b/swing/src/net/sf/openrocket/gui/util/UITheme.java
index 43805d870..ecc1a2d1a 100644
--- a/swing/src/net/sf/openrocket/gui/util/UITheme.java
+++ b/swing/src/net/sf/openrocket/gui/util/UITheme.java
@@ -2,6 +2,7 @@ package net.sf.openrocket.gui.util;
import com.github.weisj.darklaf.LafManager;
import com.github.weisj.darklaf.theme.DarculaTheme;
+import com.jthemedetecor.OsThemeDetector;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
@@ -10,14 +11,17 @@ import org.slf4j.LoggerFactory;
import javax.swing.BorderFactory;
import javax.swing.Icon;
+import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import java.awt.Color;
import java.awt.Font;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
public class UITheme {
private static final Translator trans = Application.getTranslator();
@@ -527,6 +531,243 @@ public class UITheme {
log.warn("Unable to load RSyntaxTextArea theme", ioe);
}
}
+ },
+ AUTO {
+ private final String displayName = trans.get("UITheme.Auto");
+
+ private Theme getCurrentTheme() {
+ try {
+ final OsThemeDetector detector = OsThemeDetector.getDetector();
+ final boolean isDarkThemeUsed = detector.isDark();
+ if (isDarkThemeUsed) {
+ return Themes.DARK;
+ } else {
+ return Themes.LIGHT;
+ }
+ } catch (Exception ignore) {}
+
+ return Themes.LIGHT;
+ }
+
+ @Override
+ public void applyTheme() {
+ getCurrentTheme().applyTheme();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ @Override
+ public Color getBackgroundColor() {
+ return getCurrentTheme().getBackgroundColor();
+ }
+
+ @Override
+ public Color getBorderColor() {
+ return getCurrentTheme().getBorderColor();
+ }
+
+ @Override
+ public Color getTextColor() {
+ return getCurrentTheme().getTextColor();
+ }
+
+ @Override
+ public Color getDimTextColor() {
+ return getCurrentTheme().getDimTextColor();
+ }
+
+ @Override
+ public Color getTextSelectionForegroundColor() {
+ return getCurrentTheme().getTextSelectionForegroundColor();
+ }
+
+ @Override
+ public Color getTextSelectionBackgroundColor() {
+ return getCurrentTheme().getTextSelectionBackgroundColor();
+ }
+
+ @Override
+ public Color getWarningColor() {
+ return getCurrentTheme().getWarningColor();
+ }
+
+ @Override
+ public Color getDarkWarningColor() {
+ return getCurrentTheme().getDarkWarningColor();
+ }
+
+ @Override
+ public Color getRowBackgroundLighterColor() {
+ return getCurrentTheme().getRowBackgroundLighterColor();
+ }
+
+ @Override
+ public Color getRowBackgroundDarkerColor() {
+ return getCurrentTheme().getRowBackgroundDarkerColor();
+ }
+
+ @Override
+ public Color getFlightDataTextActiveColor() {
+ return getCurrentTheme().getFlightDataTextActiveColor();
+ }
+
+ @Override
+ public Color getFlightDataTextInactiveColor() {
+ return getCurrentTheme().getFlightDataTextInactiveColor();
+ }
+
+ @Override
+ public String getDefaultBodyComponentColor() {
+ return getCurrentTheme().getDefaultBodyComponentColor();
+ }
+
+ @Override
+ public String getDefaultTubeFinSetColor() {
+ return getCurrentTheme().getDefaultTubeFinSetColor();
+ }
+
+ @Override
+ public String getDefaultFinSetColor() {
+ return getCurrentTheme().getDefaultFinSetColor();
+ }
+
+ @Override
+ public String getDefaultLaunchLugColor() {
+ return getCurrentTheme().getDefaultLaunchLugColor();
+ }
+
+ @Override
+ public String getDefaultRailButtonColor() {
+ return getCurrentTheme().getDefaultRailButtonColor();
+ }
+
+ @Override
+ public String getDefaultInternalComponentColor() {
+ return getCurrentTheme().getDefaultInternalComponentColor();
+ }
+
+ @Override
+ public String getDefaultMassObjectColor() {
+ return getCurrentTheme().getDefaultMassObjectColor();
+ }
+
+ @Override
+ public String getDefaultRecoveryDeviceColor() {
+ return getCurrentTheme().getDefaultRecoveryDeviceColor();
+ }
+
+ @Override
+ public String getDefaultPodSetColor() {
+ return getCurrentTheme().getDefaultPodSetColor();
+ }
+
+ @Override
+ public String getDefaultParallelStageColor() {
+ return getCurrentTheme().getDefaultParallelStageColor();
+ }
+
+ @Override
+ public Color getMotorBorderColor() {
+ return getCurrentTheme().getMotorBorderColor();
+ }
+
+ @Override
+ public Color getMotorFillColor() {
+ return getCurrentTheme().getMotorFillColor();
+ }
+
+ @Override
+ public Color getCGColor() {
+ return getCurrentTheme().getCGColor();
+ }
+
+ @Override
+ public Color getCPColor() {
+ return getCurrentTheme().getCPColor();
+ }
+
+ @Override
+ public Color getURLColor() {
+ return getCurrentTheme().getURLColor();
+ }
+
+ @Override
+ public Color getComponentTreeBackgroundColor() {
+ return getCurrentTheme().getComponentTreeBackgroundColor();
+ }
+
+ @Override
+ public Color getComponentTreeForegroundColor() {
+ return getCurrentTheme().getComponentTreeForegroundColor();
+ }
+
+ @Override
+ public Color getFinPointGridMajorLineColor() {
+ return getCurrentTheme().getFinPointGridMajorLineColor();
+ }
+
+ @Override
+ public Color getFinPointGridMinorLineColor() {
+ return getCurrentTheme().getFinPointGridMinorLineColor();
+ }
+
+ @Override
+ public Color getFinPointPointColor() {
+ return getCurrentTheme().getFinPointPointColor();
+ }
+
+ @Override
+ public Color getFinPointSelectedPointColor() {
+ return getCurrentTheme().getFinPointSelectedPointColor();
+ }
+
+ @Override
+ public Color getFinPointBodyLineColor() {
+ return getCurrentTheme().getFinPointBodyLineColor();
+ }
+
+ @Override
+ public Icon getMassOverrideIcon() {
+ return getCurrentTheme().getMassOverrideIcon();
+ }
+
+ @Override
+ public Icon getMassOverrideSubcomponentIcon() {
+ return getCurrentTheme().getMassOverrideSubcomponentIcon();
+ }
+
+ @Override
+ public Icon getCGOverrideIcon() {
+ return getCurrentTheme().getCGOverrideIcon();
+ }
+
+ @Override
+ public Icon getCGOverrideSubcomponentIcon() {
+ return getCurrentTheme().getCGOverrideSubcomponentIcon();
+ }
+
+ @Override
+ public Icon getCDOverrideIcon() {
+ return getCurrentTheme().getCDOverrideIcon();
+ }
+
+ @Override
+ public Icon getCDOverrideSubcomponentIcon() {
+ return getCurrentTheme().getCDOverrideSubcomponentIcon();
+ }
+
+ @Override
+ public Border getBorder() {
+ return getCurrentTheme().getBorder();
+ }
+
+ @Override
+ public void formatScriptTextArea(RSyntaxTextArea textArea) {
+ getCurrentTheme().formatScriptTextArea(textArea);
+ }
}
}