[fixes #358] Implement multi-component selection in ScaleDialog

This commit is contained in:
SiboVG 2022-02-17 02:08:45 +01:00
parent 6157ce04b6
commit 04b9332920
4 changed files with 105 additions and 25 deletions

View File

@ -1757,7 +1757,7 @@ Warning.ZERO_RADIUS_BODY = Zero length bodies may not result in accurate simulat
! Scale dialog ! Scale dialog
ScaleDialog.lbl.scaleRocket = Entire rocket ScaleDialog.lbl.scaleRocket = Entire rocket
ScaleDialog.lbl.scaleSubselection = Selection and all subcomponents ScaleDialog.lbl.scaleSubselection = Selection and all subcomponents
ScaleDialog.lbl.scaleSelection = Only selected component ScaleDialog.lbl.scaleSelection = Only selected component(s)
ScaleDialog.title = Scale design ScaleDialog.title = Scale design
ScaleDialog.lbl.scale = Scale: ScaleDialog.lbl.scale = Scale:
ScaleDialog.lbl.scale.ttip = Select whether to scale the entire design or only the selected component ScaleDialog.lbl.scale.ttip = Select whether to scale the entire design or only the selected component
@ -1769,6 +1769,8 @@ ScaleDialog.lbl.scaleTo = to
ScaleDialog.lbl.scaleFromTo.ttip = Define the scaling based on an original and resulting length. ScaleDialog.lbl.scaleFromTo.ttip = Define the scaling based on an original and resulting length.
ScaleDialog.checkbox.scaleMass = Update explicit mass values ScaleDialog.checkbox.scaleMass = Update explicit mass values
ScaleDialog.checkbox.scaleMass.ttip = Scale mass component and override mass values by the cube of the scaling factor ScaleDialog.checkbox.scaleMass.ttip = Scale mass component and override mass values by the cube of the scaling factor
ScaleDialog.checkbox.scaleOffsets = Scale component offsets
ScaleDialog.checkbox.scaleOffsets.ttip = Also scale offsets between components
ScaleDialog.button.scale = Scale ScaleDialog.button.scale = Scale
ScaleDialog.undo.scaleRocket = Scale rocket ScaleDialog.undo.scaleRocket = Scale rocket
ScaleDialog.undo.scaleComponent = Scale component ScaleDialog.undo.scaleComponent = Scale component

View File

@ -17,6 +17,7 @@ import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EventObject; import java.util.EventObject;
import java.util.List;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComboBox; import javax.swing.JComboBox;
@ -241,7 +242,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Scaling free-form fin"); log.info(Markers.USER_MARKER, "Scaling free-form fin");
ScaleDialog dialog = new ScaleDialog(document, finset, SwingUtilities.getWindowAncestor(FreeformFinSetConfig.this), true); ScaleDialog dialog = new ScaleDialog(document, new ArrayList<>(List.of(finset)), SwingUtilities.getWindowAncestor(FreeformFinSetConfig.this), true);
dialog.setVisible(true); dialog.setVisible(true);
dialog.dispose(); dialog.dispose();
} }

View File

