diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index cf604e801..ffd308eb6 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -228,8 +228,9 @@ edtmotorconfdlg.tbl.Separationheader = Separation RenameConfigDialog.title = Rename Configuration RenameConfigDialog.lbl.name = Name for flight configuration: RenameConfigDialog.but.reset = Reset to default -RenameConfigDialog.lbl.infoMotors = The text '{motors}' will be replaced with the motor designation(s). -RenameConfigDialog.lbl.infoManufacturers = The text '{manufacturers}' will be replaced with the motor manufacturer(s). +RenameConfigDialog.lbl.infoMotors = The text '{motors}' will be replaced with the motor designation(s).
\te.g. '{motors} \u2192 'M1350-0'
+RenameConfigDialog.lbl.infoManufacturers = The text '{manufacturers}' will be replaced with the motor manufacturer(s).
\te.g. '{manufacturers}' \u2192 'AeroTech'
+RenameConfigDialog.lbl.infoCombination = A combination of the above can be used.
\te.g. '{motors, manufacturers}' \u2192 'M1350-0, AeroTech'
! Example design dialog diff --git a/core/src/net/sf/openrocket/formatting/MotorConfigurationSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorConfigurationSubstitutor.java new file mode 100644 index 000000000..9c534d079 --- /dev/null +++ b/core/src/net/sf/openrocket/formatting/MotorConfigurationSubstitutor.java @@ -0,0 +1,250 @@ +package net.sf.openrocket.formatting; + +import com.google.inject.Inject; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.motor.MotorConfiguration; +import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.plugin.Plugin; +import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; +import net.sf.openrocket.rocketcomponent.FlightConfigurationId; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.ArrayList; +import net.sf.openrocket.util.Chars; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * General substitutor for motor configurations. This currently includes substitutions for + * - {motors} - the motor designation (e.g. "M1350-0") + * - {manufacturers} - the motor manufacturer (e.g. "AeroTech") + * - a combination of motors and manufacturers, e.g. {motors | manufacturers} -> "M1350-0 | AeroTech" + * You can choose which comes first and what the separator is. E.g. {manufacturers, motors} -> "AeroTech, M1350-0". + * + *

