X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=core%2Fsrc%2Fnet%2Fsf%2Fopenrocket%2Fgui%2Fscalefigure%2FRocketPanel.java;fp=core%2Fsrc%2Fnet%2Fsf%2Fopenrocket%2Fgui%2Fscalefigure%2FRocketPanel.java;h=2cef70809bbab6a969e2a4aee1e17f269514fcfa;hb=9349577cdfdff682b2aabd6daa24fdc3a7449b58;hp=cf458724344b770483d96a12decd627fff37285e;hpb=30ba0a882f0c061176ba14dbf86d3d6fad096c02;p=debian%2Fopenrocket diff --git a/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index cf458724..2cef7080 100644 --- a/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -1,6 +1,7 @@ package net.sf.openrocket.gui.scalefigure; +import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; @@ -18,6 +19,7 @@ import java.util.concurrent.ThreadFactory; import javax.swing.AbstractAction; import javax.swing.Action; +import javax.swing.ButtonGroup; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; @@ -43,6 +45,7 @@ import net.sf.openrocket.gui.components.BasicSlider; import net.sf.openrocket.gui.components.StageSelector; import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.gui.configdialog.ComponentConfigDialog; +import net.sf.openrocket.gui.figure3d.RocketFigure3d; import net.sf.openrocket.gui.figureelements.CGCaret; import net.sf.openrocket.gui.figureelements.CPCaret; import net.sf.openrocket.gui.figureelements.Caret; @@ -59,6 +62,8 @@ import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.SymmetricComponent; import net.sf.openrocket.simulation.FlightData; +import net.sf.openrocket.simulation.customexpression.CustomExpression; +import net.sf.openrocket.simulation.customexpression.CustomExpressionSimulationListener; import net.sf.openrocket.simulation.listeners.SimulationListener; import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener; import net.sf.openrocket.simulation.listeners.system.InterruptListener; @@ -74,45 +79,57 @@ import net.sf.openrocket.util.StateChangeListener; * A JPanel that contains a RocketFigure and buttons to manipulate the figure. * * @author Sampo Niskanen + * @author Bill Kuker */ public class RocketPanel extends JPanel implements TreeSelectionListener, ChangeSource { - + private static final long serialVersionUID = 1L; + private static final Translator trans = Application.getTranslator(); + + private boolean is3d; private final RocketFigure figure; + private final RocketFigure3d figure3d; + + private final ScaleScrollPane scrollPane; - + + private final JPanel figureHolder; + private JLabel infoMessage; - + private TreeSelectionModel selectionModel = null; - + + private BasicSlider rotationSlider; + ScaleSelector scaleSelector; + /* Calculation of CP and CG */ private AerodynamicCalculator aerodynamicCalculator; private MassCalculator massCalculator; - + private final OpenRocketDocument document; private final Configuration configuration; - + private Caret extraCP = null; private Caret extraCG = null; private RocketInfo extraText = null; - + private double cpAOA = Double.NaN; private double cpTheta = Double.NaN; private double cpMach = Double.NaN; private double cpRoll = Double.NaN; - + // The functional ID of the rocket that was simulated private int flightDataFunctionalID = -1; private String flightDataMotorID = null; - + private SimulationWorker backgroundSimulationWorker = null; - + private List listeners = new ArrayList(); - + /** * The executor service used for running the background simulations. @@ -123,32 +140,37 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change static { backgroundSimulationExecutor = Executors.newFixedThreadPool(SwingPreferences.getMaxThreadCount(), new ThreadFactory() { - private ThreadFactory factory = Executors.defaultThreadFactory(); - - @Override - public Thread newThread(Runnable r) { - Thread t = factory.newThread(r); - t.setDaemon(true); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }); + private ThreadFactory factory = Executors.defaultThreadFactory(); + + @Override + public Thread newThread(Runnable r) { + Thread t = factory.newThread(r); + t.setDaemon(true); + t.setPriority(Thread.MIN_PRIORITY); + return t; + } + }); } - - + + public RocketPanel(OpenRocketDocument document) { - + this.document = document; configuration = document.getDefaultConfiguration(); - + // TODO: FUTURE: calculator selection aerodynamicCalculator = new BarrowmanCalculator(); massCalculator = new BasicMassCalculator(); - + // Create figure and custom scroll pane figure = new RocketFigure(configuration); - + figure3d = new RocketFigure3d(configuration); + + figureHolder = new JPanel(new BorderLayout()); + scrollPane = new ScaleScrollPane(figure) { + private static final long serialVersionUID = 1L; + @Override public void mouseClicked(MouseEvent event) { handleMouseClick(event); @@ -156,31 +178,77 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change }; scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE); scrollPane.setFitting(true); - + createPanel(); - + + is3d = true; + go2D(); + configuration.addChangeListener(new StateChangeListener() { @Override public void stateChanged(EventObject e) { // System.out.println("Configuration changed, calling updateFigure"); updateExtras(); - figure.updateFigure(); + updateFigures(); + } + }); + + figure3d.addComponentSelectionListener(new RocketFigure3d.ComponentSelectionListener() { + @Override + public void componentClicked(RocketComponent clicked[], MouseEvent event) { + handleComponentClick(clicked, event); } }); } - - + + private void updateFigures() { + if (!is3d) + figure.updateFigure(); + else + figure3d.updateFigure(); + } + + private void go3D() { + if (is3d) + return; + is3d = true; + figureHolder.remove(scrollPane); + figureHolder.add(figure3d, BorderLayout.CENTER); + rotationSlider.setEnabled(false); + scaleSelector.setEnabled(false); + + revalidate(); + figureHolder.revalidate(); + + figure3d.repaint(); + } + + private void go2D() { + if (!is3d) + return; + is3d = false; + figureHolder.remove(figure3d); + figureHolder.add(scrollPane, BorderLayout.CENTER); + rotationSlider.setEnabled(true); + scaleSelector.setEnabled(true); + revalidate(); + figureHolder.revalidate(); + figure.repaint(); + } + /** * Creates the layout and components of the panel. */ private void createPanel() { setLayout(new MigLayout("", "[shrink][grow]", "[shrink][shrink][grow][shrink]")); - + setPreferredSize(new Dimension(800, 300)); - + //// Create toolbar - + + ButtonGroup bg = new ButtonGroup(); + // Side/back buttons FigureTypeAction action = new FigureTypeAction(RocketFigure.TYPE_SIDE); //// Side view @@ -188,27 +256,48 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change //// Side view action.putValue(Action.SHORT_DESCRIPTION, trans.get("RocketPanel.FigTypeAct.ttip.Sideview")); JToggleButton toggle = new JToggleButton(action); + bg.add(toggle); add(toggle, "spanx, split"); - + action = new FigureTypeAction(RocketFigure.TYPE_BACK); //// Back view action.putValue(Action.NAME, trans.get("RocketPanel.FigTypeAct.Backview")); //// Back view action.putValue(Action.SHORT_DESCRIPTION, trans.get("RocketPanel.FigTypeAct.ttip.Backview")); toggle = new JToggleButton(action); + bg.add(toggle); add(toggle, "gap rel"); - + + //// 3d Toggle + final JToggleButton toggle3d = new JToggleButton(new AbstractAction("3D") { + private static final long serialVersionUID = 1L; + { + putValue(Action.NAME, "3D");//TODO + putValue(Action.SHORT_DESCRIPTION, "3D"); //TODO + } + @Override + public void actionPerformed(ActionEvent e) { + if ( ((JToggleButton)e.getSource()).isSelected() ){ + go3D(); + } else { + go2D(); + } + } + }); + bg.add(toggle3d); + toggle3d.setEnabled(RocketFigure3d.is3dEnabled()); + add(toggle3d, "gap rel"); // Zoom level selector - ScaleSelector scaleSelector = new ScaleSelector(scrollPane); + scaleSelector = new ScaleSelector(scrollPane); add(scaleSelector); - + // Stage selector StageSelector stageSelector = new StageSelector(configuration); add(stageSelector, ""); - + // Motor configuration selector @@ -217,55 +306,55 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change label.setHorizontalAlignment(JLabel.RIGHT); add(label, "growx, right"); add(new JComboBox(new MotorConfigurationModel(configuration)), "wrap"); - + // Create slider and scroll pane - + DoubleModel theta = new DoubleModel(figure, "Rotation", UnitGroup.UNITS_ANGLE, 0, 2 * Math.PI); UnitSelector us = new UnitSelector(theta, true); us.setHorizontalAlignment(JLabel.CENTER); add(us, "alignx 50%, growx"); - + // Add the rocket figure - add(scrollPane, "grow, spany 2, wmin 300lp, hmin 100lp, wrap"); - + add(figureHolder, "grow, spany 2, wmin 300lp, hmin 100lp, wrap"); + // Add rotation slider // Minimum size to fit "360deg" JLabel l = new JLabel("360" + Chars.DEGREE); Dimension d = l.getPreferredSize(); - - add(new BasicSlider(theta.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true), + + add(rotationSlider = new BasicSlider(theta.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true), "ax 50%, wrap, width " + (d.width + 6) + "px:null:null, growy"); - + //// Click to select    Shift+click to select other    Double-click to edit    Click+drag to move infoMessage = new JLabel(trans.get("RocketPanel.lbl.infoMessage")); infoMessage.setFont(new Font("Sans Serif", Font.PLAIN, 9)); add(infoMessage, "skip, span, gapleft 25, wrap"); - + addExtras(); } - - + + public RocketFigure getFigure() { return figure; } - + public AerodynamicCalculator getAerodynamicCalculator() { return aerodynamicCalculator; } - + public Configuration getConfiguration() { return configuration; } - + /** * Get the center of pressure figure element. * @@ -274,7 +363,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change public Caret getExtraCP() { return extraCP; } - + /** * Get the center of gravity figure element. * @@ -283,7 +372,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change public Caret getExtraCG() { return extraCG; } - + /** * Get the extra text figure element. * @@ -292,7 +381,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change public RocketInfo getExtraText() { return extraText; } - + public void setSelectionModel(TreeSelectionModel m) { if (selectionModel != null) { selectionModel.removeTreeSelectionListener(this); @@ -301,8 +390,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change selectionModel.addTreeSelectionListener(this); valueChanged((TreeSelectionEvent) null); // updates FigureParameters } - - + + /** * Return the angle of attack used in CP calculation. NaN signifies the default value @@ -312,7 +401,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change public double getCPAOA() { return cpAOA; } - + /** * Set the angle of attack to be used in CP calculation. A value of NaN signifies that * the default AOA (zero) should be used. @@ -324,14 +413,14 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change return; cpAOA = aoa; updateExtras(); - figure.updateFigure(); + updateFigures(); fireChangeEvent(); } - + public double getCPTheta() { return cpTheta; } - + public void setCPTheta(double theta) { if (MathUtil.equals(theta, cpTheta) || (Double.isNaN(theta) && Double.isNaN(cpTheta))) @@ -340,50 +429,50 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change if (!Double.isNaN(theta)) figure.setRotation(theta); updateExtras(); - figure.updateFigure(); + updateFigures(); fireChangeEvent(); } - + public double getCPMach() { return cpMach; } - + public void setCPMach(double mach) { if (MathUtil.equals(mach, cpMach) || (Double.isNaN(mach) && Double.isNaN(cpMach))) return; cpMach = mach; updateExtras(); - figure.updateFigure(); + updateFigures(); fireChangeEvent(); } - + public double getCPRoll() { return cpRoll; } - + public void setCPRoll(double roll) { if (MathUtil.equals(roll, cpRoll) || (Double.isNaN(roll) && Double.isNaN(cpRoll))) return; cpRoll = roll; updateExtras(); - figure.updateFigure(); + updateFigures(); fireChangeEvent(); } - - + + @Override public void addChangeListener(EventListener listener) { listeners.add(0, listener); } - + @Override public void removeChangeListener(EventListener listener) { listeners.remove(listener); } - + protected void fireChangeEvent() { EventObject e = new EventObject(this); for (EventListener l : listeners) { @@ -392,8 +481,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change } } } - - + + /** @@ -406,7 +495,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change * the next component. Otherwise select the first component in the list. */ public static final int CYCLE_SELECTION_MODIFIER = InputEvent.SHIFT_DOWN_MASK; - + private void handleMouseClick(MouseEvent event) { if (event.getButton() != MouseEvent.BUTTON1) return; @@ -414,13 +503,18 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change Point p1 = scrollPane.getViewport().getViewPosition(); int x = p0.x + p1.x; int y = p0.y + p1.y; - + RocketComponent[] clicked = figure.getComponentsByPoint(x, y); - + + handleComponentClick(clicked, event); + } + + private void handleComponentClick(RocketComponent[] clicked, MouseEvent event){ + // If no component is clicked, do nothing if (clicked.length == 0) return; - + // Check whether the currently selected component is in the clicked components. TreePath path = selectionModel.getSelectionPath(); if (path != null) { @@ -437,7 +531,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change } } } - + // Currently selected component not clicked if (path == null) { if (event.isShiftDown() && event.getClickCount() == 1 && clicked.length > 1) { @@ -446,18 +540,18 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change path = ComponentTreeModel.makeTreePath(clicked[0]); } } - + // Set selection and check for double-click selectionModel.setSelectionPath(path); if (event.getClickCount() == 2) { RocketComponent component = (RocketComponent) path.getLastPathComponent(); - + ComponentConfigDialog.showDialog(SwingUtilities.getWindowAncestor(this), document, component); } } - - + + /** @@ -465,15 +559,15 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change * the CP and CG carets. */ private WarningSet warnings = new WarningSet(); - + private void updateExtras() { Coordinate cp, cg; double cpx, cgx; - + // TODO: MEDIUM: User-definable conditions FlightConditions conditions = new FlightConditions(configuration); warnings.clear(); - + if (!Double.isNaN(cpMach)) { conditions.setMach(cpMach); extraText.setMach(cpMach); @@ -481,20 +575,20 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change conditions.setMach(Application.getPreferences().getDefaultMach()); extraText.setMach(Application.getPreferences().getDefaultMach()); } - + if (!Double.isNaN(cpAOA)) { conditions.setAOA(cpAOA); } else { conditions.setAOA(0); } extraText.setAOA(cpAOA); - + if (!Double.isNaN(cpRoll)) { conditions.setRollRate(cpRoll); } else { conditions.setRollRate(0); } - + if (!Double.isNaN(cpTheta)) { conditions.setTheta(cpTheta); cp = aerodynamicCalculator.getCP(configuration, conditions, warnings); @@ -502,21 +596,24 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change cp = aerodynamicCalculator.getWorstCP(configuration, conditions, warnings); } extraText.setTheta(cpTheta); - + cg = massCalculator.getCG(configuration, MassCalcType.LAUNCH_MASS); // System.out.println("CG computed as "+cg+ " CP as "+cp); - + if (cp.weight > 0.000001) cpx = cp.x; else cpx = Double.NaN; - + if (cg.weight > 0.000001) cgx = cg.x; else cgx = Double.NaN; - + + figure3d.setCG(cg); + figure3d.setCP(cp); + // Length bound is assumed to be tight double length = 0, diameter = 0; Collection bounds = configuration.getBounds(); @@ -530,7 +627,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change } length = maxX - minX; } - + for (RocketComponent c : configuration) { if (c instanceof SymmetricComponent) { double d1 = ((SymmetricComponent) c).getForeRadius() * 2; @@ -538,31 +635,31 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change diameter = MathUtil.max(diameter, d1, d2); } } - + extraText.setCG(cgx); extraText.setCP(cpx); extraText.setLength(length); extraText.setDiameter(diameter); extraText.setMass(cg.weight); extraText.setWarnings(warnings); - + if (figure.getType() == RocketFigure.TYPE_SIDE && length > 0) { - + // TODO: LOW: Y-coordinate and rotation extraCP.setPosition(cpx * RocketFigure.EXTRA_SCALE, 0); extraCG.setPosition(cgx * RocketFigure.EXTRA_SCALE, 0); - + } else { - + extraCP.setPosition(Double.NaN, Double.NaN); extraCG.setPosition(Double.NaN, Double.NaN); - + } - + //////// Flight simulation in background - + // Check whether to compute or not if (!((SwingPreferences) Application.getPreferences()).computeFlightInBackground()) { extraText.setFlightData(null); @@ -570,38 +667,38 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change stopBackgroundSimulation(); return; } - + // Check whether data is already up to date if (flightDataFunctionalID == configuration.getRocket().getFunctionalModID() && flightDataMotorID == configuration.getMotorConfigurationID()) { return; } - + flightDataFunctionalID = configuration.getRocket().getFunctionalModID(); flightDataMotorID = configuration.getMotorConfigurationID(); - + // Stop previous computation (if any) stopBackgroundSimulation(); - + // Check that configuration has motors if (!configuration.hasMotors()) { extraText.setFlightData(FlightData.NaN_DATA); extraText.setCalculatingData(false); return; } - + // Start calculation process extraText.setCalculatingData(true); - + Rocket duplicate = (Rocket) configuration.getRocket().copy(); Simulation simulation = ((SwingPreferences)Application.getPreferences()).getBackgroundSimulation(duplicate); simulation.getOptions().setMotorConfigurationID( configuration.getMotorConfigurationID()); - - backgroundSimulationWorker = new BackgroundSimulationWorker(simulation); + + backgroundSimulationWorker = new BackgroundSimulationWorker(document, simulation); backgroundSimulationExecutor.execute(backgroundSimulationWorker); } - + /** * Cancels the current background simulation worker, if any. */ @@ -611,22 +708,26 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change backgroundSimulationWorker = null; } } - - + + /** * A SimulationWorker that simulates the rocket flight in the background and * sets the results to the extra text when finished. The worker can be cancelled * if necessary. */ private class BackgroundSimulationWorker extends SimulationWorker { - - public BackgroundSimulationWorker(Simulation sim) { + + private final CustomExpressionSimulationListener exprListener; + + public BackgroundSimulationWorker(OpenRocketDocument doc, Simulation sim) { super(sim); + List exprs = doc.getCustomExpressions(); + exprListener = new CustomExpressionSimulationListener(exprs); } - + @Override protected FlightData doInBackground() { - + // Pause a little while to allow faster UI reaction try { Thread.sleep(300); @@ -634,59 +735,71 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change } if (isCancelled() || backgroundSimulationWorker != this) return null; - + return super.doInBackground(); } - + @Override protected void simulationDone() { // Do nothing if cancelled if (isCancelled() || backgroundSimulationWorker != this) return; - + backgroundSimulationWorker = null; extraText.setFlightData(simulation.getSimulatedData()); extraText.setCalculatingData(false); figure.repaint(); + figure3d.repaint(); } - + @Override protected SimulationListener[] getExtraListeners() { return new SimulationListener[] { InterruptListener.INSTANCE, - ApogeeEndListener.INSTANCE }; + ApogeeEndListener.INSTANCE, + exprListener}; + } - + @Override protected void simulationInterrupted(Throwable t) { // Do nothing on cancel, set N/A data otherwise if (isCancelled() || backgroundSimulationWorker != this) // Double-check return; - + backgroundSimulationWorker = null; extraText.setFlightData(FlightData.NaN_DATA); extraText.setCalculatingData(false); figure.repaint(); + figure3d.repaint(); } } - - + + /** * Adds the extra data to the figure. Currently this includes the CP and CG carets. */ private void addExtras() { - figure.clearRelativeExtra(); extraCG = new CGCaret(0, 0); extraCP = new CPCaret(0, 0); extraText = new RocketInfo(configuration); updateExtras(); + + figure.clearRelativeExtra(); figure.addRelativeExtra(extraCP); figure.addRelativeExtra(extraCG); figure.addAbsoluteExtra(extraText); + + + figure3d.clearRelativeExtra(); + //figure3d.addRelativeExtra(extraCP); + //figure3d.addRelativeExtra(extraCG); + figure3d.addAbsoluteExtra(extraText); + } - - + + /** * Updates the selection in the FigureParameters and repaints the figure. * Ignores the event itself. @@ -698,14 +811,16 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change figure.setSelection(null); return; } - + RocketComponent[] components = new RocketComponent[paths.length]; for (int i = 0; i < paths.length; i++) components[i] = (RocketComponent) paths[i].getLastPathComponent(); figure.setSelection(components); + + figure3d.setSelection(components); } - - + + /** * An Action that shows whether the figure type is the type @@ -714,29 +829,31 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change * @author Sampo Niskanen */ private class FigureTypeAction extends AbstractAction implements StateChangeListener { + private static final long serialVersionUID = 1L; private final int type; - + public FigureTypeAction(int type) { this.type = type; stateChanged(null); figure.addChangeListener(this); } - + @Override public void actionPerformed(ActionEvent e) { boolean state = (Boolean) getValue(Action.SELECTED_KEY); if (state == true) { // This view has been selected figure.setType(type); + go2D(); updateExtras(); } stateChanged(null); } - + @Override public void stateChanged(EventObject e) { - putValue(Action.SELECTED_KEY, figure.getType() == type); + putValue(Action.SELECTED_KEY, figure.getType() == type && !is3d); } } - + }