diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 0e7615bbc..852f5e178 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -57,7 +57,7 @@ RocketPanel.lbl.ViewType = View Type: ! BasicFrame BasicFrame.tab.Rocketdesign = Rocket design -BasicFrame.tab.Flightconfig = Configurations +BasicFrame.tab.Flightconfig = Motors & Configuration BasicFrame.tab.Flightsim = Flight simulations BasicFrame.title.Addnewcomp = Add new component BasicFrame.dlg.lbl1 = Design ' @@ -1866,6 +1866,7 @@ MotorConfigurationPanel.btn.removeMotor = Remove motor MotorConfigurationPanel.btn.selectMotor = Select motor MotorConfigurationPanel.btn.selectIgnition = Select ignition MotorConfigurationPanel.btn.resetIgnition = Reset ignition +MotorConfigurationPanel.lbl.nomotors = No motor mounts defined. Select one or more tubes from the list on the left as motor mounts. MotorConfigurationTableModel.table.ignition.default = Default ({0}) RecoveryConfigurationPanel.table.deployment.default = Default ({0}) diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 422f110f9..baa175065 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -28,7 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable, Visitable { +public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable { private static final Logger log = LoggerFactory.getLogger(RocketComponent.class); // Because of changes to Java 1.7.0-45's mechanism to construct DataFlavor objects (used in Drag and Drop) @@ -1736,9 +1736,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } ///////////// Visitor pattern implementation - @Override - public void accept(RocketComponentVisitor visitor) { + public R accept(RocketComponentVisitor visitor) { visitor.visit(this); + return visitor.getResult(); } //////////// Helper methods for subclasses diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponentVisitor.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponentVisitor.java index 0a28982e1..28419d6d0 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponentVisitor.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponentVisitor.java @@ -1,5 +1,19 @@ package net.sf.openrocket.rocketcomponent; -public interface RocketComponentVisitor extends Visitor { +public interface RocketComponentVisitor { + + /** + * The callback method. This method is the 2nd leg of the double-dispatch, having been invoked from a + * corresponding accept. + * + * @param visitable the instance of the Visitable (the target of what is being visiting) + */ + void visit(RocketComponent visitable); + + /** + * Return the final result + * @return + */ + R getResult(); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/Visitable.java b/core/src/net/sf/openrocket/rocketcomponent/Visitable.java deleted file mode 100644 index 5f46b7ff9..000000000 --- a/core/src/net/sf/openrocket/rocketcomponent/Visitable.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Visitable.java - */ -package net.sf.openrocket.rocketcomponent; - -/** - * This interface describes a portion of the Visitor pattern, using generics to assure type-safety. - * The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors, - * while these visitors are only able to visit the elements of that hierarchy. - * - * The key concept regarding the Visitor pattern is to realize that Java will only "discriminate" the type of an - * object being called, not the type of an object being passed. - * - * In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an - * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes "known" but the - * concrete type of the argument is still unknown. visit is then called on the parameter object, passing - * the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the - * type (and identity) of both objects are known. - * - * Specifically, this interface is to be implemented by every class in the RocketComponent hierarchy that - * can be visited AND which are sufficiently specialized from their super class. If they only provide - * constraints to their superclass (such as TubeCoupler), then the implementation of this interface at - * the superclass level is sufficient. - * - * Admittedly, the syntax is a bit contorted here, as it is necessarily self-referential for type-safety. - * - * The visitor type - * The visitable (the concrete class that implements this interface) - */ -public interface Visitable, T extends Visitable> { - - /** - * Any class in the hierarchy that allows itself to be visited will implement this method. The normal - * behavior is that the visitor will invoke this method of a Visitable, passing itself. The Visitable - * turns around calls the Visitor back. This idiom is also known as 'double-dispatching'. - * - * @param visitor the visitor that will be called back - */ - public void accept(V visitor); - -} diff --git a/core/src/net/sf/openrocket/rocketcomponent/Visitor.java b/core/src/net/sf/openrocket/rocketcomponent/Visitor.java deleted file mode 100644 index edb39f2b1..000000000 --- a/core/src/net/sf/openrocket/rocketcomponent/Visitor.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Visitor.java - */ -package net.sf.openrocket.rocketcomponent; - -/** - * This interface describes a portion of the Visitor pattern, using generics to assure type-safety. - * The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors, - * while these visitors are only able to visit the elements of that hierarchy. - * - * The key concept regarding the Visitor pattern is to realize that Java will only "discriminate" the type of an - * object being called, not the type of an object being passed. - * - * In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an - * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes "known" but the - * concrete type of the argument is still unknown. visit is then called on the parameter object, passing - * the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the - * type (and identity) of both objects are known. - * - * Specifically, this interface is to be implemented by every class in the RocketComponent hierarchy that - * can be visited AND which are sufficiently specialized from their super class. If they only provide - * constraints to their superclass (such as TubeCoupler), then the implementation of this interface at - * the superclass level is sufficient. - * - * Admittedly, the syntax is a bit contorted here, as it is necessarily self-referential for type-safety. - * - * The visitor type (the concrete class that implements this interface) - * The visitable - */ -public interface Visitor, T extends Visitable> { - - /** - * The callback method. This method is the 2nd leg of the double-dispatch, having been invoked from a - * corresponding accept. - * - * @param visitable the instance of the Visitable (the target of what is being visiting) - */ - void visit(T visitable); -} \ No newline at end of file diff --git a/core/src/net/sf/openrocket/rocketvisitors/BredthFirstRecusiveVisitor.java b/core/src/net/sf/openrocket/rocketvisitors/BredthFirstRecusiveVisitor.java index ad9f9681d..a3a32f109 100644 --- a/core/src/net/sf/openrocket/rocketvisitors/BredthFirstRecusiveVisitor.java +++ b/core/src/net/sf/openrocket/rocketvisitors/BredthFirstRecusiveVisitor.java @@ -3,24 +3,24 @@ package net.sf.openrocket.rocketvisitors; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.RocketComponentVisitor; -public abstract class BredthFirstRecusiveVisitor implements RocketComponentVisitor { - +public abstract class BredthFirstRecusiveVisitor implements RocketComponentVisitor { + @Override public final void visit(RocketComponent visitable) { - + this.doAction(visitable); - - for ( RocketComponent child: visitable.getChildren() ) { + + for (RocketComponent child : visitable.getChildren()) { this.doAction(child); } - - for ( RocketComponent child: visitable.getChildren() ) { + + for (RocketComponent child : visitable.getChildren()) { this.visit(child); } - - + + } - protected abstract void doAction( RocketComponent visitable ); - + protected abstract void doAction(RocketComponent visitable); + } diff --git a/core/src/net/sf/openrocket/rocketvisitors/CopyFlightConfigurationVisitor.java b/core/src/net/sf/openrocket/rocketvisitors/CopyFlightConfigurationVisitor.java index 2bc35e084..239570907 100644 --- a/core/src/net/sf/openrocket/rocketvisitors/CopyFlightConfigurationVisitor.java +++ b/core/src/net/sf/openrocket/rocketvisitors/CopyFlightConfigurationVisitor.java @@ -1,10 +1,10 @@ package net.sf.openrocket.rocketvisitors; -import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent; +import net.sf.openrocket.rocketcomponent.RocketComponent; -public class CopyFlightConfigurationVisitor extends DepthFirstRecusiveVisitor { - +public class CopyFlightConfigurationVisitor extends DepthFirstRecusiveVisitor { + private final String oldConfigId; private final String newConfigId; @@ -13,14 +13,18 @@ public class CopyFlightConfigurationVisitor extends DepthFirstRecusiveVisitor { this.oldConfigId = oldConfigId; this.newConfigId = newConfigId; } - + @Override public void doAction(RocketComponent visitable) { - if ( visitable instanceof FlightConfigurableComponent ) { - ((FlightConfigurableComponent)visitable).cloneFlightConfiguration(oldConfigId, newConfigId); + if (visitable instanceof FlightConfigurableComponent) { + ((FlightConfigurableComponent) visitable).cloneFlightConfiguration(oldConfigId, newConfigId); } } + @Override + public Void getResult() { + return null; + } } diff --git a/core/src/net/sf/openrocket/rocketvisitors/DepthFirstRecusiveVisitor.java b/core/src/net/sf/openrocket/rocketvisitors/DepthFirstRecusiveVisitor.java index f08d65080..29a0f6e51 100644 --- a/core/src/net/sf/openrocket/rocketvisitors/DepthFirstRecusiveVisitor.java +++ b/core/src/net/sf/openrocket/rocketvisitors/DepthFirstRecusiveVisitor.java @@ -3,19 +3,19 @@ package net.sf.openrocket.rocketvisitors; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.RocketComponentVisitor; -public abstract class DepthFirstRecusiveVisitor implements RocketComponentVisitor { - +public abstract class DepthFirstRecusiveVisitor implements RocketComponentVisitor { + @Override public final void visit(RocketComponent visitable) { - + this.doAction(visitable); - for ( RocketComponent child: visitable.getChildren() ) { + for (RocketComponent child : visitable.getChildren()) { this.visit(child); } } - protected abstract void doAction( RocketComponent visitable ); - + protected abstract void doAction(RocketComponent visitable); + } diff --git a/core/src/net/sf/openrocket/rocketvisitors/ListComponents.java b/core/src/net/sf/openrocket/rocketvisitors/ListComponents.java new file mode 100644 index 000000000..8a6f8f96b --- /dev/null +++ b/core/src/net/sf/openrocket/rocketvisitors/ListComponents.java @@ -0,0 +1,30 @@ +package net.sf.openrocket.rocketvisitors; + +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.rocketcomponent.RocketComponent; + +public class ListComponents extends DepthFirstRecusiveVisitor> { + + private final Class componentClazz; + protected List components = new ArrayList(); + + public ListComponents(Class componentClazz) { + super(); + this.componentClazz = componentClazz; + } + + @Override + public List getResult() { + return components; + } + + @Override + protected void doAction(RocketComponent visitable) { + if (componentClazz.isAssignableFrom(visitable.getClass())) { + components.add((T) visitable); + } + + } +} diff --git a/core/src/net/sf/openrocket/rocketvisitors/ListMotorMounts.java b/core/src/net/sf/openrocket/rocketvisitors/ListMotorMounts.java new file mode 100644 index 000000000..f86960e5c --- /dev/null +++ b/core/src/net/sf/openrocket/rocketvisitors/ListMotorMounts.java @@ -0,0 +1,18 @@ +package net.sf.openrocket.rocketvisitors; + +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.RocketComponent; + +public class ListMotorMounts extends ListComponents { + + public ListMotorMounts() { + super(RocketComponent.class); + } + + @Override + protected void doAction(RocketComponent visitable) { + if (visitable instanceof MotorMount && ((MotorMount) visitable).isMotorMount()) { + components.add(visitable); + } + } +} diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java index 28417a1a7..dd3e022d6 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java @@ -16,8 +16,11 @@ import net.sf.openrocket.gui.dialogs.flightconfiguration.RenameConfigDialog; import net.sf.openrocket.gui.main.BasicFrame; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent; +import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketvisitors.ListComponents; +import net.sf.openrocket.rocketvisitors.ListMotorMounts; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.StateChangeListener; @@ -38,7 +41,11 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe private final MotorConfigurationPanel motorConfigurationPanel; private final RecoveryConfigurationPanel recoveryConfigurationPanel; private final SeparationConfigurationPanel separationConfigurationPanel; - + + private final static int MOTOR_TAB_INDEX = 0; + private final static int RECOVERY_TAB_INDEX = 1; + private final static int SEPARATION_TAB_INDEX = 2; + @Override public void stateChanged(EventObject e) { updateButtonState(); @@ -178,9 +185,37 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe private void updateButtonState() { String currentId = rocket.getDefaultConfiguration().getFlightConfigurationID(); + // Enable the remove/rename/copy buttons only when a configuration is selected. removeConfButton.setEnabled(currentId != null); renameConfButton.setEnabled(currentId != null); copyConfButton.setEnabled(currentId != null); + + // Count the number of motor mounts + int motorMountCount = rocket.accept(new ListMotorMounts()).size(); + + // Count the number of recovery devices + int recoveryDeviceCount = rocket.accept(new ListComponents(RecoveryDevice.class)).size(); + + // Count the number of stages + int stageCount = rocket.getStageCount(); + + // Enable the new configuration button only when a motor mount is defined. + newConfButton.setEnabled(motorMountCount > 0); + + // Only enable the recovery tab if there is a motor mount and there is a recovery device + tabs.setEnabledAt(RECOVERY_TAB_INDEX, motorMountCount > 0 && recoveryDeviceCount > 0); + + // If the selected tab was the recovery tab, and there is no longer any recovery devices, + // switch to the motor tab. + if( recoveryDeviceCount == 0 && tabs.getSelectedIndex() == RECOVERY_TAB_INDEX ) { + tabs.setSelectedIndex(MOTOR_TAB_INDEX); + } + + tabs.setEnabledAt(SEPARATION_TAB_INDEX, motorMountCount > 0 && stageCount > 1); + + if ( stageCount ==1 && tabs.getSelectedIndex() == SEPARATION_TAB_INDEX ) { + tabs.setSelectedIndex(MOTOR_TAB_INDEX); + } } diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java index a012f034f..836746166 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java @@ -1,5 +1,6 @@ package net.sf.openrocket.gui.main.flightconfigpanel; +import java.awt.CardLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; @@ -49,6 +50,10 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton; + private final JPanel cards; + private final static String HELP_LABEL = "help"; + private final static String TABLE_LABEL = "table"; + private final MotorChooserDialog motorChooserDialog; protected FlightConfigurableTableModel configurationTableModel; @@ -57,7 +62,6 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel motorChooserDialog = new MotorChooserDialog(SwingUtilities.getWindowAncestor(flightConfigurationPanel)); - { //// Select motor mounts JPanel subpanel = new JPanel(new MigLayout("")); @@ -75,8 +79,16 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel this.add(subpanel, "split, w 200lp, growy"); } + cards = new JPanel(new CardLayout()); + this.add( cards ); + + JLabel helpText = new JLabel(trans.get("MotorConfigurationPanel.lbl.nomotors")); + cards.add(helpText, HELP_LABEL ); + JScrollPane scroll = new JScrollPane(table); - this.add(scroll, "grow, wrap"); + cards.add(scroll, TABLE_LABEL ); + + this.add(cards, "grow, wrap"); //// Select motor selectMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectMotor")); @@ -122,6 +134,14 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } + protected void showEmptyText() { + ((CardLayout)cards.getLayout()).show(cards, HELP_LABEL); + } + + protected void showContent() { + ((CardLayout)cards.getLayout()).show(cards, TABLE_LABEL); + } + @Override protected JTable initializeTable() { //// Motor selection table. @@ -165,12 +185,21 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } private void updateButtonState() { - String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); - MotorMount currentMount = getSelectedComponent(); - selectMotorButton.setEnabled(currentMount != null && currentID != null); - removeMotorButton.setEnabled(currentMount != null && currentID != null); - selectIgnitionButton.setEnabled(currentMount != null && currentID != null); - resetIgnitionButton.setEnabled(currentMount != null && currentID != null); + if( configurationTableModel.getColumnCount() > 1 ) { + showContent(); + String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); + MotorMount currentMount = getSelectedComponent(); + selectMotorButton.setEnabled(currentMount != null && currentID != null); + removeMotorButton.setEnabled(currentMount != null && currentID != null); + selectIgnitionButton.setEnabled(currentMount != null && currentID != null); + resetIgnitionButton.setEnabled(currentMount != null && currentID != null); + } else { + showEmptyText(); + selectMotorButton.setEnabled(false); + removeMotorButton.setEnabled(false); + selectIgnitionButton.setEnabled(false); + resetIgnitionButton.setEnabled(false); + } }