+ * This substitutor is added through injection. All substitutors with the "@Plugin" tag in the formatting package will + * be included automatically. + */ +@Plugin +public class MotorConfigurationSubstitutor implements RocketSubstitutor { + public static final String SUBSTITUTION_START = "{"; + public static final String SUBSTITUTION_END = "}"; + public static final String SUBSTITUTION_MOTORS = "motors"; + public static final String SUBSTITUTION_MANUFACTURERS = "manufacturers"; + + // Substitutions for combinations of motors and manufacturers + private static final String SUBSTITUTION_PATTERN = "\\" + SUBSTITUTION_START + + "(" + SUBSTITUTION_MOTORS + "|" + SUBSTITUTION_MANUFACTURERS + ")" + + "(.*?)" + + "(" + SUBSTITUTION_MOTORS + "|" + SUBSTITUTION_MANUFACTURERS + ")" + + "\\" + SUBSTITUTION_END; + + @Inject + private Translator trans; + + @Override + public boolean containsSubstitution(String input) { + return getSubstitutionContent(input) != null; + } + + @Override + public String substitute(String input, Rocket rocket, FlightConfigurationId configId) { + String description = getConfigurationSubstitution(input, rocket, configId); + String substitutionString = getSubstiutionString(input); + if (substitutionString != null) { + return input.replace(substitutionString, description); + } + return input; + } + + @Override + public Map getDescriptions() { + return null; + } + + public String getConfigurationSubstitution(String input, Rocket rocket, FlightConfigurationId fcid) { + StringBuilder configurations = new StringBuilder(); + int motorCount = 0; + + // Iterate over each stage and store the manufacturer of each motor + List> list = new ArrayList<>(); + List currentList = new ArrayList<>(); + + String[] content = getSubstitutionContent(input); + if (content == null) { + return ""; + } + + FlightConfiguration config = rocket.getFlightConfiguration(fcid); + for (RocketComponent c : rocket) { + if (c instanceof AxialStage) { + currentList = new ArrayList<>(); + list.add(currentList); + } else if (c instanceof MotorMount) { + MotorMount mount = (MotorMount) c; + MotorConfiguration inst = mount.getMotorConfig(fcid); + Motor motor = inst.getMotor(); + + if (mount.isMotorMount() && config.isComponentActive(mount) && (motor != null)) { + String motorDesignation = motor.getDesignation(inst.getEjectionDelay()); + String manufacturer = ""; + if (motor instanceof ThrustCurveMotor) { + manufacturer = ((ThrustCurveMotor) motor).getManufacturer().getDisplayName(); + } + + for (int i = 0; i < mount.getMotorCount(); i++) { + if (content.length == 2) { + if (SUBSTITUTION_MOTORS.equals(content[1])) { + currentList.add(motorDesignation); + } else if (SUBSTITUTION_MANUFACTURERS.equals(content[1])) { + currentList.add(manufacturer); + } else { + continue; + } + } else if (content.length == 4) { + String configString; + if (content[1].equals(SUBSTITUTION_MOTORS)) { + configString = motorDesignation; + } else if (content[1].equals(SUBSTITUTION_MANUFACTURERS)) { + configString = manufacturer; + } else { + continue; + } + configString += content[2]; + if (content[3].equals(SUBSTITUTION_MOTORS)) { + configString += motorDesignation; + } else if (content[3].equals(SUBSTITUTION_MANUFACTURERS)) { + configString += manufacturer; + } else { + continue; + } + currentList.add(configString); + } else { + continue; + } + motorCount++; + } + } + } + } + + if (motorCount == 0) { + return trans.get("Rocket.motorCount.Nomotor"); + } + + // Change multiple occurrences of a motor to n x motor + List stages = new ArrayList<>(); + for (List stage : list) { + String stageName = ""; + String previous = null; + int count = 0; + + Collections.sort(stage); + for (String current : stage) { + if (current.equals(previous)) { + count++; + } else { + if (previous != null) { + String s = count > 1 ? count + Chars.TIMES + previous : previous; + stageName = stageName.equals("") ? s : stageName + "," + s; + } + + previous = current; + count = 1; + } + } + + if (previous != null) { + String s = count > 1 ? "" + count + Chars.TIMES + previous : previous; + stageName = stageName.equals("") ? s : stageName + "," + s; + } + + stages.add(stageName); + } + + for (int i = 0; i < stages.size(); i++) { + String s = stages.get(i); + if (s.equals("") && config.isStageActive(i)) { + s = trans.get("Rocket.motorCount.noStageMotors"); + } + + configurations.append(i == 0 ? s : "; " + s); + } + + return configurations.toString(); + } + + /** + * Returns which string in input should be replaced, or null if no text needs to be replaced. + * @param input The input string + * @return The string to replace, or null if no text needs to be replaced. + */ + private static String getSubstiutionString(String input) { + String[] content = getSubstitutionContent(input); + if (content != null) { + return content[0]; + } + return null; + } + + /** + * Fills in the content of the substitution tag and the separator. + * If there are both a motor and a manufacturer substitution tag, the array will contain the following: + * [0] = The full tag, including substitution start and end + * [1] = The motor/manufacturer substitution tag, depending on which one was found first. + * if there are two substitution tags, the array will also contain the following: + * ([2] = The separator) + * ([3] = The motor/manufacturer substitution tag, depending on which one was found first.) + * @param input The input string + * @return The content of the substitution tag and the separator, or null if no text needs to be replaced. + */ + private static String[] getSubstitutionContent(String input) { + // First try with only the motors tag + String pattern = "\\" + SUBSTITUTION_START + "(" + SUBSTITUTION_MOTORS + ")" + "\\" + SUBSTITUTION_END; + Pattern regexPattern = Pattern.compile(pattern); + Matcher matcher = regexPattern.matcher(input); + if (matcher.find()) { + String[] content = new String[2]; + content[0] = matcher.group(0); + content[1] = matcher.group(1); + return content; + } + // First try with only the manufacturers tag + pattern = "\\" + SUBSTITUTION_START + "(" + SUBSTITUTION_MANUFACTURERS + ")" + "\\" + SUBSTITUTION_END; + regexPattern = Pattern.compile(pattern); + matcher = regexPattern.matcher(input); + if (matcher.find()) { + String[] content = new String[2]; + content[0] = matcher.group(0); + content[1] = matcher.group(1); + return content; + } + + // Then try combined patterns + pattern = SUBSTITUTION_PATTERN; + regexPattern = Pattern.compile(pattern); + matcher = regexPattern.matcher(input); + if (matcher.find()) { + String[] content = new String[4]; + content[0] = matcher.group(0); + content[1] = matcher.group(1); + if (matcher.groupCount() >= 3) { + content[2] = matcher.group(2); + content[3] = matcher.group(3); + for (int i = 4; i < matcher.groupCount(); i++) { + content[3] += matcher.group(i); + } + } + return content; + } + return null; + } +} + diff --git a/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java deleted file mode 100644 index f8d90741f..000000000 --- a/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java +++ /dev/null @@ -1,148 +0,0 @@ -package net.sf.openrocket.formatting; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import com.google.inject.Inject; - -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.motor.Motor; -import net.sf.openrocket.motor.MotorConfiguration; -import net.sf.openrocket.plugin.Plugin; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.FlightConfiguration; -import net.sf.openrocket.rocketcomponent.FlightConfigurationId; -import net.sf.openrocket.rocketcomponent.MotorMount; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.util.ArrayList; -import net.sf.openrocket.util.Chars; - -@Plugin -public class MotorDescriptionSubstitutor implements RocketSubstitutor { - public static final String SUBSTITUTION = "{motors}"; - - @Inject - private Translator trans; - - @Override - public boolean containsSubstitution(String str) { - return str.contains(SUBSTITUTION); - } - - @Override - public String substitute(String str, Rocket rocket, FlightConfigurationId configId) { - String description = getMotorConfigurationDescription(rocket, configId); - return str.replace(SUBSTITUTION, description); - } - - @Override - public Map getDescriptions() { - Map desc = new HashMap(); - desc.put(SUBSTITUTION, trans.get("MotorDescriptionSubstitutor.description")); - return null; - } - - - - public String getMotorConfigurationDescription(Rocket rocket, FlightConfigurationId fcid) { - String name; - int motorCount = 0; - - // Generate the description - - // First iterate over each stage and store the designations of each motor - List> list = new ArrayList>(); - List currentList = Collections.emptyList(); - - FlightConfiguration config = rocket.getFlightConfiguration(fcid); - for (RocketComponent c : rocket) { - if (c instanceof AxialStage) { - currentList = new ArrayList<>(); - list.add(currentList); - - } else if (c instanceof MotorMount) { - MotorMount mount = (MotorMount) c; - MotorConfiguration inst = mount.getMotorConfig(fcid); - Motor motor = inst.getMotor(); - - if (mount.isMotorMount() && config.isComponentActive(mount) && motor != null) { - String designation = motor.getDesignation(inst.getEjectionDelay()); - - for (int i = 0; i < mount.getMotorCount(); i++) { - currentList.add(designation); - motorCount++; - } - } - } - } - - if (motorCount == 0) { - return trans.get("Rocket.motorCount.Nomotor"); - } - - // Change multiple occurrences of a motor to n x motor - List stages = new ArrayList(); - - for (List stage : list) { - String stageName = ""; - String previous = null; - int count = 0; - - Collections.sort(stage); - for (String current : stage) { - if (current.equals(previous)) { - count++; - } else { - if (previous != null) { - String s = ""; - if (count > 1) { - s = "" + count + Chars.TIMES + previous; - } else { - s = previous; - } - - if (stageName.equals("")) - stageName = s; - else - stageName = stageName + "," + s; - } - previous = current; - count = 1; - } - } - if (previous != null) { - String s = ""; - if (count > 1) { - s = "" + count + Chars.TIMES + previous; - } else { - s = previous; - } - - if (stageName.equals("")) - stageName = s; - else - stageName = stageName + "," + s; - } - stages.add(stageName); - } - - name = ""; - for (int i = 0; i < stages.size(); i++) { - String s = stages.get(i); - if (s.equals("") && config.isStageActive(i)) - s = trans.get("Rocket.motorCount.noStageMotors"); - if (i == 0) - name = name + s; - else - name = name + "; " + s; - } - return name; - } - - - -} diff --git a/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java deleted file mode 100644 index bafd73361..000000000 --- a/core/src/net/sf/openrocket/formatting/MotorManufacturerSubstitutor.java +++ /dev/null @@ -1,146 +0,0 @@ -package net.sf.openrocket.formatting; - -import com.google.inject.Inject; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.motor.Motor; -import net.sf.openrocket.motor.MotorConfiguration; -import net.sf.openrocket.motor.ThrustCurveMotor; -import net.sf.openrocket.plugin.Plugin; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.FlightConfiguration; -import net.sf.openrocket.rocketcomponent.FlightConfigurationId; -import net.sf.openrocket.rocketcomponent.MotorMount; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.util.ArrayList; -import net.sf.openrocket.util.Chars; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Plugin -public class MotorManufacturerSubstitutor implements RocketSubstitutor { - public static final String SUBSTITUTION = "{manufacturers}"; - - @Inject - private Translator trans; - - @Override - public boolean containsSubstitution(String str) { - return str.contains(SUBSTITUTION); - } - - @Override - public String substitute(String str, Rocket rocket, FlightConfigurationId configId) { - String description = getMotorConfigurationManufacturer(rocket, configId); - return str.replace(SUBSTITUTION, description); - } - - @Override - public Map getDescriptions() { - Map desc = new HashMap<>(); - desc.put(SUBSTITUTION, trans.get("MotorManufacturerSubstitutor.description")); - return null; - } - - - - public String getMotorConfigurationManufacturer(Rocket rocket, FlightConfigurationId fcid) { - String manufacturers; - int motorCount = 0; - - // Generate the description - - // First iterate over each stage and store the manufacturer of each motor - List> list = new ArrayList<>(); - List currentList = Collections.emptyList(); - - FlightConfiguration config = rocket.getFlightConfiguration(fcid); - for (RocketComponent c : rocket) { - if (c instanceof AxialStage) { - currentList = new ArrayList<>(); - list.add(currentList); - - } else if (c instanceof MotorMount) { - MotorMount mount = (MotorMount) c; - MotorConfiguration inst = mount.getMotorConfig(fcid); - Motor motor = inst.getMotor(); - - if (mount.isMotorMount() && config.isComponentActive(mount) && motor instanceof ThrustCurveMotor) { - String manufacturer = ((ThrustCurveMotor) motor).getManufacturer().getDisplayName(); - - for (int i = 0; i < mount.getMotorCount(); i++) { - currentList.add(manufacturer); - motorCount++; - } - } - } - } - - if (motorCount == 0) { - return trans.get("Rocket.motorCount.Nomotor"); - } - - // Change multiple occurrences of a motor to n x motor - List stages = new ArrayList<>(); - - for (List stage : list) { - String stageName = ""; - String previous = null; - int count = 0; - - Collections.sort(stage); - for (String current : stage) { - if (current.equals(previous)) { - count++; - } else { - if (previous != null) { - String s = ""; - if (count > 1) { - s = "" + count + Chars.TIMES + previous; - } else { - s = previous; - } - - if (stageName.equals("")) - stageName = s; - else - stageName = stageName + "," + s; - } - - previous = current; - count = 1; - } - } - if (previous != null) { - String s = ""; - if (count > 1) { - s = "" + count + Chars.TIMES + previous; - } else { - s = previous; - } - - if (stageName.equals("")) - stageName = s; - else - stageName = stageName + "," + s; - } - stages.add(stageName); - } - - manufacturers = ""; - for (int i = 0; i < stages.size(); i++) { - String s = stages.get(i); - if (s.equals("") && config.isStageActive(i)) - s = trans.get("Rocket.motorCount.noStageMotors"); - if (i == 0) - manufacturers = manufacturers + s; - else - manufacturers = manufacturers + "; " + s; - } - return manufacturers; - } -} - diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java index b516ea05a..372d3f2de 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java @@ -584,6 +584,8 @@ public class FlightConfigurationTest extends BaseTestCase { public void testName() { Rocket rocket = TestRockets.makeFalcon9Heavy(); FlightConfiguration selected = rocket.getSelectedConfiguration(); + + // Test only motors or only manufacturers selected.setName("[{motors}] - [{manufacturers}]"); selected.setAllStages(); @@ -598,6 +600,87 @@ public class FlightConfigurationTest extends BaseTestCase { selected.setAllStages(); selected._setStageActive(0, false); assertEquals("[; M1350-0; 4\u00D7G77-0] - [; AeroTech; 4\u00D7AeroTech]", selected.getName()); + + + // Test combination of motors and manufacturers + selected.setName("[{motors manufacturers}] -- [{manufacturers}] - [{motors}]"); + + selected.setAllStages(); + assertEquals("[[Rocket.motorCount.noStageMotors]; M1350-0 AeroTech; 4\u00D7G77-0 AeroTech] -- [[Rocket.motorCount.noStageMotors]; AeroTech; 4\u00D7AeroTech] - [[Rocket.motorCount.noStageMotors]; M1350-0; 4\u00D7G77-0]", selected.getName()); + + selected.setOnlyStage(0); + assertEquals("[[Rocket.motorCount.Nomotor]] -- [[Rocket.motorCount.Nomotor]] - [[Rocket.motorCount.Nomotor]]", selected.getName()); + + selected.setOnlyStage(1); + assertEquals("[; M1350-0 AeroTech; ] -- [; AeroTech; ] - [; M1350-0; ]", selected.getName()); + + selected.setAllStages(); + selected._setStageActive(0, false); + assertEquals("[; M1350-0 AeroTech; 4\u00D7G77-0 AeroTech] -- [; AeroTech; 4\u00D7AeroTech] - [; M1350-0; 4\u00D7G77-0]", selected.getName()); + + // Test combination of manufacturers and motors + selected.setName("[{manufacturers | motors}]"); + + selected.setAllStages(); + assertEquals("[[Rocket.motorCount.noStageMotors]; AeroTech | M1350-0; 4\u00D7AeroTech | G77-0]", selected.getName()); + + selected.setOnlyStage(0); + assertEquals("[[Rocket.motorCount.Nomotor]]", selected.getName()); + + selected.setOnlyStage(1); + assertEquals("[; AeroTech | M1350-0; ]", selected.getName()); + + selected.setAllStages(); + selected._setStageActive(0, false); + assertEquals("[; AeroTech | M1350-0; 4\u00D7AeroTech | G77-0]", selected.getName()); + + // Test empty tags + selected.setName("{}"); + + selected.setAllStages(); + assertEquals("{}", selected.getName()); + + selected.setOnlyStage(0); + assertEquals("{}", selected.getName()); + + selected.setOnlyStage(1); + assertEquals("{}", selected.getName()); + + selected.setAllStages(); + selected._setStageActive(0, false); + assertEquals("{}", selected.getName()); + + // Test invalid tags (1) + selected.setName("{motorsm}"); + + selected.setAllStages(); + assertEquals("{motorsm}", selected.getName()); + + selected.setOnlyStage(0); + assertEquals("{motorsm}", selected.getName()); + + selected.setOnlyStage(1); + assertEquals("{motorsm}", selected.getName()); + + selected.setAllStages(); + selected._setStageActive(0, false); + assertEquals("{motorsm}", selected.getName()); + + // Test invalid tags (2) + selected.setName("{motors manufacturers '}"); + + selected.setAllStages(); + assertEquals("{motors manufacturers '}", selected.getName()); + + selected.setOnlyStage(0); + assertEquals("{motors manufacturers '}", selected.getName()); + + selected.setOnlyStage(1); + assertEquals("{motors manufacturers '}", selected.getName()); + + selected.setAllStages(); + selected._setStageActive(0, false); + assertEquals("{motors manufacturers '}", selected.getName()); } @Test diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java index 919bf1260..ee295ee9f 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java @@ -70,8 +70,9 @@ public class RenameConfigDialog extends JDialog { // {motors} & {manufacturers} info String text = "" + CommonStrings.dagger + " " + trans.get("RenameConfigDialog.lbl.infoMotors") - + "
" + trans.get("RenameConfigDialog.lbl.infoManufacturers"); - StyledLabel info = new StyledLabel(text, -1); + + trans.get("RenameConfigDialog.lbl.infoManufacturers") + + trans.get("RenameConfigDialog.lbl.infoCombination"); + StyledLabel info = new StyledLabel(text, -2); info.setFontColor(Color.DARK_GRAY); panel.add(info, "spanx, growx, wrap");