@ -3,7 +3,10 @@ package net.sf.openrocket.gui.dialogs;
import java.awt.Window; import java.awt.Window;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -194,22 +197,23 @@ public class ScaleDialog extends JDialog {
private final DoubleModel toField = new DoubleModel(0, UnitGroup.UNITS_LENGTH, 0); private final DoubleModel toField = new DoubleModel(0, UnitGroup.UNITS_LENGTH, 0);
private final OpenRocketDocument document; private final OpenRocketDocument document;
private final RocketComponent selection; private final List<RocketComponent> selection;
private final boolean onlySelection; private final boolean onlySelection;
private JComboBox<String> selectionOption; private JComboBox<String> selectionOption;
private JCheckBox scaleMassValues; private JCheckBox scaleMassValues;
private JCheckBox scaleOffsets;
private boolean changing = false; private boolean changing = false;
/** /**
* Sole constructor. * Sole constructor.
* *
* @param document the document to modify. * @param document the document to modify.
* @param selection the currently selected component (or <code>null</code> if none selected). * @param selection the currently selected componentents (or <code>null</code> if none selected).
* @param parent the parent window. * @param parent the parent window.
*/ */
public ScaleDialog(OpenRocketDocument document, RocketComponent selection, Window parent) { public ScaleDialog(OpenRocketDocument document, List<RocketComponent> selection, Window parent) {
this(document, selection, parent, false); this(document, selection, parent, false);
} }
@ -221,7 +225,7 @@ public class ScaleDialog extends JDialog {
* @param parent the parent window. * @param parent the parent window.
* @param onlySelection true to only allow scaling on the selected component (not the whole rocket) * @param onlySelection true to only allow scaling on the selected component (not the whole rocket)
*/ */
public ScaleDialog(OpenRocketDocument document, RocketComponent selection, Window parent, Boolean onlySelection) { public ScaleDialog(OpenRocketDocument document, List<RocketComponent> selection, Window parent, Boolean onlySelection) {
super(parent, trans.get("title"), ModalityType.APPLICATION_MODAL); super(parent, trans.get("title"), ModalityType.APPLICATION_MODAL);
this.document = document; this.document = document;
@ -236,10 +240,21 @@ public class ScaleDialog extends JDialog {
List<String> options = new ArrayList<String>(); List<String> options = new ArrayList<String>();
if (!onlySelection) if (!onlySelection)
options.add(SCALE_ROCKET); options.add(SCALE_ROCKET);
if (selection != null && selection.getChildCount() > 0) {
boolean subPartsPresent = false;
if (selection != null) {
for (RocketComponent component : selection) {
if (component.getChildCount() > 0) {
subPartsPresent = true;
break;
}
}
}
if (selection != null && subPartsPresent) {
options.add(SCALE_SUBSELECTION); options.add(SCALE_SUBSELECTION);
} }
if (selection != null) {
if (selection != null && selection.size() > 0) {
options.add(SCALE_SELECTION); options.add(SCALE_SELECTION);
} }
@ -251,15 +266,16 @@ public class ScaleDialog extends JDialog {
* Otherwise the maximum body diameter is selected. As a fallback DEFAULT_INITIAL_SIZE is used. * Otherwise the maximum body diameter is selected. As a fallback DEFAULT_INITIAL_SIZE is used.
*/ */
double initialSize = 0; double initialSize = 0;
if (selection != null) { if (selection != null && selection.size() == 1) {
if (selection instanceof SymmetricComponent) { RocketComponent component = selection.get(0);
SymmetricComponent s = (SymmetricComponent) selection; if (component instanceof SymmetricComponent) {
SymmetricComponent s = (SymmetricComponent) component;
initialSize = s.getForeRadius() * 2; initialSize = s.getForeRadius() * 2;
initialSize = MathUtil.max(initialSize, s.getAftRadius() * 2); initialSize = MathUtil.max(initialSize, s.getAftRadius() * 2);
}else if ((selection instanceof ParallelStage) || (selection instanceof PodSet )) { }else if ((component instanceof ParallelStage) || (component instanceof PodSet )) {
initialSize = selection.getRadiusOffset(); initialSize = component.getRadiusOffset();
} else { } else {
initialSize = selection.getLength(); initialSize = component.getLength();
} }
} else { } else {
for (RocketComponent c : document.getRocket()) { for (RocketComponent c : document.getRocket()) {
@ -267,8 +283,6 @@ public class ScaleDialog extends JDialog {
SymmetricComponent s = (SymmetricComponent) c; SymmetricComponent s = (SymmetricComponent) c;
initialSize = s.getForeRadius() * 2; initialSize = s.getForeRadius() * 2;
initialSize = MathUtil.max(initialSize, s.getAftRadius() * 2); initialSize = MathUtil.max(initialSize, s.getAftRadius() * 2);
} else if ((selection instanceof ParallelStage) || (selection instanceof PodSet )) {
initialSize = selection.getRadiusOffset();
} }
} }
} }
@ -330,6 +344,18 @@ public class ScaleDialog extends JDialog {
selectionOption.setEditable(false); selectionOption.setEditable(false);
selectionOption.setToolTipText(tip); selectionOption.setToolTipText(tip);
panel.add(selectionOption, "growx, wrap para*2"); panel.add(selectionOption, "growx, wrap para*2");
// Change the offset checkbox to false when 'Scale selection' is selection and only one component is selected,
// since this is a common action.
selectionOption.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (SCALE_SELECTION.equals(selectionOption.getSelectedItem()) && (selection != null) &&
(selection.size() == 1) && (scaleOffsets != null)) {
scaleOffsets.setSelected(false);
}
}
});
// Scale multiplier // Scale multiplier
@ -393,7 +419,13 @@ public class ScaleDialog extends JDialog {
} }
} }
scaleMassValues.setEnabled(overridden); scaleMassValues.setEnabled(overridden);
panel.add(scaleMassValues, "span, wrap para*3"); panel.add(scaleMassValues, "span, wrap");
// Scale offsets
scaleOffsets = new JCheckBox(trans.get("checkbox.scaleOffsets"));
scaleOffsets.setToolTipText(trans.get("checkbox.scaleOffsets.ttip"));
scaleOffsets.setSelected(true);
panel.add(scaleOffsets, "span, wrap para*3");
// Scale / Accept Buttons // Scale / Accept Buttons
@ -455,7 +487,7 @@ public class ScaleDialog extends JDialog {
try { try {
document.startUndo(trans.get("undo.scaleRocket")); document.startUndo(trans.get("undo.scaleRocket"));
for (RocketComponent c : document.getRocket()) { for (RocketComponent c : document.getRocket()) {
scale(c, mul, scaleMass, true); scale(c, mul, scaleMass, scaleOffsets.isSelected());
} }
} finally { } finally {
document.stopUndo(); document.stopUndo();
@ -466,9 +498,17 @@ public class ScaleDialog extends JDialog {
// Scale component and subcomponents // Scale component and subcomponents
try { try {
document.startUndo(trans.get("undo.scaleComponents")); document.startUndo(trans.get("undo.scaleComponents"));
scale(selection, mul, scaleMass, false);
for (RocketComponent c : selection.getChildren()) { // Keep track of which components are already scaled so that we don't scale children multiple times (if
scale(c, mul, scaleMass, true); // they were also part of selection)
List<RocketComponent> scaledComponents = new ArrayList<>();
for (RocketComponent component : selection) {
scale(component, mul, scaleMass, scaleOffsets.isSelected());
scaledComponents.add(component);
if (component.getChildCount() > 0) {
scaleChildren(component, scaledComponents, mul, scaleMass);
}
} }
} finally { } finally {
document.stopUndo(); document.stopUndo();
@ -476,10 +516,13 @@ public class ScaleDialog extends JDialog {
} else if (SCALE_SELECTION.equals(item)) { } else if (SCALE_SELECTION.equals(item)) {
// Scale only the selected component // Scale only the selected components
try { try {
document.startUndo(trans.get("undo.scaleComponent")); document.startUndo(trans.get("undo.scaleComponent"));
scale(selection, mul, scaleMass, false);
for (RocketComponent component : selection) {
scale(component, mul, scaleMass, scaleOffsets.isSelected());
}
} finally { } finally {
document.stopUndo(); document.stopUndo();
} }
@ -519,6 +562,22 @@ public class ScaleDialog extends JDialog {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
} }
/**
* Iteratively scale the children of component. If one of the children was already present in scaledComponents,
* don't scale it.
* @param component component whose children need to be scaled
* @param scaledComponents list of components that were already scaled
*/
private void scaleChildren(RocketComponent component, List<RocketComponent> scaledComponents, double mul, boolean scaleMass) {
for (RocketComponent child : component.getChildren()) {
if (!scaledComponents.contains(component)) {
scale(child, mul, scaleMass, scaleOffsets.isSelected());
scaledComponents.add(child);
scaleChildren(child, scaledComponents, mul, scaleMass);
}
}
}
private void updateToField() { private void updateToField() {

View File

@ -407,6 +407,24 @@ public class BasicFrame extends JFrame {
return (RocketComponent) path.getLastPathComponent(); return (RocketComponent) path.getLastPathComponent();
} }
/**
* Return the currently selected rocket component, or <code>null</code> if none selected.
*/
private List<RocketComponent> getSelectedComponents() {
TreePath[] paths = componentSelectionModel.getSelectionPaths();
if (paths == null || paths.length == 0)
return null;
List<RocketComponent> result = new LinkedList<>();
for (TreePath path : paths) {
tree.scrollPathToVisible(path);
RocketComponent component = (RocketComponent) path.getLastPathComponent();
result.add(component);
}
return result;
}
/** /**
* Creates the menu for the window. * Creates the menu for the window.
@ -651,7 +669,7 @@ public class BasicFrame extends JFrame {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Scale... selected"); log.info(Markers.USER_MARKER, "Scale... selected");
ScaleDialog dialog = new ScaleDialog(document, getSelectedComponent(), BasicFrame.this); ScaleDialog dialog = new ScaleDialog(document, getSelectedComponents(), BasicFrame.this);
dialog.setVisible(true); dialog.setVisible(true);
dialog.dispose(); dialog.dispose();
} }