Merge branch 'openrocket:unstable' into inch-surface-roughness

This commit is contained in:
Joe Pfeiffer 2023-11-15 11:02:36 -07:00 committed by GitHub
commit 7fd390e4d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 556 additions and 2744 deletions

BIN
.github/getting-started.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -63,7 +63,6 @@ Right, you've dug into the codebase, found that one nasty line that caused all y
1. Explain briefly which issue that you are trying to solve, e.g. 'This PR solves #123 in which buttons were displayed as red instead of blue'
2. Next explain what the underlying issue was, e.g. 'The problem was that by default Java swing displays buttons as red.'
3. Next is how you fixed the issue, e.g. 'Fixed it by overriding the default button color to blue'
4. Finally, for other people to test your code, it is good to include a jar file of your fixed OpenRocket. This can be done using ant ([more info here](https://github.com/openrocket/openrocket/wiki/Instructions-to-Build-from-Terminal)). You can upload this jar-file to e.g. Dropbox or Google Drive and include it in your PR, e.g. 'Here is a jar file for testing: ' or if you're really GitHub-savvy, you can add a hyperlink to the 'jar file'-text. If necessary, you can also include information on how to recreate the original issue so that testers can check whether your code solved the issue. If needed, you can also included information about the expected behavior so that others know what your solution should do.
You can take a look at example PR [#979](https://github.com/openrocket/openrocket/pull/979).

102
README.md
View File

@ -1,65 +1,76 @@
OpenRocket
==========
# OpenRocket 🚀
OpenRocket is a free, fully featured model rocket simulator that allows you to design and simulate your rockets before actually building and flying them.
![Build Status](https://github.com/openrocket/openrocket/actions/workflows/build.yml/badge.svg)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
![GitHub release](https://img.shields.io/github/release/openrocket/openrocket.svg)
[![Github Releases (by release)](https://img.shields.io/github/downloads/openrocket/openrocket/latest/total.svg)](https://GitHub.com/openrocket/openrocket/releases/)
[![Join our Discord server!](https://img.shields.io/discord/1073297014814691328?logo=discord)](https://discord.gg/qD2G5v2FAw)
--------
OpenRocket is a free, fully featured model rocket simulator that allows you to design and simulate your rockets before actually building and flying them.
## 🛠️ Design, Visualize, and Analyze
![Three-stage rocket - 2D](.github/OpenRocket_home_2D.png)
![Three-stage rocket - 3D](.github/OpenRocket_home_3D.png)
![Three-stage rocket - Simulation plot](.github/OpenRocket_sim.png)
1. **Design** your rockets using a rich selection of built-in components:
![Three-stage rocket - 2D](.github/OpenRocket_home_2D.png)
The main features include:
2. **Visualize** your masterpiece in 3D:
![Three-stage rocket - 3D](.github/OpenRocket_home_3D.png)
* Six-degree-of-freedom flight simulation
* Automatic design optimization
* Realtime simulated altitude, velocity and acceleration display
* Staging and clustering support
* Cross-platform (Java-based)
3. **Plot & Analyze** your simulation results for precision and improvements:
![Three-stage rocket - Simulation plot](.github/OpenRocket_sim.png)
Read more about it on the [OpenRocket Wiki](http://wiki.openrocket.info).
## 🌟 Features
Installers
----------
OpenRocket maintains an installer for installing the software and Java runtime. You can find the installers on [our
website](https://openrocket.info/downloads.html).
- **Six-degree-of-freedom flight simulation**
- **Automatic design optimization**
- **Realtime simulated altitude, velocity, and acceleration display**
- **Staging and clustering support**
- **Cross-platform (Java-based)**
... plus many more
📖 Read more on [our website](https://openrocket.info/) or the [OpenRocket Wiki](http://wiki.openrocket.info).
## 💾 Installers
You can find the OpenRocket installers [here](https://openrocket.info/downloads.html).
## 📝 Release Notes
Release Notes
-------------
Release notes are available on each [release's page](https://github.com/openrocket/openrocket/releases) or on [our website](https://openrocket.info/release_notes.html).
License
-------
## 🚀 Getting started
OpenRocket is an Open Source project licensed under the [GNU GPL](https://www.gnu.org/licenses/gpl-3.0.en.html). This means that the software is free to use for whatever purposes, and the source code is also available for studying and extending.
The easiest way to get started is to open one of our in-program example designs:
Contributing
------------
OpenRocket needs help to become even better. Implementing features, writing documentation and creating example designs are just a few ways of helping. If you are interested in helping make OpenRocket the best rocket simulator out there, please [click here for information on how to get involved](http://openrocket.sourceforge.net/getinvolved.html) and [read the practicalities of contributing here](CONTRIBUTING.md).
![Get started with the example designs](.github/getting-started.png)
**Contributors**
- Sampo Niskanen, main developer
- Doug Pedrick, support for RockSim designs, printing
- Kevin Ruland, Android version
- Bill Kuker, 3D visualization
- Richard Graham, geodetic computations
- Jason Blood, freeform fin set import
- Boris du Reau, internationalization
- Daniel Williams, pod support, maintainer
- Joe Pfeiffer (maintainer)
- Billy Olsen (maintainer)
- Sibo Van Gool (RASAero file format, 3D OBJ export, dark theme, maintainer)
- Neil Weinstock (tester, icons, forum support)
- H. Craig Miller (tester)
Dive into the essentials: adjust component dimensions, plot a simulation, swap out motors, ... Explore the impact of your changes and, most importantly, enjoy the process! 😊
## 💪 Contribute
**Translators**
Help us soar higher! Whether it's implementing features, writing documentation, or creating design examples, every contribution matters. Interested? Check out [how to get involved](http://openrocket.sourceforge.net/getinvolved.html) and the [practicalities of contributing](CONTRIBUTING.md).
### ✨ Contributors
- [Sampo Niskanen](https://github.com/plaa) - Original developer
- [Doug Pedrick](https://github.com/rodinia814) - RockSim designs, printing
- [Kevin Ruland](https://github.com/kruland2607) - Android version
- [Bill Kuker](https://github.com/bkuker) - 3D visualization
- [Richard Graham](https://github.com/rdgraham) - Geodetic computations
- Jason Blood - Freeform fin set import
- [Boris du Reau](https://github.com/bdureau) - Internationalization
- [Daniel Williams](https://github.com/teyrana) - Pod support, maintainer
- [Joe Pfeiffer](https://github.com/JoePfeiffer) - Maintainer
- [Billy Olsen](https://github.com/wolsen) - Maintainer
- [Sibo Van Gool](https://github.com/SiboVG) - RASAero file format, 3D OBJ export, dark theme, maintainer
- [Neil Weinstock](https://github.com/neilweinstock) - Tester, icons, forum support
- [H. Craig Miller](https://github.com/hcraigmiller) - Tester
You can view the full list of contributors [here](https://github.com/openrocket/openrocket/graphs/contributors).
### 🌍Translators
- Tripoli France
- Tripoli Spain
- Stefan Lobas / ERIG
@ -69,3 +80,12 @@ OpenRocket needs help to become even better. Implementing features, writing docu
- Polish Rocketry Society / Łukasz & Alex Kazanski
- Sibo Van Gool
- Mohamed Amin Elkebsi
## 📜 License
OpenRocket is proudly open-source under the [GNU GPL](https://www.gnu.org/licenses/gpl-3.0.en.html) license. Feel free to use, study, and extend.
---
⭐ Please give us a star if you find OpenRocket useful, and spread the word! ⭐
[![Star History Chart](https://api.star-history.com/svg?repos=openrocket/openrocket&type=Date)](https://star-history.com/#openrocket/openrocket&Date)

View File

@ -13,19 +13,19 @@ Release Notes
</div>
<div id="23.09.beta.01">
<div id="23.09">
OpenRocket 23.09.beta.01
OpenRocket 23.09
------------------------
This is the first beta release of version 23.09.
You can find a visual overview of what's new for this release on [our website](https://openrocket.info//downloads.html?vers=23.09#whats-new).
### Major Updates
#### New Features:
* **3D Printing Support: Export any component or combination to OBJ file** (fixes #604)
* **RASAero compatibility: Import/Export CDX1 files** (fixes #875 and #1147)
* **Dark mode and custom UI font size support** (fixes #1089)
* **Dark mode (normal and high-contrast) and custom UI font size support** (fixes #1089)
* **Export sim table to CSV** (fixes #2077)
#### Bug Fixes:
@ -48,27 +48,32 @@ This is the first beta release of version 23.09.
* Set cluster tube separation in absolute or relative units (fixes #1970)
* Support transparent rendering and export of Photo Studio images (fixes #2076)
* Added "Select -> Components of same color" and "Select -> None" options (fixes #2129)
* Remember column width, order and visibility in component preset table (fixes #2357)
### Bug Fixes
* Fix mass issues with fin sets (fixes #2217)
* Fix CG issues for launch lugs and rail buttons (fixes #2040)
* Fixed mass issues with fin sets (fixes #2217)
* Fixed CG issues for launch lugs and rail buttons (fixes #2040)
* Improved rail button drag calculations
* Add parts detail for pods and boosters (fixes #2084)
* Fix parachute position when using auto radius (fixes #2036)
* Added parts detail for pods and boosters (fixes #2084)
* Fixed parachute position when using auto radius (fixes #2036)
* Fixed pod set and booster marker position under certain circumstances (fixes #2047)
* Fix CG marker location in top view (fixes #2050)
* Handle zero-area fins (warn and don't crash with NaN error) (fixes #2032)
* Don't dispose config dialog when no components are selected in 3D view (fixes #2108)
* Display ISA values in temp and pressure fields (fixes #2104)
* Improve simulation of fins on transitions and nose cones (fixes #2113)
* Improved simulation of fins on transitions and nose cones (fixes #2113)
* Cleaned up multi-sim editing (fixes #2138 and #1826)
* Update ruler units immediately when preferences are changed (fixes #2151)
* Compute CG and CP based on currently active stages (fixes #2171)
* Improved mass/CG calculations for fillets (fixes 2209)
* Set auto radius correctly for mass objects (fixes #2267)
* Apply radial positioning to multi-engine clusters (fixes #2283)
* Fix 3D rendering of fin tabs (fixes #2286)
* Fixed 3D rendering of fin tabs (fixes #2286)
* Update recent file list when opening via file association (fixes #2222)
* Corrected the columns displayed in the component preset table's popup menu, ensuring only relevant columns appear
* Ensured optimum delay is saved in flight summary and .ork files (fixes #2353)
* Corrected longitudinal moment of inertia calculations by excluding shoulders (fixes #2278)
* Fixed exception when setting wind speed to zero (fixes #2386)
### Miscellaneous
* Updated example rockets (including brand-new two stage example)
@ -76,15 +81,17 @@ This is the first beta release of version 23.09.
* Decrease minimum FoV to 10 degrees in Photo Studio
* Increase resolution of launch temperature and pressure to 2 decimal places (fixes #2003)
* Display Cd override with 3 decimal places
* Add wiki button to help menu (fixes #2046)
* Added wiki button to help menu (fixes #2046)
* Eliminate option to save "some" sim data (fixes #2024)
* Add OK/Cancel buttons when editing simulations (fixes #2158)
* Add OK/Cancel buttons when editing preferences (fixes #2266)
* Added OK/Cancel buttons when editing simulations (fixes #2158)
* Added OK/Cancel buttons when editing preferences (fixes #2266)
* Added multi-sim edit indicators (fixes #2159)
* Show warning when motor file has illegal format (fixes #2150)
* Reset window position if off-screen (fixes #2141)
* Keep current field value when "auto" option is unchecked (fixes #2096)
* Open dialog to save design info when first saving file
* Added '3D Printable Nose Cone and Fins' to example rockets
* Use more sensible colors for thrust curve selection in motor selection dialog (fixes #2385)
...along with numerous other minor fixes and enhancements.
@ -95,6 +102,8 @@ This is the first beta release of version 23.09.
OpenRocket 22.02 (2023-02-08)
------------------------
You can find a visual overview of what's new for this release on [our website](https://openrocket.info//downloads.html?vers=22.02#whats-new).
The 22.02 release includes hundreds of new features, bug fixes, and UI improvements, more than we could ever fit into one set of release notes. These notes summarize the highlights of the entire release; for more detail consult the notes from the five previous public beta releases.
Please note that version 22.02 is required for Macs running macOS 13.0 or later.

View File

@ -1,6 +1,6 @@
# The OpenRocket build version
build.version=23.09.beta.01
build.version=23.09
# The copyright year for the build. Displayed in the about dialog.
# Will show as Copyright 2013-${build.copyright}

View File

@ -40,6 +40,11 @@ public abstract class TubeCalc extends RocketComponentCalc {
public double calculatePressureCD(FlightConditions conditions,
double stagnationCD, double baseCD, WarningSet warnings) {
// If we aren't moving, treat CD as 0
final double v = conditions.getVelocity();
if (v < MathUtil.EPSILON)
return 0;
// Need to check for tube inner area 0 in case of rockets using launch lugs with
// an inner radius of 0 to emulate rail guides (or just weird rockets, of course)
double tubeCD = 0.0;
@ -49,7 +54,6 @@ public abstract class TubeCalc extends RocketComponentCalc {
final double p = conditions.getAtmosphericConditions().getPressure();
final double t = conditions.getAtmosphericConditions().getTemperature();
final double rho = conditions.getAtmosphericConditions().getDensity();
final double v = conditions.getVelocity();
// Reynolds number (note Reynolds number for the interior of a pipe is based on diameter,
// not length (t))

View File

@ -338,14 +338,12 @@ public class MassCalculation {
eachChild.prefix = prefix + "....";
eachChild.calculateStructure();
// accumulate children's data
children.merge( eachChild );
}
}
this.merge( children );
if (this.config.isComponentActive(component) ){
Coordinate compCM = component.getComponentCG();
@ -355,26 +353,25 @@ public class MassCalculation {
// setting zero as the CG position means the top of the component, which is component.getPosition()
final Coordinate compZero = parentTransform.transform( component.getPosition() );
if (component.isSubcomponentsOverriddenMass() || component.isSubcomponentsOverriddenCG()) {
if (component.isMassive()) {
// if this component mass, merge it in before overriding:
this.addMass( compCM );
if (component.isMassOverridden()) {
if (!component.isMassive()) {
compCM = children.getCM();
}
if (component.isSubcomponentsOverriddenMass() && component.isMassOverridden()) {
this.setCM( this.getCM().setWeight(component.getOverrideMass()) );
compCM = compCM.setWeight(component.getOverrideMass());
if (component.isSubcomponentsOverriddenMass()) {
children.setCM(children.getCM().setWeight(0));
}
if (component.isSubcomponentsOverriddenCG() && component.isCGOverridden()) {
this.setCM( this.getCM().setX(compZero.x + component.getOverrideCGX()));
}
}else {
if (component.isMassOverridden()) {
compCM = compCM.setWeight( component.getOverrideMass() );
}
if (component.isCGOverridden()) {
compCM = compCM.setX( compZero.x + component.getOverrideCGX() );
}
this.addMass( compCM );
}
if (component.isCGOverridden()) {
compCM = compCM.setX( compZero.x + component.getOverrideCGX() );
if (component.isSubcomponentsOverriddenCG()) {
children.setCM(children.getCM().setX(compCM.x));
}
}
this.addMass(compCM);
if(null != analysisMap){
final CMAnalysisEntry entry = analysisMap.get(component.hashCode());
@ -398,6 +395,8 @@ public class MassCalculation {
// System.err.println(String.format( "%s....componentData: %s", prefix, compCM.toPreciseString() ));
// }
}
this.merge( children );
// // vvv DEBUG
// if( this.config.isComponentActive(component) && 0 < this.getMass() ) {

View File

@ -855,7 +855,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
/**
* Return whether the mass and/or CG override overrides all subcomponent values
* Return whether the mass override overrides all subcomponent values
* as well. The default implementation is a normal getter/setter implementation,
* however, subclasses are allowed to override this behavior if some subclass
* always or never overrides subcomponents. In this case the subclass should
@ -869,7 +869,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
return overrideSubcomponentsMass;
}
// TODO: delete when compatibility with OR 15.03 is not needed anymore
// For compatibility with files created with 15.03
public void setSubcomponentsOverridden(boolean override) {
setSubcomponentsOverriddenMass(override);
setSubcomponentsOverriddenCG(override);
@ -878,10 +878,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
/**
* Set whether the mass and/or CG override overrides all subcomponent values
* Set whether the mass override overrides all subcomponent values
* as well. See {@link #isSubcomponentsOverriddenMass()} for details.
*
* @param override whether the mass and/or CG override overrides all subcomponent.
* @param override whether the mass override overrides all subcomponent.
*/
public void setSubcomponentsOverriddenMass(boolean override) {
for (RocketComponent listener : configListeners) {
@ -916,10 +916,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
/**
* Set whether the mass and/or CG override overrides all subcomponent values
* Set whether the CG override overrides all subcomponent values
* as well. See {@link #isSubcomponentsOverriddenCG()} for details.
*
* @param override whether the mass and/or CG override overrides all subcomponent.
* @param override whether the CG override overrides all subcomponent.
*/
public void setSubcomponentsOverriddenCG(boolean override) {
for (RocketComponent listener : configListeners) {

View File

@ -48,6 +48,68 @@ public class MassCalculatorTest extends BaseTestCase {
assertEquals("Empty Rocket Longitudinal MOI calculated incorrectly: ", 0, actualMOIlong, 0);
}
@Test
public void testStageOverride() {
Rocket rocket = new Rocket();
AxialStage stage = new AxialStage();
rocket.addChild(stage);
FlightConfiguration config = rocket.getEmptyConfiguration();
config.setAllStages();
rocket.enableEvents();
BodyTube tube1 = new BodyTube();
tube1.setLength(1.0);
tube1.setMassOverridden(true);
tube1.setOverrideMass(1.0);
stage.addChild(tube1);
BodyTube tube2 = new BodyTube();
tube2.setLength(2.0);
tube2.setMassOverridden(true);
tube2.setOverrideMass(2.0);
stage.addChild(tube2);
// tube2.setAxialMethod(AxialMethod.ABSOLUTE);
// tube2.setAxialOffset(1.0);
RigidBody structure = MassCalculator.calculateStructure(config);
assertEquals("No overrides -- mass incorrect", 3.0, structure.cm.weight, EPSILON);
assertEquals("No overrides -- CG incorrect", 1.5, structure.cm.x, EPSILON);
stage.setMassOverridden(true);
stage.setOverrideMass(1.0);
structure = MassCalculator.calculateStructure(config);
assertEquals("Overrides: mass -- mass incorrect", 4.0, structure.cm.weight, EPSILON);
assertEquals("Overrides: mass -- CG incorrect", 1.5, structure.cm.x, EPSILON);
stage.setSubcomponentsOverriddenMass(true);
structure = MassCalculator.calculateStructure(config);
assertEquals("Overrides: mass, children mass -- mass incorrect", 1.0, structure.cm.weight, EPSILON);
assertEquals("Overrides: mass, children mass -- CG incorrect", 1.5, structure.cm.x, EPSILON);
stage.setCGOverridden(true);
stage.setOverrideCGX(1.0);
structure = MassCalculator.calculateStructure(config);
assertEquals("Overrides: mass, children mass, CG -- mass incorrect", 1.0, structure.cm.weight, EPSILON);
assertEquals("Overrides: mass, children mass, CG -- CG incorrect", 1.0, structure.cm.x, EPSILON);
stage.setSubcomponentsOverriddenCG(true);
structure = MassCalculator.calculateStructure(config);
assertEquals("Overrides: mass, children mass, CG, children CG -- mass incorrect", 1.0, structure.cm.weight, EPSILON);
assertEquals("Overrides: mass, children mass, CG, children CG -- CG incorrect", 1.0, structure.cm.x, EPSILON);
stage.setSubcomponentsOverriddenMass(false);
structure = MassCalculator.calculateStructure(config);
assertEquals("Overrides: mass, CG, children CG -- mass incorrect", 4.0, structure.cm.weight, EPSILON);
assertEquals("Overrides: mass, CG, children CG -- CG incorrect", 1.0, structure.cm.x, EPSILON);
stage.setSubcomponentsOverriddenCG(false);
structure = MassCalculator.calculateStructure(config);
assertEquals("Overrides: mass, CG -- mass incorrect", 4.0, structure.cm.weight, EPSILON);
assertEquals("Overrides: mass, CG -- CG incorrect", 1.375, structure.cm.x, EPSILON);
}
@Test
public void testAlphaIIIStructure() {
Rocket rocket = TestRockets.makeEstesAlphaIII();
@ -1094,7 +1156,6 @@ public class MassCalculatorTest extends BaseTestCase {
mmt.setOverrideCGX(0.395);
RigidBody structure = MassCalculator.calculateStructure(config);
final double expMass = 0.6063562096046;
double calcTotalMass = structure.getMass();
assertEquals(" Booster Launch Mass is incorrect: ", expMass, calcTotalMass, EPSILON);

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

22
install4j/.gitignore vendored
View File

@ -1,22 +0,0 @@
# Compiled class file
*.class
# Log file
*.log
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# Install4j media
media/
# macOS files
.DS_Store
code_signing/

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<install4j version="10.0.4" transformSequenceNumber="10">
<directoryPresets config="../../core/resources/pix/icon" />
<application name="OpenRocket 23.09.beta.01" applicationId="8434-9327-1469-6373" mediaDir="media" mediaFilePattern="${compiler:sys.shortName}_${compiler:sys.version}_${compiler:sys.platform}" shortName="OpenRocket" publisher="OpenRocket" publisherWeb="http://openrocket.info" version="23.09.beta.01" allPathsRelative="true" convertDotsToUnderscores="false" macVolumeId="5f58a2be20d8e22f" javaMinVersion="17" javaMaxVersion="17" jdkMode="jdk" jdkName="JDK 11.0">
<application name="OpenRocket" applicationId="8434-9327-1469-6373" mediaDir="media" mediaFilePattern="${compiler:sys.shortName}_${compiler:sys.version}_${compiler:sys.platform}" shortName="OpenRocket" publisher="OpenRocket" publisherWeb="http://openrocket.info" version="23.09" allPathsRelative="true" convertDotsToUnderscores="false" macVolumeId="5f58a2be20d8e22f" javaMinVersion="17" javaMaxVersion="17" jdkMode="jdk" jdkName="JDK 11.0">
<codeSigning macEnabled="true" macPkcs12File="./code_signing/OpenRocket_macOS.p12" windowsEnabled="true" windowsPkcs12File="./code_signing/OpenRocket_Windows.pfx" macNotarize="true" appleId="sibo.vangool@hotmail.com" />
<jreBundles jdkProviderId="Liberica" release="17/17.0.7+7">
<modules>
@ -19,7 +19,7 @@
</entries>
</files>
<launchers>
<launcher name="OpenRocket 23.09.beta.01" id="59" icnsFile="../../core/resources/pix/icon/icon-macos.icns">
<launcher name="OpenRocket 23.09" id="59" icnsFile="../../core/resources/pix/icon/icon-macos.icns">
<executable name="OpenRocket" iconSet="true" iconFile="../../core/resources/pix/icon/icon-windows.ico" executableDir="." executableMode="gui">
<versionInfo include="true" fileDescription="A model rocket flight-trajectory simulator." legalCopyright="Copyright 2007-2023 Sampo Niskanen and Others" internalName="${compiler:sys.shortName} ${compiler:sys.version}" />
</executable>

View File

@ -18,13 +18,17 @@ to publish installers for the following platforms.
# Instructions on updating the macOS drag-and-drop installer
This is an example of updating the installer from 22.02 to 23.09.beta.01:
If you use the `macOS_resources/template_dmg_rw.dmg` file, you can skip to step 4
1. Make sure install4j is not opened
2. Download the OpenRocket-22.02-macOS.dmg file
3. Make a read/write .dmg file using the terminal command `hdiutil convert OpenRocket-22.02-macOS.dmg -format UDRW -o 22.02.beta.05_rw.dmg` OR use the `template_dmg_rw.dmg`.
3. Make a read/write .dmg file using the terminal command `hdiutil convert OpenRocket-22.02-macOS.dmg -format UDRW -o 22.02.beta.05_rw.dmg`
4. Enlarge the writable DMG, by first checking the current size: `hdiutil resize template_dmg_rw.dmg`, e.g. you get 430000 in the 'cur' column, then just resize it to e.g. 500000: `hdiutil resize -sectors 500000 template_dmg_rw.dmg`
5. Mount the DMG: `hdiutil attach template_dmg_rw.dmg`
6. Open the OpenRocket-disk from your desktop and change the app name from 22.02 to 23.09
7. Copy the .DS_Store to `openrocket/install4j/23.09/macOS_resources` by running the command `cp /Volumes/OpenRocket/.DS_Store openrocket/install4j/23.09/macOS_resources/DS_Store`
8. Eject the OpenRocket DMG disk from your desktop (important step)
9. Delete `template_dmg_rw.dmg`
9. (optional) Delete `template_dmg_rw.dmg`
10. You're all done!

View File

@ -1,7 +1,6 @@
package net.sf.openrocket.gui.components;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.ItemSelectable;
import java.awt.event.ActionEvent;
@ -18,11 +17,10 @@ import javax.swing.Action;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.UITheme;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.StateChangeListener;
@ -47,13 +45,17 @@ public class UnitSelector extends StyledLabel implements StateChangeListener, Mo
private final boolean showValue;
private final Border normalBorder;
private final Border withinBorder;
private static Border normalBorder;
private static Border withinBorder;
private final List<ItemListener> itemListeners = new ArrayList<ItemListener>();
static {
initColors();
}
/**
* Common private constructor that sets the values and sets up the borders.
* Either model or group must be null.
@ -85,14 +87,6 @@ public class UnitSelector extends StyledLabel implements StateChangeListener, Mo
addMouseListener(this);
// Define borders to use:
normalBorder = new CompoundBorder(
new LineBorder(new Color(0f, 0f, 0f, 0.08f), 1), new EmptyBorder(1, 1, 1,
1));
withinBorder = new CompoundBorder(new LineBorder(new Color(0f, 0f, 0f, 0.6f)),
new EmptyBorder(1, 1, 1, 1));
// Add model listener if showing value
if (showValue)
this.model.addChangeListener(this);
@ -117,6 +111,15 @@ public class UnitSelector extends StyledLabel implements StateChangeListener, Mo
}
private static void initColors() {
updateColors();
UITheme.Theme.addUIThemeChangeListener(UnitSelector::updateColors);
}
private static void updateColors() {
normalBorder = GUIUtil.getUITheme().getUnitSelectorBorder();
withinBorder = GUIUtil.getUITheme().getUnitSelectorFocusBorder();
}
/**

View File

@ -113,7 +113,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
JTable table;
JPanel panel = new JPanel(new MigLayout("fill"));
JPanel panel = new JPanel(new MigLayout("fill", "[120lp][70lp][]"));
add(panel);
rkt = rocketPanel.getDocument().getRocket();
@ -133,7 +133,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
roll = new DoubleModel(rocketPanel, "CPRoll", UnitGroup.UNITS_ROLL);
//// Wind direction:
panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.winddir")), "width 120lp!");
panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.winddir")));
panel.add(new UnitSelector(theta, true), "width 50lp!");
BasicSlider slider = new BasicSlider(theta.getSliderModel(0, 2 * Math.PI));
panel.add(slider, "growx, split 2");
@ -164,17 +164,17 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
panel.add(scrollPane, "gap paragraph, spany 4, w 300lp, grow, height :100lp:, wrap");
////Angle of attack:
panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.angleofattack")), "width 120lp!");
panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.angleofattack")));
panel.add(new UnitSelector(aoa, true), "width 50lp!");
panel.add(new BasicSlider(aoa.getSliderModel(0, Math.PI)), "growx, wrap");
//// Mach number:
panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.machnumber")), "width 120lp!");
panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.machnumber")));
panel.add(new UnitSelector(mach, true), "width 50lp!");
panel.add(new BasicSlider(mach.getSliderModel(0, 3)), "growx, wrap");
//// Roll rate:
panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.rollrate")), "width 120lp!");
panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.rollrate")));
panel.add(new UnitSelector(roll, true), "width 50lp!");
panel.add(new BasicSlider(roll.getSliderModel(-20 * 2 * Math.PI, 20 * 2 * Math.PI)),
"growx, wrap");

View File

@ -48,6 +48,8 @@ class MotorInformationPanel extends JPanel {
private static Color WITH_COMMENT_COLOR;
private static Color textColor;
private static Color dimTextColor;
private static Color backgroundColor;
private static Color gridColor;
private static Border border;
// Motors in set
@ -203,9 +205,9 @@ class MotorInformationPanel extends JPanel {
title.setPaint(textColor);
chart.setTitle(title);
chart.setBackgroundPaint(this.getBackground());
plot.setBackgroundPaint(Color.WHITE);
plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
plot.setBackgroundPaint(backgroundColor);
plot.setDomainGridlinePaint(gridColor);
plot.setRangeGridlinePaint(gridColor);
chartPanel = new ChartPanel(chart,
false, // properties
@ -259,6 +261,8 @@ class MotorInformationPanel extends JPanel {
WITH_COMMENT_COLOR = GUIUtil.getUITheme().getTextColor();
textColor = GUIUtil.getUITheme().getTextColor();
dimTextColor = GUIUtil.getUITheme().getDimTextColor();
backgroundColor = GUIUtil.getUITheme().getBackgroundColor();
gridColor = GUIUtil.getUITheme().getFinPointGridMajorLineColor();
border = GUIUtil.getUITheme().getBorder();
}

View File

@ -2,7 +2,6 @@ package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
@ -45,6 +44,7 @@ import javax.swing.event.RowSorterListener;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import net.sf.openrocket.gui.plot.Util;
import net.sf.openrocket.gui.util.UITheme;
import net.sf.openrocket.util.StateChangeListener;
import org.jfree.chart.ChartColor;
@ -609,7 +609,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
public static Color getColor(int index) {
Color color = (Color) CURVE_COLORS[index % CURVE_COLORS.length];
Color color = Util.getPlotColor(index);
if (UITheme.isLightTheme(GUIUtil.getUITheme())) {
return color;
} else {
@ -752,16 +752,22 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
public Component getListCellRendererComponent(JList<? extends MotorHolder> list, MotorHolder value, int index,
boolean isSelected, boolean cellHasFocus) {
Component c = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof MotorHolder) {
MotorHolder m = (MotorHolder) value;
c.setForeground(getColor(m.getIndex()));
JLabel label = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value != null) {
Color color = getColor(value.getIndex());
if (isSelected || cellHasFocus) {
label.setBackground(color);
label.setOpaque(true);
Color fg = list.getBackground();
fg = new Color(fg.getRed(), fg.getGreen(), fg.getBlue()); // List background changes for some reason, so clone the color
label.setForeground(fg);
} else {
label.setBackground(list.getBackground());
label.setForeground(color);
}
}
return c;
return label;
}
}
}

View File

@ -52,6 +52,7 @@ import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import net.sf.openrocket.arch.SystemInfo;
import net.sf.openrocket.gui.util.UITheme;
import net.sf.openrocket.gui.widgets.SaveFileChooser;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import org.slf4j.Logger;
@ -1162,7 +1163,7 @@ public class GeneralOptimizationDialog extends JDialog {
// TODO: update this dynamically instead of hard-coded values
// The macOS file chooser has an issue where it does not update its size when the accessory is added.
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS && UITheme.isLightTheme(GUIUtil.getUITheme())) {
Dimension currentSize = chooser.getPreferredSize();
Dimension newSize = new Dimension((int) (1.5 * currentSize.width), (int) (1.3 * currentSize.height));
chooser.setPreferredSize(newSize);

View File

@ -338,17 +338,6 @@ public class GeneralPreferencesPanel extends PreferencesPanel {
// Preference buttons
JPanel buttonPanel = new JPanel(new MigLayout("fillx, ins 0"));
//// Export preferences
final JButton exportPreferences = new SelectColorButton(trans.get("pref.dlg.but.exportPreferences"));
exportPreferences.setToolTipText(trans.get("pref.dlg.but.exportPreferences.ttip"));
exportPreferences.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PreferencesExporter.exportPreferences(parent, preferences.getPreferences());
}
});
buttonPanel.add(exportPreferences);
//// Import preferences
final JButton importPreferences = new SelectColorButton(trans.get("pref.dlg.but.importPreferences"));
importPreferences.setToolTipText(trans.get("pref.dlg.but.importPreferences.ttip"));
@ -376,6 +365,17 @@ public class GeneralPreferencesPanel extends PreferencesPanel {
});
buttonPanel.add(importPreferences);
//// Export preferences
final JButton exportPreferences = new SelectColorButton(trans.get("pref.dlg.but.exportPreferences"));
exportPreferences.setToolTipText(trans.get("pref.dlg.but.exportPreferences.ttip"));
exportPreferences.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PreferencesExporter.exportPreferences(parent, preferences.getPreferences());
}
});
buttonPanel.add(exportPreferences);
//// Reset all preferences
final JButton resetAllPreferences = new SelectColorButton(trans.get("pref.dlg.but.resetAllPreferences"));
resetAllPreferences.setToolTipText(trans.get("pref.dlg.but.resetAllPreferences.ttip"));

View File

@ -36,6 +36,7 @@ import net.sf.openrocket.gui.adaptors.PresetModel;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.gui.util.TableUIPreferences;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.TypedKey;
@ -52,6 +53,7 @@ import net.sf.openrocket.utils.TableRowTraversalPolicy;
*/
@SuppressWarnings("serial")
public class ComponentPresetChooserDialog extends JDialog {
private static final String TABLE_ID = "CmpPrst.";
private static final Translator trans = Application.getTranslator();
@ -154,10 +156,10 @@ public class ComponentPresetChooserDialog extends JDialog {
// need to create componentSelectionTable before filter checkboxes,
// but add to panel after
componentSelectionTable = new ComponentPresetTable(presetType, presets, displayedColumnKeys);
GUIUtil.setAutomaticColumnTableWidths(componentSelectionTable, 20);
// Make the first column (the favorite column) as small as possible
int w = componentSelectionTable.getRowHeight() + 4;
XTableColumnModel tm = componentSelectionTable.getXColumnModel();
//TableColumn tc = componentSelectionTable.getColumnModel().getColumn(0);
TableColumn tc = tm.getColumn(0);
tc.setPreferredWidth(w);
tc.setMaxWidth(w);
@ -166,7 +168,14 @@ public class ComponentPresetChooserDialog extends JDialog {
// The normal left/right and tab/shift-tab key action traverses each cell/column of the table instead of going to the next row.
TableRowTraversalPolicy.setTableRowTraversalPolicy(componentSelectionTable);
// Add checkboxes (legacy, match fore diameter, ...)
panel.add(getFilterCheckboxes(tm, legacyColumnIndex), "wrap para");
// Load the table UI settings from the preferences
boolean legacySelected = showLegacyCheckBox.isSelected();
TableUIPreferences.loadTableUIPreferences(componentSelectionTable, TABLE_ID + component.getComponentName(),
preferences.getTablePreferences());
showLegacyCheckBox.setSelected(legacySelected); // Restore legacy state (may change during UI preference loading)
JScrollPane scrollpane = new JScrollPane();
scrollpane.setViewportView(componentSelectionTable);
@ -202,6 +211,8 @@ public class ComponentPresetChooserDialog extends JDialog {
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TableUIPreferences.storeTableUIPreferences(componentSelectionTable, TABLE_ID + component.getComponentName(),
preferences.getTablePreferences());
ComponentPresetChooserDialog.this.setVisible(false);
applySelectedPreset();
}
@ -214,7 +225,6 @@ public class ComponentPresetChooserDialog extends JDialog {
GUIUtil.rememberWindowSize(this);
this.setLocationByPlatform(true);
GUIUtil.rememberWindowPosition(this);
GUIUtil.rememberTableColumnWidths(componentSelectionTable, "Presets" + component.getClass().getCanonicalName());
updateFilters();
}

View File

@ -166,7 +166,7 @@ public class ComponentPresetTable extends JTable {
this.sorter.toggleSortOrder(2); // Sort by the first column (manufacturer) by default
for (TableColumn hiddenColumn : this.hiddenColumns) {
this.tableColumnModel.setColumnVisible(hiddenColumn, false);
this.tableColumnModel.removeColumn(hiddenColumn);
}
JTableHeader header = this.getTableHeader();

View File

@ -239,4 +239,13 @@ public class XTableColumnModel extends DefaultTableColumnModel {
public TableColumn getColumn(int columnIndex, boolean onlyVisible) {
return tableColumns.elementAt(columnIndex);
}
/**
* Returns an Enumeration of all columns, regardless of their visibility.
*
* @return an Enumeration of all TableColumn objects in this model
*/
public Enumeration<TableColumn> getAllColumns() {
return allTableColumns.elements();
}
}

View File

@ -19,6 +19,8 @@ import javax.swing.KeyStroke;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.sf.openrocket.appearance.Appearance;
import net.sf.openrocket.appearance.defaults.DefaultAppearance;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
@ -943,42 +945,11 @@ public class RocketActions {
}
RocketComponent component = components.get(0);
List<RocketComponent> sameColorComponents;
// Case 1: component has a default appearance (null)
if (component.getAppearance() == null) {
sameColorComponents = getComponentsDefaultColor(component);
}
// Case 2: component has a custom appearance
else {
sameColorComponents = getComponentsCustomColor(component);
}
List<RocketComponent> sameColorComponents = getComponentsSameColor(component);
selectionModel.setSelectedComponents(sameColorComponents);
}
private List<RocketComponent> getComponentsCustomColor(RocketComponent component) {
Color targetColor = component.getAppearance().getPaint();
List<RocketComponent> components = new ArrayList<>();
components.add(component);
for (RocketComponent c : rocket) {
if (c == component || c.getAppearance() == null || c.getAppearance().getPaint() == null) {
continue;
}
Color color = c.getAppearance().getPaint();
// Add components with the same RGB values (ignore alpha)
if (color.getRed() == targetColor.getRed() &&
color.getGreen() == targetColor.getGreen() &&
color.getBlue() == targetColor.getBlue()) {
components.add(c);
}
}
return components;
}
private List<RocketComponent> getComponentsDefaultColor(RocketComponent component) {
private List<RocketComponent> getComponentsSameColor(RocketComponent component) {
List<RocketComponent> components = new ArrayList<>();
components.add(component);
@ -986,9 +957,7 @@ public class RocketActions {
if (c == component) {
continue;
}
// Only add same components & components that also have the default color
if (c.getClass().equals(component.getClass()) && c.getAppearance() == null) {
if (isAppearanceEqual(component, c)) {
components.add(c);
}
}
@ -996,6 +965,38 @@ public class RocketActions {
return components;
}
private boolean isAppearanceEqual(RocketComponent component1, RocketComponent component2) {
Appearance appearance1 = component1.getAppearance();
Appearance appearance2 = component2.getAppearance();
// Both components must have the same default material state
if ((appearance1 == null && appearance2 != null) || (appearance1 != null && appearance2 == null)) {
return false;
}
appearance1 = appearance1 == null ? DefaultAppearance.getDefaultAppearance(component1) : appearance1;
appearance2 = appearance2 == null ? DefaultAppearance.getDefaultAppearance(component2) : appearance2;
return isAppearanceEqual(appearance1, appearance2);
}
private boolean isAppearanceEqual(Appearance app1, Appearance app2) {
Color color1 = app1.getPaint();
Color color2 = app2.getPaint();
if (color1 == null && color2 == null) {
return true;
}
if (color1 == null || color2 == null) {
return false;
}
// Add components with the same RGB values (ignore alpha)
return color1.getRed() == color2.getRed() &&
color1.getGreen() == color2.getGreen() &&
color1.getBlue() == color2.getBlue();
}
@Override
public void clipboardChanged() {
List<RocketComponent> components = selectionModel.getSelectedComponents();

View File

@ -44,7 +44,9 @@ import javax.swing.table.DefaultTableCellRenderer;
import net.sf.openrocket.arch.SystemInfo;
import net.sf.openrocket.gui.components.CsvOptionPanel;
import net.sf.openrocket.gui.util.FileHelper;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.gui.util.UITheme;
import net.sf.openrocket.gui.widgets.SaveFileChooser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -534,7 +536,7 @@ public class SimulationPanel extends JPanel {
// TODO: update this dynamically instead of hard-coded values
// The macOS file chooser has an issue where it does not update its size when the accessory is added.
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS && UITheme.isLightTheme(GUIUtil.getUITheme())) {
Dimension currentSize = chooser.getPreferredSize();
Dimension newSize = new Dimension((int) (1.5 * currentSize.width), (int) (1.3 * currentSize.height));
chooser.setPreferredSize(newSize);

View File

@ -240,13 +240,8 @@ public class SimulationPlot {
plot.setDomainGridlinesVisible(true);
plot.setDomainGridlinePaint(Color.lightGray);
Color[] colors = {new Color(0,114,189), // Colors for data lines
new Color(217,83,25),
new Color(237,177,32),
new Color(126,49,142),
new Color(119,172,48),
new Color(77,190,238),
new Color(162,20,47)};
int cumulativeSeriesCount = 0;
for (int axisno = 0; axisno < 2; axisno++) {
// Check whether axis has any data
if (data[axisno].getSeriesCount() > 0) {
@ -299,13 +294,19 @@ public class SimulationPlot {
plot.setRenderer(axisno, r);
r.setBaseShapesVisible(initialShowPoints);
r.setBaseShapesFilled(true);
r.setSeriesPaint(0, colors[axisno]);
r.setSeriesPaint(1, colors[axisno+2]);
r.setSeriesPaint(2, colors[axisno+4]);
for (int j = 0; j < data[axisno].getSeriesCount(); j++) {
// Set colors for all series of the current axis
for (int seriesIndex = 0; seriesIndex < data[axisno].getSeriesCount(); seriesIndex++) {
int colorIndex = cumulativeSeriesCount + seriesIndex;
r.setSeriesPaint(seriesIndex, Util.getPlotColor(colorIndex));
Stroke lineStroke = new BasicStroke(PLOT_STROKE_WIDTH);
r.setSeriesStroke(j, lineStroke);
r.setSeriesStroke(seriesIndex, lineStroke);
}
// Update the cumulative count for the next axis
cumulativeSeriesCount += data[axisno].getSeriesCount();
// Now we pull the colors for the legend.
for (int j = 0; j < data[axisno].getSeriesCount(); j += branchCount) {
String name = data[axisno].getSeries(j).getDescription();

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.gui.plot;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -7,6 +8,18 @@ import java.util.List;
import net.sf.openrocket.document.Simulation;
public abstract class Util {
private static final Color[] PLOT_COLORS = {
new Color(0,114,189),
new Color(217,83,25),
new Color(237,177,32),
new Color(126,49,142),
new Color(119,172,48),
new Color(77,190,238),
new Color(162,20,47),
new Color(197, 106, 122),
new Color(255, 127, 80),
new Color(85, 107, 47),
};
public static List<String> generateSeriesLabels( Simulation simulation ) {
int size = simulation.getSimulatedData().getBranchCount();
@ -33,4 +46,8 @@ public abstract class Util {
}
return stages;
}
public static Color getPlotColor(int index) {
return PLOT_COLORS[index % PLOT_COLORS.length];
}
}

View File

@ -58,6 +58,7 @@ import javax.swing.event.ChangeListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
@ -405,36 +406,6 @@ public class GUIUtil {
}
}
public static void rememberTableColumnWidths(final JTable table, String keyName) {
final String key = keyName == null ? table.getClass().getName() : keyName;
Enumeration<TableColumn> columns = table.getColumnModel().getColumns();
while (columns.hasMoreElements()) {
TableColumn column = columns.nextElement();
column.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("width")) {
log.debug("Storing width of " + table.getName() + "-" + column + ": " + column.getWidth());
((SwingPreferences) Application.getPreferences()).setTableColumnWidth(
key, column.getModelIndex(), column.getWidth());
}
}
});
final Integer width = ((SwingPreferences) Application.getPreferences()).getTableColumnWidth(
key, column.getModelIndex());
if (width != null) {
column.setPreferredWidth(width);
} else {
column.setPreferredWidth(getOptimalColumnWidth(table, column.getModelIndex()));
}
}
}
public static void rememberTableColumnWidths(final JTable table) {
rememberTableColumnWidths(table, null);
}
public static int getOptimalColumnWidth(JTable table, int columnIndex) {
if (columnIndex >= table.getColumnModel().getColumnCount()) {
return -1;
@ -477,26 +448,46 @@ public class GUIUtil {
window.setLocation(position);
}
}
public static void setAutomaticColumnTableWidths(JTable table, int max) {
/**
* Computes the optimal column widths for the specified table, based on the content in all rows for that column,
* and optionally also the column header content.
* @param table the table
* @param max the maximum width for a column
* @param includeHeaderWidth whether to include the width of the column header
* @return an array of column widths
*/
public static int[] computeOptimalColumnWidths(JTable table, int max, boolean includeHeaderWidth) {
int columns = table.getColumnCount();
int widths[] = new int[columns];
int[] widths = new int[columns];
Arrays.fill(widths, 1);
// Consider the width required by the header text if includeHeaderWidth is true
if (includeHeaderWidth) {
TableCellRenderer headerRenderer = table.getTableHeader().getDefaultRenderer();
for (int col = 0; col < columns; col++) {
TableColumn column = table.getColumnModel().getColumn(col);
Component headerComp = headerRenderer.getTableCellRendererComponent(table, column.getHeaderValue(), false, false, 0, col);
widths[col] = headerComp.getPreferredSize().width;
}
}
// Compare header width to the width required by the cell data
for (int row = 0; row < table.getRowCount(); row++) {
for (int col = 0; col < columns; col++) {
Object value = table.getValueAt(row, col);
//System.out.println("row=" + row + " col=" + col + " : " + value);
widths[col] = Math.max(widths[col], value == null ? 0 : value.toString().length());
TableCellRenderer cellRenderer = table.getCellRenderer(row, col);
Component cellComp = cellRenderer.getTableCellRendererComponent(table, value, false, false, row, col);
int cellWidth = cellComp.getPreferredSize().width;
widths[col] = Math.max(widths[col], cellWidth);
}
}
for (int col = 0; col < columns; col++) {
//System.err.println("Setting column " + col + " to width " + widths[col]);
table.getColumnModel().getColumn(col).setPreferredWidth(Math.min(widths[col], max) * 100);
widths[col] = Math.min(widths[col], max * 100); // Adjusting for your max value and scaling
}
return widths;
}
public static Window getWindowAncestor(Component c) {

View File

@ -55,7 +55,7 @@ public abstract class PreferencesExporter {
// TODO: update this dynamically instead of hard-coded values
// The macOS file chooser has an issue where it does not update its size when the accessory is added.
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS && UITheme.isLightTheme(GUIUtil.getUITheme())) {
Dimension currentSize = chooser.getPreferredSize();
Dimension newSize = new Dimension((int) (1.35 * currentSize.width), (int) (1.2 * currentSize.height));
chooser.setPreferredSize(newSize);

View File

@ -136,6 +136,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public Preferences getPreferences() {
return PREFNODE;
}
/**
* Returns the preference node responsible for saving UI window information (position, size...)
* @return the preference node for window information
*/
public Preferences getWindowsPreferences() {
return PREFNODE.node(NODE_WINDOWS);
}
/**
* Returns the preference node responsible for saving table information (column widths, order...)
* @return the preference node for table information
*/
public Preferences getTablePreferences() {
return PREFNODE.node(NODE_TABLES);
}
public void clearPreferences() {
try {
@ -550,7 +566,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public Point getWindowPosition(Class<?> c) {
int x, y;
String pref = PREFNODE.node(NODE_WINDOWS).get("position." + c.getCanonicalName(), null);
String pref = getWindowsPreferences().get("position." + c.getCanonicalName(), null);
if (pref == null)
return null;
@ -575,7 +591,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
}
public void setWindowPosition(Class<?> c, Point p) {
PREFNODE.node(NODE_WINDOWS).put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
getWindowsPreferences().put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
storeVersion();
}
@ -603,7 +619,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public Dimension getWindowSize(Class<?> c) {
int x, y;
String pref = PREFNODE.node(NODE_WINDOWS).get("size." + c.getCanonicalName(), null);
String pref = getWindowsPreferences().get("size." + c.getCanonicalName(), null);
if (pref == null)
return null;
@ -622,22 +638,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public boolean isWindowMaximized(Class<?> c) {
String pref = PREFNODE.node(NODE_WINDOWS).get("size." + c.getCanonicalName(), null);
String pref = getWindowsPreferences().get("size." + c.getCanonicalName(), null);
return "max".equals(pref);
}
public void setWindowSize(Class<?> c, Dimension d) {
PREFNODE.node(NODE_WINDOWS).put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
getWindowsPreferences().put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
storeVersion();
}
public void setWindowMaximized(Class<?> c) {
PREFNODE.node(NODE_WINDOWS).put("size." + c.getCanonicalName(), "max");
getWindowsPreferences().put("size." + c.getCanonicalName(), "max");
storeVersion();
}
public Integer getTableColumnWidth(String keyName, int columnIdx) {
String pref = PREFNODE.node(NODE_TABLES).get(
String pref = getTablePreferences().get(
"cw." + keyName + "." + columnIdx, null);
if (pref == null)
return null;
@ -655,7 +671,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
}
public void setTableColumnWidth(String keyName, int columnIdx, Integer width) {
PREFNODE.node(NODE_TABLES).put(
getTablePreferences().put(
"cw." + keyName + "." + columnIdx, width.toString());
storeVersion();
}

View File

@ -0,0 +1,107 @@
package net.sf.openrocket.gui.util;
import net.sf.openrocket.gui.dialogs.preset.XTableColumnModel;
import javax.swing.JTable;
import javax.swing.table.TableColumn;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.prefs.Preferences;
/**
* Utility class for storing and loading the following table UI preferences:
* - column width
* - column order
* - column visibility
*/
public class TableUIPreferences {
private static final String TABLE_COLUMN_WIDTH_PREFIX = ".cw.";
private static final String TABLE_COLUMN_ORDER_PREFIX = ".co.";
private static final String TABLE_COLUMN_VISIBILITY_PREFIX = ".cv.";
public static void storeTableUIPreferences(JTable table, String tableName, Preferences preferences) {
// Store column widths
for (int i = 0; i < table.getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
int width = column.getWidth();
preferences.putInt(tableName + TABLE_COLUMN_WIDTH_PREFIX + column.getIdentifier(), width);
}
// Store column order
for (int i = 0; i < table.getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
preferences.putInt(tableName + TABLE_COLUMN_ORDER_PREFIX + column.getIdentifier(), i);
}
// Store column visibility
if (table.getColumnModel() instanceof XTableColumnModel customModel) {
Enumeration<TableColumn> columns = customModel.getAllColumns();
while (columns.hasMoreElements()) {
TableColumn column = columns.nextElement();
boolean isVisible = customModel.isColumnVisible(column);
preferences.putBoolean(tableName + TABLE_COLUMN_VISIBILITY_PREFIX + column.getIdentifier(), isVisible);
}
}
}
public static void loadTableUIPreferences(JTable table, String tableName, Preferences preferences) {
// Ensure all columns are visible
Enumeration<TableColumn> allColumns = table.getColumnModel().getColumns();
List<TableColumn> removedColumns = new ArrayList<>();
while (allColumns.hasMoreElements()) {
TableColumn column = allColumns.nextElement();
if (table.convertColumnIndexToView(column.getModelIndex()) == -1) {
removedColumns.add(column);
}
}
for (TableColumn col : removedColumns) {
table.addColumn(col);
}
// Get all columns from the table's column model and restore visibility
if (table.getColumnModel() instanceof XTableColumnModel customModel) {
Enumeration<TableColumn> columns = customModel.getAllColumns(); // Use getAllColumns to get all columns, including invisible ones
while (columns.hasMoreElements()) {
TableColumn column = columns.nextElement();
String identifier = column.getIdentifier().toString();
// Default to true if the preference is not found
boolean isVisible = preferences.getBoolean(tableName + TABLE_COLUMN_VISIBILITY_PREFIX + identifier, true);
customModel.setColumnVisible(column, isVisible);
}
}
// Now, restore column order
for (int i = 0; i < table.getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
int storedOrder = preferences.getInt(tableName + TABLE_COLUMN_ORDER_PREFIX + column.getIdentifier(), i);
if (storedOrder != i && storedOrder < table.getColumnCount()) {
table.moveColumn(table.convertColumnIndexToView(column.getModelIndex()), storedOrder);
}
}
// Check if any column width is missing from preferences
boolean computeOptimalWidths = false;
for (int i = 0; i < table.getColumnCount() && !computeOptimalWidths; i++) {
TableColumn column = table.getColumnModel().getColumn(i);
if (preferences.get(tableName + TABLE_COLUMN_WIDTH_PREFIX + column.getIdentifier(), null) == null) {
computeOptimalWidths = true;
}
}
// If any column width is missing, compute optimal widths for all columns
int[] optimalWidths = null;
if (computeOptimalWidths) {
optimalWidths = GUIUtil.computeOptimalColumnWidths(table, 20, true);
}
// Restore column widths
for (int i = 0; i < table.getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
int defaultWidth = (optimalWidths != null) ? optimalWidths[i] : column.getWidth();
int width = preferences.getInt(tableName + TABLE_COLUMN_WIDTH_PREFIX + column.getIdentifier(), defaultWidth);
column.setPreferredWidth(width);
}
}
}

View File

@ -14,6 +14,9 @@ import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import java.awt.Color;
import java.awt.Font;
import java.io.IOException;
@ -82,6 +85,8 @@ public class UITheme {
Icon getCDOverrideSubcomponentIcon();
Border getBorder();
Border getUnitSelectorBorder();
Border getUnitSelectorFocusBorder();
void formatScriptTextArea(RSyntaxTextArea textArea);
@ -381,6 +386,20 @@ public class UITheme {
return null;
}
@Override
public Border getUnitSelectorBorder() {
return new CompoundBorder(
new LineBorder(new Color(0f, 0f, 0f, 0.08f), 1),
new EmptyBorder(1, 1, 1, 1));
}
@Override
public Border getUnitSelectorFocusBorder() {
return new CompoundBorder(
new LineBorder(new Color(0f, 0f, 0f, 0.6f)),
new EmptyBorder(1, 1, 1, 1));
}
@Override
public void formatScriptTextArea(RSyntaxTextArea textArea) {
try {
@ -729,6 +748,20 @@ public class UITheme {
return BorderFactory.createLineBorder(getBorderColor());
}
@Override
public Border getUnitSelectorBorder() {
return new CompoundBorder(
new LineBorder(new Color(1f, 1f, 1f, 0.08f), 1),
new EmptyBorder(1, 1, 1, 1));
}
@Override
public Border getUnitSelectorFocusBorder() {
return new CompoundBorder(
new LineBorder(new Color(1f, 1f, 1f, 0.6f)),
new EmptyBorder(1, 1, 1, 1));
}
@Override
public void formatScriptTextArea(RSyntaxTextArea textArea) {
try {
@ -1073,6 +1106,20 @@ public class UITheme {
return BorderFactory.createLineBorder(getBorderColor());
}
@Override
public Border getUnitSelectorBorder() {
return new CompoundBorder(
new LineBorder(new Color(.9f, 0.9f, 0.9f, 0.15f), 1),
new EmptyBorder(1, 1, 1, 1));
}
@Override
public Border getUnitSelectorFocusBorder() {
return new CompoundBorder(
new LineBorder(new Color(0.9f, 0.9f, 0.9f, 0.6f)),
new EmptyBorder(1, 1, 1, 1));
}
@Override
public void formatScriptTextArea(RSyntaxTextArea textArea) {
try {
@ -1439,6 +1486,16 @@ public class UITheme {
return getCurrentTheme().getBorder();
}
@Override
public Border getUnitSelectorBorder() {
return getCurrentTheme().getUnitSelectorBorder();
}
@Override
public Border getUnitSelectorFocusBorder() {
return getCurrentTheme().getUnitSelectorFocusBorder();
}
@Override
public void formatScriptTextArea(RSyntaxTextArea textArea) {
getCurrentTheme().formatScriptTextArea(textArea);