From 4b214bb1bdbcbe40d47c16c380715ed0b5095d64 Mon Sep 17 00:00:00 2001 From: plaa Date: Sun, 21 Jun 2009 18:24:33 +0000 Subject: [PATCH] Motor chooser search field and new edit motor config dialog git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@10 180e2498-e6e9-4542-8430-84ac67f01cd8 --- ChangeLog | 13 + ReleaseNotes | 7 + TODO | 127 +---- build.properties | 2 +- .../document/OpenRocketDocument.java | 48 +- .../sf/openrocket/file/OpenRocketLoader.java | 5 +- src/net/sf/openrocket/file/RocketLoader.java | 16 + .../gui/adaptors/MotorConfigurationModel.java | 228 +-------- .../configdialog/FreeformFinSetConfig.java | 8 +- .../gui/configdialog/InnerTubeConfig.java | 8 +- .../ComponentAnalysisDialog.java | 2 +- .../gui/{ => dialogs}/DetailDialog.java | 2 +- .../dialogs/EditMotorConfigurationDialog.java | 479 ++++++++++++++++++ .../gui/{main => dialogs}/LicenseDialog.java | 2 +- .../gui/{ => dialogs}/PreferencesDialog.java | 2 +- .../gui/dialogs/SwingWorkerDialog.java | 118 +++++ .../sf/openrocket/gui/main/BasicFrame.java | 81 ++- .../gui/main/MotorChooserDialog.java | 91 +++- .../gui/main/SimulationRunDialog.java | 2 +- .../rocketcomponent/ClusterConfiguration.java | 8 + .../rocketcomponent/FreeformFinSet.java | 21 +- .../IllegalFinPointException.java | 27 + .../sf/openrocket/rocketcomponent/Rocket.java | 30 +- .../util/ConcurrentProgressMonitor.java | 45 ++ .../ConcurrentProgressMonitorInputStream.java | 146 ++++++ src/net/sf/openrocket/util/Test.java | 19 +- 26 files changed, 1134 insertions(+), 403 deletions(-) rename src/net/sf/openrocket/gui/{ => dialogs}/ComponentAnalysisDialog.java (99%) rename src/net/sf/openrocket/gui/{ => dialogs}/DetailDialog.java (90%) create mode 100644 src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java rename src/net/sf/openrocket/gui/{main => dialogs}/LicenseDialog.java (98%) rename src/net/sf/openrocket/gui/{ => dialogs}/PreferencesDialog.java (99%) create mode 100644 src/net/sf/openrocket/gui/dialogs/SwingWorkerDialog.java create mode 100644 src/net/sf/openrocket/rocketcomponent/IllegalFinPointException.java create mode 100644 src/net/sf/openrocket/util/ConcurrentProgressMonitor.java create mode 100644 src/net/sf/openrocket/util/ConcurrentProgressMonitorInputStream.java diff --git a/ChangeLog b/ChangeLog index 9808b616..f54156a7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2009-06-20 Sampo Niskanen + + * New edit motor configurations dialog + * Changed FreeformFinSet to throw checked exceptions + +2009-06-11 Sampo Niskanen + + * Added search field to motor chooser dialog + +2009-06-09 Sampo Niskanen + + * Release 0.9.1 + 2009-06-08 Sampo Niskanen * Fixed loading of icons from JAR diff --git a/ReleaseNotes b/ReleaseNotes index c802e1ab..2703d971 100644 --- a/ReleaseNotes +++ b/ReleaseNotes @@ -1,4 +1,11 @@ +OpenRocket 0.9.2 (future): +--------------------------- + +- a new and enhanced "Edit motor configurations" dialog +- a search field in the motor selection dialog + + OpenRocket 0.9.1 (2009-06-09): ------------------------------- diff --git a/TODO b/TODO index a39c6647..e05d6f84 100644 --- a/TODO +++ b/TODO @@ -1,126 +1,35 @@ -GUI: +Feature roadmap for OpenRocket 1.0 -- Preferences dialog +Must-have: -BUGS: +- Exporting flight data +- Store custom materials +- Read more thrust curve formats / go through thrust curves and correct errors +- Create application icon and take into use +- Fix engine block icons +- Progress and error dialogs when reading/writing files -COMPUTATION: +Maybe: - -FILE/STORAGE: - - -OTHER: - -- web-sivut - - -DIPPA: - - - - -------------------- - -LATER: - -- Simulation delete/copy/paste hotkeys - (either component or simulation selected, but not both) -- Add BodyComponent at end of rocket when no component is selected -- Showing events in plot (maybe future) -- Search field in motor selection dialog +- Reading (writing) .RKT format +- Showing events in plots - Through-the-wall fins -- Store materials - -- Streamer CD estimation - -- exporting (maybe later) - - Make ThicknessRingComponent implement RadialParent and allow attaching components to a TubeCoupler +- Reading thrust curves from external directory +Postponed: +- Importing flight data -DONE: -- Automatic diameters of body components -- Copy/paste +Done: -18.4.: -- Esc, Ctrl-Z and Y etc. -- Look and feel - -19.4.: -- Nose cone and transition shoulders in GUI -- zoom, cut/copy/paste etc. icons - -23.4.: -- Figure or rocket not updating when using a new BasicFrame - -24.4.: -- File save and load -- Motor configuration editing (pre-alpha) -- Save simulations - -25.4.: -- Multi-stages simulation (pre-alpha) -- Make sure simulations end -- Mass and CG overrides (pre-alpha) -- General loader - -26.4.: -- Centering ring inner diameter automatics (pre-alpha) -- Landing simulation (pre-alpha ??) -- Parachute/Streamer editing in GUI (pre-alpha) -- Launch lug editing in GUI (pre-alpha) - -29.4.: -- Actual plotting done -- Refactored source code packages - -2.5.: -- Plotting (pre-alpha) -- Gravity model -- More units and specific custom units (angle, temperature, ...) -- Transition/Nose cone description text wrapping -- Fin set CP jumps at Mach 0.9 - -- Error dialogs for load/save/etc - -3.5.: -- More materials (pre-alpha) -- File opening from command line - -9.5.: -- Rocket configuration dialog -- Warnings in poor conditions (transition supersonic) -- New or old fin-body interference? -- poista tiedot laminaarisesta vastuksesta -- vertailuosio - -11.5.: -- Better default values for components -- Component analysis dialog show zero total mass and CG -- Compression support in save -- Simulation storage options - -12.5.: -- Load simulations -- Update file version to 1.0 - -13.5.: -- statistiikat softasta - -17.5.: -- jonkin verran TODOja -- conclusion -- viitteet -- Draw the component icons -- splashscreen +- Search field in motor selection dialog +- Motor selection/editing from Edit configurations dialog +- Change FreeformFinSet to throw checked exceptions -18.5.: -- About dialog + version number diff --git a/build.properties b/build.properties index 3a32f338..9fd21c5a 100644 --- a/build.properties +++ b/build.properties @@ -1,6 +1,6 @@ # The OpenRocket build version -build.version=0.9.1 +build.version=0.9.2pre # The source of the package. When building a package for a specific # distribution (Debian, Fedora etc.), this should be changed appropriately! diff --git a/src/net/sf/openrocket/document/OpenRocketDocument.java b/src/net/sf/openrocket/document/OpenRocketDocument.java index 24c144cd..164f76da 100644 --- a/src/net/sf/openrocket/document/OpenRocketDocument.java +++ b/src/net/sf/openrocket/document/OpenRocketDocument.java @@ -45,6 +45,7 @@ public class OpenRocketDocument implements ComponentChangeListener { private LinkedList undoDescription = new LinkedList(); private String nextDescription = null; + private String storedDescription = null; private File file = null; @@ -70,9 +71,7 @@ public class OpenRocketDocument implements ComponentChangeListener { this.configuration = configuration; this.rocket = configuration.getRocket(); - undoHistory.add(rocket.copy()); - undoDescription.add(null); - undoPosition = 0; + clearUndo(); undoAction = new UndoRedoAction(UndoRedoAction.UNDO); redoAction = new UndoRedoAction(UndoRedoAction.REDO); @@ -234,6 +233,31 @@ public class OpenRocketDocument implements ComponentChangeListener { } + /** + * Start a time-limited undoable operation. After the operation {@link #stopUndo()} + * must be called, which will restore the previous undo description into effect. + * Only one level of start-stop undo descriptions is supported, i.e. start-stop + * undo cannot be nested, and no other undo operations may be called between + * the start and stop calls. + * + * @param description Description of the following undoable operations. + */ + public void startUndo(String description) { + storedDescription = nextDescription; + addUndoPosition(description); + } + + /** + * End the previous time-limited undoable operation. This must be called after + * {@link #startUndo(String)} has been called before any other undo operations are + * performed. + */ + public void stopUndo() { + addUndoPosition(storedDescription); + storedDescription = null; + } + + public Action getUndoAction() { return undoAction; } @@ -244,6 +268,24 @@ public class OpenRocketDocument implements ComponentChangeListener { } + /** + * Clear the undo history. + */ + public void clearUndo() { + undoHistory.clear(); + undoDescription.clear(); + + undoHistory.add(rocket.copy()); + undoDescription.add(null); + undoPosition = 0; + + if (undoAction != null) + undoAction.setAllValues(); + if (redoAction != null) + redoAction.setAllValues(); + } + + @Override public void componentChanged(ComponentChangeEvent e) { diff --git a/src/net/sf/openrocket/file/OpenRocketLoader.java b/src/net/sf/openrocket/file/OpenRocketLoader.java index e1978a7f..81edde84 100644 --- a/src/net/sf/openrocket/file/OpenRocketLoader.java +++ b/src/net/sf/openrocket/file/OpenRocketLoader.java @@ -29,6 +29,7 @@ import net.sf.openrocket.rocketcomponent.EngineBlock; import net.sf.openrocket.rocketcomponent.ExternalComponent; import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.IllegalFinPointException; import net.sf.openrocket.rocketcomponent.InnerTube; import net.sf.openrocket.rocketcomponent.InternalComponent; import net.sf.openrocket.rocketcomponent.LaunchLug; @@ -137,6 +138,8 @@ public class OpenRocketLoader extends RocketLoader { doc.getDefaultStorageOptions().setSimulationTimeSkip(timeSkip); doc.getDefaultStorageOptions().setCompressionEnabled(false); // Set by caller if compressed doc.getDefaultStorageOptions().setExplicitlySet(false); + + doc.clearUndo(); return doc; } @@ -977,7 +980,7 @@ class FinSetPointHandler extends ElementHandler { String content, WarningSet warnings) { try { finset.setPoints(coordinates.toArray(new Coordinate[0])); - } catch (IllegalArgumentException e) { + } catch (IllegalFinPointException e) { warnings.add(Warning.fromString("Freeform fin set point definitions illegal, ignoring.")); } } diff --git a/src/net/sf/openrocket/file/RocketLoader.java b/src/net/sf/openrocket/file/RocketLoader.java index 13f888ca..0e8e051e 100644 --- a/src/net/sf/openrocket/file/RocketLoader.java +++ b/src/net/sf/openrocket/file/RocketLoader.java @@ -1,5 +1,6 @@ package net.sf.openrocket.file; +import java.awt.Component; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; @@ -7,6 +8,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import javax.swing.ProgressMonitorInputStream; + import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; @@ -14,6 +17,19 @@ import net.sf.openrocket.document.OpenRocketDocument; public abstract class RocketLoader { protected final WarningSet warnings = new WarningSet(); + + public final OpenRocketDocument load(File source, Component parent) + throws RocketLoadException { + warnings.clear(); + + try { + return load(new BufferedInputStream(new ProgressMonitorInputStream( + parent, "Loading " + source.getName(), + new FileInputStream(source)))); + } catch (FileNotFoundException e) { + throw new RocketLoadException("File not found: " + source); + } + } /** * Loads a rocket from the specified File object. diff --git a/src/net/sf/openrocket/gui/adaptors/MotorConfigurationModel.java b/src/net/sf/openrocket/gui/adaptors/MotorConfigurationModel.java index b24d0c94..92e84739 100644 --- a/src/net/sf/openrocket/gui/adaptors/MotorConfigurationModel.java +++ b/src/net/sf/openrocket/gui/adaptors/MotorConfigurationModel.java @@ -1,44 +1,22 @@ package net.sf.openrocket.gui.adaptors; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import javax.swing.ComboBoxModel; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.JTextField; -import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.gui.TextFieldListener; -import net.sf.openrocket.gui.components.ResizeLabel; +import net.sf.openrocket.gui.dialogs.EditMotorConfigurationDialog; +import net.sf.openrocket.gui.main.BasicFrame; import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.Configuration; -import net.sf.openrocket.rocketcomponent.Motor; -import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.util.GUIUtil; public class MotorConfigurationModel implements ComboBoxModel, ChangeListener { @@ -91,13 +69,8 @@ public class MotorConfigurationModel implements ComboBoxModel, ChangeListener { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - EditConfigurationDialog dialog = new EditConfigurationDialog(); - dialog.setVisible(true); - - if (dialog.isRowSelected()) { - rocket.getDefaultConfiguration().setMotorConfigurationID( - dialog.getSelectedID()); - } + new EditMotorConfigurationDialog(rocket, BasicFrame.findFrame(rocket)) + .setVisible(true); } }); @@ -185,198 +158,5 @@ public class MotorConfigurationModel implements ComboBoxModel, ChangeListener { } } - - private class EditConfigurationDialog extends JDialog { - private final ColumnTableModel tableModel; - private String[] ids; - int selection = -1; - - private final JButton addButton; - private final JButton removeButton; - private final JTextField nameField; - - private final JTable table; - - - public boolean isRowSelected() { - return selection >= 0; - } - - public String getSelectedID() { - if (selection >= 0) - return ids[selection]; - return null; - } - - - public EditConfigurationDialog() { - super((JFrame)null, "Edit configurations", true); - - ids = rocket.getMotorConfigurationIDs(); - - // Create columns - ArrayList columnList = new ArrayList(); - columnList.add(new Column("Name") { - @Override - public Object getValueAt(int row) { - return rocket.getMotorConfigurationNameOrDescription(ids[row]); - } - }); - - // Create columns from the motor mounts - Iterator iterator = rocket.deepIterator(); - while (iterator.hasNext()) { - RocketComponent c = iterator.next(); - if (!(c instanceof MotorMount)) - continue; - - final MotorMount mount = (MotorMount)c; - if (!mount.isMotorMount()) - continue; - - Column col = new Column(c.getName()) { - @Override - public Object getValueAt(int row) { - Motor motor = mount.getMotor(ids[row]); - if (motor == null) - return ""; - return motor.getDesignation(mount.getMotorDelay(ids[row])); - } - }; - columnList.add(col); - } - tableModel = new ColumnTableModel(columnList.toArray(new Column[0])) { - @Override - public int getRowCount() { - return ids.length; - } - }; - - - - // Create the panel - JPanel panel = new JPanel(new MigLayout("fill","[shrink][grow]")); - - - panel.add(new JLabel("Configuration name:"), "gapright para"); - nameField = new JTextField(); - new TextFieldListener() { - @Override - public void setText(String text) { - if (selection < 0 || ids[selection] == null) - return; - rocket.setMotorConfigurationName(ids[selection], text); - fireChange(); - } - }.listenTo(nameField); - panel.add(nameField, "growx, wrap"); - - panel.add(new ResizeLabel("Leave empty for default description", -2), - "skip, growx, wrap para"); - - - table = new JTable(tableModel); - table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - updateSelection(); - } - }); - - // Mouse listener to act on double-clicks - table.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { - EditConfigurationDialog.this.dispose(); - } - } - }); - - - - JScrollPane scrollpane = new JScrollPane(table); - panel.add(scrollpane, "spanx, height 150lp, width 400lp, grow, wrap"); - - - addButton = new JButton("New"); - addButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String id = rocket.newMotorConfigurationID(); - ids = rocket.getMotorConfigurationIDs(); - tableModel.fireTableDataChanged(); - int sel; - for (sel=0; sel < ids.length; sel++) { - if (id.equals(ids[sel])) - break; - } - table.getSelectionModel().addSelectionInterval(sel, sel); - } - }); - panel.add(addButton, "growx, spanx, split 2"); - - removeButton = new JButton("Remove"); - removeButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - int sel = table.getSelectedRow(); - if (sel < 0 || sel >= ids.length || ids[sel] == null) - return; - rocket.removeMotorConfigurationID(ids[sel]); - ids = rocket.getMotorConfigurationIDs(); - tableModel.fireTableDataChanged(); - if (sel >= ids.length) - sel--; - table.getSelectionModel().addSelectionInterval(sel, sel); - } - }); - panel.add(removeButton, "growx, wrap para"); - - - JButton close = new JButton("Close"); - close.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - EditConfigurationDialog.this.dispose(); - } - }); - panel.add(close, "spanx, alignx 100%"); - - this.getRootPane().setDefaultButton(close); - - - this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); - GUIUtil.installEscapeCloseOperation(this); - this.setLocationByPlatform(true); - - updateSelection(); - - this.add(panel); - this.validate(); - this.pack(); - } - - private void fireChange() { - int sel = table.getSelectedRow(); - tableModel.fireTableDataChanged(); - table.getSelectionModel().addSelectionInterval(sel, sel); - } - - private void updateSelection() { - selection = table.getSelectedRow(); - if (selection < 0 || ids[selection] == null) { - removeButton.setEnabled(false); - nameField.setEnabled(false); - nameField.setText(""); - } else { - removeButton.setEnabled(true); - nameField.setEnabled(true); - nameField.setText(rocket.getMotorConfigurationName(ids[selection])); - } - } - } - } diff --git a/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index 889d4adf..edda7dc4 100644 --- a/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -30,6 +30,7 @@ import net.sf.openrocket.gui.scalefigure.ScaleSelector; import net.sf.openrocket.material.Material; import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.IllegalFinPointException; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.Coordinate; @@ -275,7 +276,7 @@ public class FreeformFinSetConfig extends FinSetConfig { finset.addPoint(index); try { finset.setPoint(index, point.x, point.y); - } catch (IllegalArgumentException ignore) { } + } catch (IllegalFinPointException ignore) { } dragIndex = index; return; @@ -299,7 +300,7 @@ public class FreeformFinSetConfig extends FinSetConfig { try { finset.setPoint(dragIndex, point.x, point.y); - } catch (IllegalArgumentException ignore) { + } catch (IllegalFinPointException ignore) { System.out.println("IAE:"+ignore); } } @@ -328,7 +329,7 @@ public class FreeformFinSetConfig extends FinSetConfig { try { finset.removePoint(index); - } catch (IllegalArgumentException ignore) { + } catch (IllegalFinPointException ignore) { } } @@ -462,6 +463,7 @@ public class FreeformFinSetConfig extends FinSetConfig { finset.setPoint(rowIndex, c.x, c.y); } catch (NumberFormatException ignore) { + } catch (IllegalFinPointException ignore) { } } diff --git a/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java b/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java index b1319b7b..6f568194 100644 --- a/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java @@ -50,12 +50,12 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig { tab = positionTab(); tabbedPane.insertTab("Radial position", null, tab, "Radial position", 1); - tab = clusterTab(); - tabbedPane.insertTab("Cluster", null, tab, "Cluster configuration", 2); - tab = new MotorConfig((MotorMount)c); - tabbedPane.insertTab("Motor", null, tab, "Motor mount configuration", 3); + tabbedPane.insertTab("Motor", null, tab, "Motor mount configuration", 2); + tab = clusterTab(); + tabbedPane.insertTab("Cluster", null, tab, "Cluster configuration", 3); + tabbedPane.setSelectedIndex(0); } diff --git a/src/net/sf/openrocket/gui/ComponentAnalysisDialog.java b/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java similarity index 99% rename from src/net/sf/openrocket/gui/ComponentAnalysisDialog.java rename to src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java index 7fa39a7a..3c900f25 100644 --- a/src/net/sf/openrocket/gui/ComponentAnalysisDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java @@ -1,4 +1,4 @@ -package net.sf.openrocket.gui; +package net.sf.openrocket.gui.dialogs; import static net.sf.openrocket.unit.Unit.NOUNIT2; diff --git a/src/net/sf/openrocket/gui/DetailDialog.java b/src/net/sf/openrocket/gui/dialogs/DetailDialog.java similarity index 90% rename from src/net/sf/openrocket/gui/DetailDialog.java rename to src/net/sf/openrocket/gui/dialogs/DetailDialog.java index f0c08308..05a19950 100644 --- a/src/net/sf/openrocket/gui/DetailDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/DetailDialog.java @@ -1,4 +1,4 @@ -package net.sf.openrocket.gui; +package net.sf.openrocket.gui.dialogs; import java.awt.Component; diff --git a/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java new file mode 100644 index 00000000..ffa3a096 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java @@ -0,0 +1,479 @@ +package net.sf.openrocket.gui.dialogs; + +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.Iterator; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.gui.main.BasicFrame; +import net.sf.openrocket.gui.main.MotorChooserDialog; +import net.sf.openrocket.rocketcomponent.Motor; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.GUIUtil; + +public class EditMotorConfigurationDialog extends JDialog { + + private final Rocket rocket; + + private final MotorMount[] mounts; + + private final JTable configurationTable; + private final MotorConfigurationTableModel configurationTableModel; + + + private final JButton newConfButton, removeConfButton; + private final JButton selectMotorButton, removeMotorButton; + private final JTextField configurationNameField; + + + private String currentID = null; + private MotorMount currentMount = null; + + public EditMotorConfigurationDialog(final Rocket rocket, Window parent) { + super(parent, "Edit motor configurations"); + + if (parent != null) + this.setModalityType(ModalityType.DOCUMENT_MODAL); + else + this.setModalityType(ModalityType.APPLICATION_MODAL); + + this.rocket = rocket; + + ArrayList mountList = new ArrayList(); + Iterator iterator = rocket.deepIterator(); + while (iterator.hasNext()) { + RocketComponent c = iterator.next(); + if (c instanceof MotorMount) { + mountList.add((MotorMount)c); + } + } + mounts = mountList.toArray(new MotorMount[0]); + + + + JPanel panel = new JPanel(new MigLayout("fill, flowy")); + + + //// Motor mount selection + + JLabel label = new JLabel("Motor mounts:"); + panel.add(label, "gapbottom para"); + + label = new JLabel("Select which components function as motor mounts:"); + panel.add(label,"ay 100%, w 1px, growx"); + + + JTable table = new JTable(new MotorMountTableModel()); + table.setTableHeader(null); + table.setShowVerticalLines(false); + table.setRowSelectionAllowed(false); + table.setColumnSelectionAllowed(false); + + TableColumnModel columnModel = table.getColumnModel(); + TableColumn col0 = columnModel.getColumn(0); + int w = table.getRowHeight() + 2; + col0.setMinWidth(w); + col0.setPreferredWidth(w); + col0.setMaxWidth(w); + + JScrollPane scroll = new JScrollPane(table); + panel.add(scroll, "w 200lp, h 150lp, grow, wrap 20lp"); + + + + + + //// Motor selection + + label = new JLabel("Motor configurations:"); + panel.add(label, "spanx, gapbottom para"); + + + label = new JLabel("Configuration name:"); + String tip = "Leave name empty for default."; + label.setToolTipText(tip); + panel.add(label, ""); + + configurationNameField = new JTextField(10); + configurationNameField.setToolTipText(tip); + configurationNameField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + private void update() { + String text = configurationNameField.getText(); + if (currentID != null) { + rocket.setMotorConfigurationName(currentID, text); + int row = configurationTable.getSelectedRow(); + configurationTableModel.fireTableCellUpdated(row, 0); + updateEnabled(); + } + } + }); + panel.add(configurationNameField, "cell 2 1, gapright para"); + + newConfButton = new JButton("New configuration"); + newConfButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String id = rocket.newMotorConfigurationID(); + rocket.getDefaultConfiguration().setMotorConfigurationID(id); + configurationTableModel.fireTableDataChanged(); + updateEnabled(); + } + }); + panel.add(newConfButton, "cell 3 1"); + + removeConfButton = new JButton("Remove configuration"); + removeConfButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (currentID == null) + return; + rocket.removeMotorConfigurationID(currentID); + rocket.getDefaultConfiguration().setMotorConfigurationID(null); + configurationTableModel.fireTableDataChanged(); + updateEnabled(); + } + }); + panel.add(removeConfButton, "cell 4 1"); + + + + + configurationTableModel = new MotorConfigurationTableModel(); + configurationTable = new JTable(configurationTableModel); + configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + configurationTable.setCellSelectionEnabled(true); + + configurationTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + + if (e.getClickCount() == 1) { + + // Single click updates selection + updateEnabled(); + + } else if (e.getClickCount() == 2) { + + // Double-click edits motor + selectMotor(); + + } + + } + }); + + + scroll = new JScrollPane(configurationTable); + panel.add(scroll, "cell 1 2, spanx, w 500lp, h 150lp, grow"); + + + selectMotorButton = new JButton("Select motor"); + selectMotorButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selectMotor(); + } + }); + panel.add(selectMotorButton, "spanx, flowx, split 2, ax 50%"); + + + removeMotorButton = new JButton("Remove motor"); + removeMotorButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + removeMotor(); + } + }); + panel.add(removeMotorButton, "ax 50%"); + + + + //// Close button + + JButton close = new JButton("Close"); + close.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + EditMotorConfigurationDialog.this.dispose(); + } + }); + panel.add(close, "spanx, right"); + + this.add(panel); + this.validate(); + this.pack(); + + updateEnabled(); + + GUIUtil.installEscapeCloseOperation(this); + GUIUtil.setDefaultButton(close); + + // Undo description + final OpenRocketDocument document = BasicFrame.findDocument(rocket); + if (document != null) { + document.startUndo("Edit motor configurations"); + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + document.stopUndo(); + } + }); + } + } + + + + + private void updateEnabled() { + int column = configurationTable.getSelectedColumn(); + int row = configurationTable.getSelectedRow(); + + if (column < 0 || row < 0) { + currentID = null; + currentMount = null; + } else { + + currentID = findID(row); + if (column == 0) { + currentMount = null; + } else { + currentMount = findMount(column); + } + rocket.getDefaultConfiguration().setMotorConfigurationID(currentID); + + } + + configurationNameField.setEnabled(currentID != null); + if (currentID == null) { + configurationNameField.setText(""); + } else { + configurationNameField.setText(rocket.getMotorConfigurationName(currentID)); + } + removeConfButton.setEnabled(currentID != null); + selectMotorButton.setEnabled(currentMount != null && currentID != null); + removeMotorButton.setEnabled(currentMount != null && currentID != null); + } + + + + + private void selectMotor() { + if (currentID == null || currentMount == null) + return; + + MotorChooserDialog dialog = new MotorChooserDialog(currentMount.getMotor(currentID), + currentMount.getMotorDelay(currentID), currentMount.getMotorMountDiameter()); + dialog.setVisible(true); + Motor m = dialog.getSelectedMotor(); + double d = dialog.getSelectedDelay(); + + if (m != null) { + currentMount.setMotor(currentID, m); + currentMount.setMotorDelay(currentID, d); + } + + int row = configurationTable.getSelectedRow(); + configurationTableModel.fireTableRowsUpdated(row, row); + updateEnabled(); + } + + + private void removeMotor() { + if (currentID == null || currentMount == null) + return; + + currentMount.setMotor(currentID, null); + + int row = configurationTable.getSelectedRow(); + configurationTableModel.fireTableRowsUpdated(row, row); + updateEnabled(); + } + + + private String findID(int row) { + return rocket.getMotorConfigurationIDs()[row+1]; + } + + + private MotorMount findMount(int column) { + MotorMount mount = null; + + int count = column; + for (MotorMount m: mounts) { + if (m.isMotorMount()) + count--; + if (count <= 0) { + mount = m; + break; + } + } + + if (mount == null) { + throw new IndexOutOfBoundsException("motor mount not found, column="+column); + } + return mount; + } + + + /** + * The table model for selecting whether components are motor mounts or not. + */ + private class MotorMountTableModel extends AbstractTableModel { + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public int getRowCount() { + return mounts.length; + } + + @Override + public Class getColumnClass(int column) { + switch (column) { + case 0: + return Boolean.class; + + case 1: + return String.class; + + default: + throw new IndexOutOfBoundsException("column="+column); + } + } + + @Override + public Object getValueAt(int row, int column) { + switch (column) { + case 0: + return new Boolean(mounts[row].isMotorMount()); + + case 1: + return mounts[row].toString(); + + default: + throw new IndexOutOfBoundsException("column="+column); + } + } + + @Override + public boolean isCellEditable(int row, int column) { + return column == 0; + } + + @Override + public void setValueAt(Object value, int row, int column) { + if (column != 0 || !(value instanceof Boolean)) { + throw new IllegalArgumentException("column="+column+", value="+value); + } + + mounts[row].setMotorMount((Boolean)value); + configurationTableModel.fireTableStructureChanged(); + updateEnabled(); + } + } + + + + /** + * The table model for selecting and editing the motor configurations. + */ + private class MotorConfigurationTableModel extends AbstractTableModel { + + @Override + public int getColumnCount() { + int count = 1; + for (MotorMount m: mounts) { + if (m.isMotorMount()) + count++; + } + return count; + } + + @Override + public int getRowCount() { + return rocket.getMotorConfigurationIDs().length-1; + } + + @Override + public Object getValueAt(int row, int column) { + + String id = findID(row); + + if (column == 0) { + return rocket.getMotorConfigurationNameOrDescription(id); + } + + MotorMount mount = findMount(column); + Motor motor = mount.getMotor(id); + if (motor == null) + return "None"; + + String str = motor.getDesignation(mount.getMotorDelay(id)); + int count = mount.getMotorCount(); + if (count > 1) { + str = "" + count + "\u00d7 " + str; + } + return str; + } + + + @Override + public String getColumnName(int column) { + if (column == 0) { + return "Configuration name"; + } + + MotorMount mount = findMount(column); + String name = mount.toString(); + int count = mount.getMotorCount(); + if (count > 1) { + name = name + " (\u00d7" + count + ")"; + } + return name; + } + + + } + + + + +} diff --git a/src/net/sf/openrocket/gui/main/LicenseDialog.java b/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java similarity index 98% rename from src/net/sf/openrocket/gui/main/LicenseDialog.java rename to src/net/sf/openrocket/gui/dialogs/LicenseDialog.java index 6df25692..bc718d6d 100644 --- a/src/net/sf/openrocket/gui/main/LicenseDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java @@ -1,4 +1,4 @@ -package net.sf.openrocket.gui.main; +package net.sf.openrocket.gui.dialogs; import java.awt.Font; import java.awt.event.ActionEvent; diff --git a/src/net/sf/openrocket/gui/PreferencesDialog.java b/src/net/sf/openrocket/gui/dialogs/PreferencesDialog.java similarity index 99% rename from src/net/sf/openrocket/gui/PreferencesDialog.java rename to src/net/sf/openrocket/gui/dialogs/PreferencesDialog.java index 2cdb4c45..cfb6f882 100644 --- a/src/net/sf/openrocket/gui/PreferencesDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/PreferencesDialog.java @@ -1,4 +1,4 @@ -package net.sf.openrocket.gui; +package net.sf.openrocket.gui.dialogs; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; diff --git a/src/net/sf/openrocket/gui/dialogs/SwingWorkerDialog.java b/src/net/sf/openrocket/gui/dialogs/SwingWorkerDialog.java new file mode 100644 index 00000000..6670a931 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/SwingWorkerDialog.java @@ -0,0 +1,118 @@ +package net.sf.openrocket.gui.dialogs; + +import java.awt.Window; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.SwingWorker; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.util.Pair; + + +/** + * A modal dialog that runs specific SwingWorkers and waits until they complete. + * A message and progress bar is provided and a cancel button. If the cancel button + * is pressed, the currently running worker is interrupted and the later workers are not + * executed. + * + * @author Sampo Niskanen + */ +public class SwingWorkerDialog extends JDialog implements PropertyChangeListener { + + private final JLabel label; + private final JProgressBar progressBar; + + private int position; + private Pair>[] workers; + + private boolean cancelled = false; + + public SwingWorkerDialog(Window parent, String title) { + super(parent, title, ModalityType.APPLICATION_MODAL); + + JPanel panel = new JPanel(new MigLayout("fill")); + + label = new JLabel(""); + panel.add(label, "wrap para"); + + progressBar = new JProgressBar(); + panel.add(progressBar, "growx, wrap para"); + + JButton cancel = new JButton("Cancel"); + // TODO: CRITICAL: Implement cancel + panel.add(cancel, "right"); + + this.add(panel); + this.pack(); + this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + } + + + /** + * Execute the provided workers one after another. When this call returns + * the workers will all have completed. + * + * @param workers pairs of description texts and workers to run. + */ + public void runWorkers(Pair> ... workers) { + if (workers.length == 0) { + throw new IllegalArgumentException("No workers provided."); + } + + this.workers = workers; + position = -1; + + for (int i=0; i < workers.length; i++) { + workers[i].getV().addPropertyChangeListener(this); + } + + nextWorker(); + this.setVisible(true); // Waits until all have ended + } + + + + /** + * Starts the execution of the next worker in the queue. If the last worker + * has completed or the operation has been cancelled, closes the dialog. + */ + private void nextWorker() { + if ((position >= workers.length-1) || cancelled) { + close(); + return; + } + + position++; + + label.setText(workers[position].getU()); + workers[position].getV().execute(); + } + + + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (workers[position].getV().getState() == SwingWorker.StateValue.DONE) { + nextWorker(); + } + + int value = workers[position].getV().getProgress(); + value = (value + position*100 ) / workers.length; + progressBar.setValue(value); + } + + + + private void close() { + for (int i=0; i < workers.length; i++) { + workers[i].getV().removePropertyChangeListener(this); + } + this.setVisible(false); + } +} diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index 3f12d259..e7cb2ca5 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -1,5 +1,6 @@ package net.sf.openrocket.gui.main; +import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Toolkit; @@ -14,6 +15,7 @@ import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; @@ -38,6 +40,7 @@ import javax.swing.ListSelectionModel; import javax.swing.LookAndFeel; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; import javax.swing.ToolTipManager; import javax.swing.UIManager; import javax.swing.border.TitledBorder; @@ -56,17 +59,20 @@ import net.sf.openrocket.file.OpenRocketSaver; import net.sf.openrocket.file.RocketLoadException; import net.sf.openrocket.file.RocketLoader; import net.sf.openrocket.file.RocketSaver; -import net.sf.openrocket.gui.ComponentAnalysisDialog; -import net.sf.openrocket.gui.PreferencesDialog; import net.sf.openrocket.gui.StorageOptionChooser; import net.sf.openrocket.gui.configdialog.ComponentConfigDialog; import net.sf.openrocket.gui.dialogs.BugDialog; +import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog; +import net.sf.openrocket.gui.dialogs.LicenseDialog; +import net.sf.openrocket.gui.dialogs.PreferencesDialog; import net.sf.openrocket.gui.scalefigure.RocketPanel; import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.Stage; +import net.sf.openrocket.util.ConcurrentProgressMonitor; +import net.sf.openrocket.util.ConcurrentProgressMonitorInputStream; import net.sf.openrocket.util.Icons; import net.sf.openrocket.util.Prefs; @@ -588,7 +594,7 @@ public class BasicFrame extends JFrame { for (File file: files) { System.out.println("Opening file: " + file); - if (open(file)) { + if (open(file, this)) { opened = true; } } @@ -604,13 +610,16 @@ public class BasicFrame extends JFrame { * Open the specified file in a new design frame. If an error occurs, an error dialog * is shown and false is returned. * - * @param file the file to open. - * @return whether the file was successfully loaded and opened. + * @param file the file to open. + * @param parent the parent component for which a progress dialog is opened. + * @return whether the file was successfully loaded and opened. */ - private static boolean open(File file) { + private static boolean open(File file, Component parent) { OpenRocketDocument doc = null; + + try { - doc = ROCKET_LOADER.load(file); + doc = ROCKET_LOADER.load(file, parent); } catch (RocketLoadException e) { JOptionPane.showMessageDialog(null, "Unable to open file '" + file.getName() +"': " + e.getMessage(), "Error opening file", JOptionPane.ERROR_MESSAGE); @@ -643,6 +652,33 @@ public class BasicFrame extends JFrame { + private static class OpenWorker extends SwingWorker { + private final File file; + private final Component parent; + private ConcurrentProgressMonitor monitor = null; + + public OpenWorker(File file, Component parent) { + this.file = file; + this.parent = parent; + } + + @Override + protected OpenRocketDocument doInBackground() throws Exception { + ConcurrentProgressMonitorInputStream is = + new ConcurrentProgressMonitorInputStream(parent, + "Loading " + file.getName(), new FileInputStream(file)); + monitor = is.getProgressMonitor(); + return ROCKET_LOADER.load(is); + } + + public ConcurrentProgressMonitor getMonitor() { + return monitor; + } + } + + + + private boolean saveAction() { File file = document.getFile(); if (file==null) { @@ -804,6 +840,35 @@ public class BasicFrame extends JFrame { + /** + * Find a currently open BasicFrame containing the specified rocket. This method + * can be used to map a Rocket to a BasicFrame from GUI methods. + * + * @param rocket the Rocket. + * @return the corresponding BasicFrame, or null if none found. + */ + public static BasicFrame findFrame(Rocket rocket) { + for (BasicFrame f: frames) { + if (f.rocket == rocket) + return f; + } + return null; + } + + /** + * Find a currently open document by the rocket object. This method can be used + * to map a Rocket to OpenRocketDocument from GUI methods. + * + * @param rocket the Rocket. + * @return the corresponding OpenRocketDocument, or null if not found. + */ + public static OpenRocketDocument findDocument(Rocket rocket) { + for (BasicFrame f: frames) { + if (f.rocket == rocket) + return f.document; + } + return null; + } @@ -861,7 +926,7 @@ public class BasicFrame extends JFrame { // Check command-line for files boolean opened = false; for (String file: args) { - if (open(new File(file))) { + if (open(new File(file), null)) { opened = true; } } diff --git a/src/net/sf/openrocket/gui/main/MotorChooserDialog.java b/src/net/sf/openrocket/gui/main/MotorChooserDialog.java index 04a78979..63c8535e 100644 --- a/src/net/sf/openrocket/gui/main/MotorChooserDialog.java +++ b/src/net/sf/openrocket/gui/main/MotorChooserDialog.java @@ -9,6 +9,7 @@ import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.text.Collator; +import java.util.ArrayList; import java.util.Comparator; import java.util.Locale; import java.util.regex.Matcher; @@ -22,8 +23,11 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; +import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.RowFilter; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; @@ -49,7 +53,9 @@ public class MotorChooserDialog extends JDialog { "Show motors with diameter equal to that of the motor mount" }; private static final int SHOW_MAX = 2; - + + private final JTextField searchField; + private String[] searchTerms = new String[0]; private final double diameter; @@ -81,16 +87,16 @@ public class MotorChooserDialog extends JDialog { this.selectedDelay = delay; this.diameter = diameter; - JPanel panel = new JPanel(new MigLayout("fill")); + JPanel panel = new JPanel(new MigLayout("fill", "[grow][]")); // Label JLabel label = new JLabel("Select a rocket motor:"); label.setFont(label.getFont().deriveFont(Font.BOLD)); - panel.add(label,"split 2, growx"); + panel.add(label,"growx"); label = new JLabel("Motor mount diameter: " + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter)); - panel.add(label,"alignx 100%, wrap paragraph"); + panel.add(label,"gapleft para, wrap paragraph"); // Diameter selection @@ -104,17 +110,14 @@ public class MotorChooserDialog extends JDialog { sel = SHOW_ALL; switch (sel) { case SHOW_ALL: - System.out.println("Setting filter: all"); sorter.setRowFilter(new MotorRowFilterAll()); break; case SHOW_SMALLER: - System.out.println("Setting filter: smaller"); sorter.setRowFilter(new MotorRowFilterSmaller()); break; case SHOW_EXACT: - System.out.println("Setting filter: exact"); sorter.setRowFilter(new MotorRowFilterExact()); break; @@ -125,9 +128,46 @@ public class MotorChooserDialog extends JDialog { setSelectionVisible(); } }); - panel.add(combo,"growx, wrap"); + panel.add(combo,"growx 1000"); + + label = new JLabel("Search:"); + panel.add(label, "gapleft para, split 2"); + + searchField = new JTextField(); + searchField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + + private void update() { + String text = searchField.getText().trim(); + String[] split = text.split("\\s+"); + ArrayList list = new ArrayList(); + for (String s: split) { + s = s.trim().toLowerCase(); + if (s.length() > 0) { + list.add(s); + } + } + searchTerms = list.toArray(new String[0]); + sorter.sort(); + } + }); + panel.add(searchField, "growx 1, wrap"); + + + // Table, overridden to show meaningful tooltip texts model = new MotorDatabaseModel(current); table = new JTable(model) { @@ -186,11 +226,11 @@ public class MotorChooserDialog extends JDialog { JScrollPane scrollpane = new JScrollPane(); scrollpane.setViewportView(table); - panel.add(scrollpane,"grow, width :700:, height :300:, wrap paragraph"); + panel.add(scrollpane,"spanx, grow, width :700:, height :300:, wrap paragraph"); // Ejection delay - panel.add(new JLabel("Select ejection charge delay:"), "split 3, gap rel"); + panel.add(new JLabel("Select ejection charge delay:"), "spanx, split 3, gap rel"); delayBox = new JComboBox(); delayBox.setEditable(true); @@ -222,7 +262,7 @@ public class MotorChooserDialog extends JDialog { MotorChooserDialog.this.setVisible(false); } }); - panel.add(okButton,"split, tag ok"); + panel.add(okButton,"spanx, split, tag ok"); button = new JButton("Cancel"); button.addActionListener(new ActionListener() { @@ -248,6 +288,9 @@ public class MotorChooserDialog extends JDialog { // Table can be scrolled only after pack() has been called setSelectionVisible(); + + // Focus the search field + searchField.grabFocus(); } private void setSelectionVisible() { @@ -567,14 +610,26 @@ public class MotorChooserDialog extends JDialog { */ private abstract class MotorRowFilter extends RowFilter { @Override - public boolean include( - RowFilter.Entry entry) { + public boolean include(RowFilter.Entry entry) { int index = entry.getIdentifier(); Motor m = model.getMotor(index); - return include(m); + return filterByDiameter(m) && filterByString(m); } - public abstract boolean include(Motor m); + public abstract boolean filterByDiameter(Motor m); + + + public boolean filterByString(Motor m) { + main: for (String s : searchTerms) { + for (MotorColumns col : MotorColumns.values()) { + String str = col.getValue(m).toLowerCase(); + if (str.indexOf(s) >= 0) + continue main; + } + return false; + } + return true; + } } /** @@ -582,7 +637,7 @@ public class MotorChooserDialog extends JDialog { */ private class MotorRowFilterAll extends MotorRowFilter { @Override - public boolean include(Motor m) { + public boolean filterByDiameter(Motor m) { return true; } } @@ -592,7 +647,7 @@ public class MotorChooserDialog extends JDialog { */ private class MotorRowFilterSmaller extends MotorRowFilter { @Override - public boolean include(Motor m) { + public boolean filterByDiameter(Motor m) { return (m.getDiameter() <= diameter + 0.0004); } } @@ -602,7 +657,7 @@ public class MotorChooserDialog extends JDialog { */ private class MotorRowFilterExact extends MotorRowFilter { @Override - public boolean include(Motor m) { + public boolean filterByDiameter(Motor m) { return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015)); } diff --git a/src/net/sf/openrocket/gui/main/SimulationRunDialog.java b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java index fa98140a..e16486b7 100644 --- a/src/net/sf/openrocket/gui/main/SimulationRunDialog.java +++ b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java @@ -25,7 +25,7 @@ import javax.swing.JProgressBar; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.document.Simulation; -import net.sf.openrocket.gui.DetailDialog; +import net.sf.openrocket.gui.dialogs.DetailDialog; import net.sf.openrocket.rocketcomponent.Configuration; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.MotorMount.IgnitionEvent; diff --git a/src/net/sf/openrocket/rocketcomponent/ClusterConfiguration.java b/src/net/sf/openrocket/rocketcomponent/ClusterConfiguration.java index 5019fccc..d80d3695 100644 --- a/src/net/sf/openrocket/rocketcomponent/ClusterConfiguration.java +++ b/src/net/sf/openrocket/rocketcomponent/ClusterConfiguration.java @@ -56,9 +56,11 @@ public class ClusterConfiguration { }; + private final List points; private final String xmlName; + private ClusterConfiguration(String xmlName, double... points) { this.xmlName = xmlName; if (points.length == 0 || points.length%2 == 1) { @@ -100,4 +102,10 @@ public class ClusterConfiguration { } return ret; } + + + @Override + public String toString() { + return xmlName; + } } diff --git a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java index 1ea92cae..1312799f 100644 --- a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java @@ -59,10 +59,11 @@ public class FreeformFinSet extends FinSet { * if attempted. * * @param index the fin point index to remove + * @throws IllegalFinPointException if removing the first or last fin point was attempted. */ - public void removePoint(int index) { + public void removePoint(int index) throws IllegalFinPointException { if (index == 0 || index == points.size()-1) { - throw new IllegalArgumentException("cannot remove first or last point"); + throw new IllegalFinPointException("cannot remove first or last point"); } points.remove(index); fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); @@ -73,19 +74,19 @@ public class FreeformFinSet extends FinSet { return points.size(); } - public void setPoints(Coordinate[] p) { + public void setPoints(Coordinate[] p) throws IllegalFinPointException { if (p[0].x != 0 || p[0].y != 0 || p[p.length-1].y != 0) { - throw new IllegalArgumentException("Start or end point illegal."); + throw new IllegalFinPointException("Start or end point illegal."); } for (int i=0; i < p.length-1; i++) { for (int j=i+2; j < p.length-1; j++) { if (intersects(p[i].x, p[i].y, p[i+1].x, p[i+1].y, p[j].x, p[j].y, p[j+1].x, p[j+1].y)) { - throw new IllegalArgumentException("segments intersect"); + throw new IllegalFinPointException("segments intersect"); } } if (p[i].z != 0) { - throw new IllegalArgumentException("z-coordinate not zero"); + throw new IllegalFinPointException("z-coordinate not zero"); } } @@ -112,8 +113,10 @@ public class FreeformFinSet extends FinSet { * @param index the point index to modify. * @param x the x-coordinate. * @param y the y-coordinate. + * @throws IllegalFinPointException if the specified fin point would cause intersecting + * segments */ - public void setPoint(int index, double x, double y) { + public void setPoint(int index, double x, double y) throws IllegalFinPointException { if (y < 0) y = 0; @@ -160,12 +163,12 @@ public class FreeformFinSet extends FinSet { if (i != index-1 && i != index && i != index+1) { if (intersects(x0,y0,x,y,px0,py0,px1,py1)) { - throw new IllegalArgumentException("segments intersect"); + throw new IllegalFinPointException("segments intersect"); } } if (i != index && i != index+1 && i != index+2) { if (intersects(x,y,x1,y1,px0,py0,px1,py1)) { - throw new IllegalArgumentException("segments intersect"); + throw new IllegalFinPointException("segments intersect"); } } diff --git a/src/net/sf/openrocket/rocketcomponent/IllegalFinPointException.java b/src/net/sf/openrocket/rocketcomponent/IllegalFinPointException.java new file mode 100644 index 00000000..8f9a88ac --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/IllegalFinPointException.java @@ -0,0 +1,27 @@ +package net.sf.openrocket.rocketcomponent; + +/** + * An exception signifying that an operation on the freeform fin set points was + * illegal (segments intersect, removing first or last point, etc). + * + * @author Sampo Niskanen + */ +public class IllegalFinPointException extends Exception { + + public IllegalFinPointException() { + + } + + public IllegalFinPointException(String message) { + super(message); + } + + public IllegalFinPointException(Throwable cause) { + super(cause); + } + + public IllegalFinPointException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/Rocket.java b/src/net/sf/openrocket/rocketcomponent/Rocket.java index f696a69d..48904f89 100644 --- a/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -7,7 +7,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.UUID; import javax.swing.event.ChangeListener; @@ -72,8 +71,8 @@ public class Rocket extends RocketComponent { // Motor configuration list - private List motorConfigurationIDs = new ArrayList(); - private Map motorConfigurationNames = new HashMap(); + private ArrayList motorConfigurationIDs = new ArrayList(); + private HashMap motorConfigurationNames = new HashMap(); { motorConfigurationIDs.add(null); } @@ -267,22 +266,25 @@ public class Rocket extends RocketComponent { } + + + /** * Make a deep copy of the Rocket structure. This is a helper method which simply * casts the result of the superclass method to a Rocket. */ + @SuppressWarnings("unchecked") @Override public Rocket copy() { Rocket copy = (Rocket)super.copy(); + copy.motorConfigurationIDs = (ArrayList) this.motorConfigurationIDs.clone(); + copy.motorConfigurationNames = + (HashMap) this.motorConfigurationNames.clone(); copy.resetListeners(); + return copy; } - - - - - /** * Load the rocket structure from the source. The method loads the fields of this * Rocket object and copies the references to siblings from the source. @@ -293,6 +295,7 @@ public class Rocket extends RocketComponent { * and therefore fires an UNDO_EVENT, masked with all applicable mass/aerodynamic/tree * changes. */ + @SuppressWarnings("unchecked") public void loadFrom(Rocket r) { super.copyFrom(r); @@ -312,10 +315,15 @@ public class Rocket extends RocketComponent { this.refType = r.refType; this.customReferenceLength = r.customReferenceLength; - this.motorConfigurationIDs = r.motorConfigurationIDs; - this.motorConfigurationNames = r.motorConfigurationNames; + this.motorConfigurationIDs = (ArrayList) r.motorConfigurationIDs.clone(); + this.motorConfigurationNames = + (HashMap) r.motorConfigurationNames.clone(); this.perfectFinish = r.perfectFinish; + String id = defaultConfiguration.getMotorConfigurationID(); + if (!this.motorConfigurationIDs.contains(id)) + defaultConfiguration.setMotorConfigurationID(null); + fireComponentChangeEvent(type); } @@ -594,7 +602,7 @@ public class Rocket extends RocketComponent { */ public void setMotorConfigurationName(String id, String name) { motorConfigurationNames.put(id,name); - fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); } diff --git a/src/net/sf/openrocket/util/ConcurrentProgressMonitor.java b/src/net/sf/openrocket/util/ConcurrentProgressMonitor.java new file mode 100644 index 00000000..6d98a5fc --- /dev/null +++ b/src/net/sf/openrocket/util/ConcurrentProgressMonitor.java @@ -0,0 +1,45 @@ +package net.sf.openrocket.util; + +import java.awt.Component; + +import javax.swing.ProgressMonitor; +import javax.swing.SwingUtilities; + + +/** + * A thread-safe ProgressMonitor. This class may be instantiated + * and the method {@link #setProgress(int)} called safely from any thread. + *

+ * Why the FSCK&!¤#&%¤ isn't the default API version thread-safe?!?! + * + * @author Sampo Niskanen + */ +public class ConcurrentProgressMonitor extends ProgressMonitor { + + public ConcurrentProgressMonitor(Component parentComponent, Object message, + String note, int min, int max) { + super(parentComponent, message, note, min, max); + } + + @Override + public void setProgress(final int nv) { + + if (SwingUtilities.isEventDispatchThread()) { + super.setProgress(nv); + } else { + + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + ConcurrentProgressMonitor.super.setProgress(nv); + } + + }); + + } + } + + + +} diff --git a/src/net/sf/openrocket/util/ConcurrentProgressMonitorInputStream.java b/src/net/sf/openrocket/util/ConcurrentProgressMonitorInputStream.java new file mode 100644 index 00000000..7fd5a8a9 --- /dev/null +++ b/src/net/sf/openrocket/util/ConcurrentProgressMonitorInputStream.java @@ -0,0 +1,146 @@ +/* + * TODO: CRITICAL: Licensing + */ + +package net.sf.openrocket.util; + +import java.awt.Component; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; + + +/** + * A functional equivalent of ProgressMonitorInputStream which + * uses {@link ConcurrentProgressMonitor} and leaves the progress dialog open + * to be manually closed later on. + */ + +public class ConcurrentProgressMonitorInputStream extends FilterInputStream { + private ConcurrentProgressMonitor monitor; + private int nread = 0; + private int size = 0; + + + /** + * Constructs an object to monitor the progress of an input stream. + * + * @param message Descriptive text to be placed in the dialog box + * if one is popped up. + * @param parentComponent The component triggering the operation + * being monitored. + * @param in The input stream to be monitored. + */ + public ConcurrentProgressMonitorInputStream(Component parentComponent, + Object message, InputStream in) { + super(in); + try { + size = in.available(); + } catch (IOException ioe) { + size = 0; + } + monitor = new ConcurrentProgressMonitor(parentComponent, message, null, 0, + size + 1); + } + + + /** + * Get the ProgressMonitor object being used by this stream. Normally + * this isn't needed unless you want to do something like change the + * descriptive text partway through reading the file. + * @return the ProgressMonitor object used by this object + */ + public ConcurrentProgressMonitor getProgressMonitor() { + return monitor; + } + + + /** + * Overrides FilterInputStream.read + * to update the progress monitor after the read. + */ + @Override + public int read() throws IOException { + int c = in.read(); + if (c >= 0) + monitor.setProgress(++nread); + if (monitor.isCanceled()) { + InterruptedIOException exc = new InterruptedIOException("progress"); + exc.bytesTransferred = nread; + throw exc; + } + return c; + } + + + /** + * Overrides FilterInputStream.read + * to update the progress monitor after the read. + */ + @Override + public int read(byte b[]) throws IOException { + int nr = in.read(b); + if (nr > 0) + monitor.setProgress(nread += nr); + if (monitor.isCanceled()) { + InterruptedIOException exc = new InterruptedIOException("progress"); + exc.bytesTransferred = nread; + throw exc; + } + return nr; + } + + + /** + * Overrides FilterInputStream.read + * to update the progress monitor after the read. + */ + @Override + public int read(byte b[], int off, int len) throws IOException { + int nr = in.read(b, off, len); + if (nr > 0) + monitor.setProgress(nread += nr); + if (monitor.isCanceled()) { + InterruptedIOException exc = new InterruptedIOException("progress"); + exc.bytesTransferred = nread; + throw exc; + } + return nr; + } + + + /** + * Overrides FilterInputStream.skip + * to update the progress monitor after the skip. + */ + @Override + public long skip(long n) throws IOException { + long nr = in.skip(n); + if (nr > 0) + monitor.setProgress(nread += nr); + return nr; + } + + + /** + * Overrides FilterInputStream.close + * to close the progress monitor as well as the stream. + */ + @Override + public void close() throws IOException { + in.close(); + } + + + /** + * Overrides FilterInputStream.reset + * to reset the progress monitor as well as the stream. + */ + @Override + public synchronized void reset() throws IOException { + in.reset(); + nread = size - in.available(); + monitor.setProgress(nread); + } +} diff --git a/src/net/sf/openrocket/util/Test.java b/src/net/sf/openrocket/util/Test.java index 8f039555..4d150d42 100644 --- a/src/net/sf/openrocket/util/Test.java +++ b/src/net/sf/openrocket/util/Test.java @@ -6,6 +6,7 @@ import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.Bulkhead; import net.sf.openrocket.rocketcomponent.CenteringRing; import net.sf.openrocket.rocketcomponent.FreeformFinSet; +import net.sf.openrocket.rocketcomponent.IllegalFinPointException; import net.sf.openrocket.rocketcomponent.InnerTube; import net.sf.openrocket.rocketcomponent.LaunchLug; import net.sf.openrocket.rocketcomponent.MassComponent; @@ -144,13 +145,17 @@ public class Test { bodytube = new BodyTube(0.69,0.033,0.001); finset = new FreeformFinSet(); - finset.setPoints(new Coordinate[] { - new Coordinate(0, 0), - new Coordinate(0.115, 0.072), - new Coordinate(0.255, 0.072), - new Coordinate(0.255, 0.037), - new Coordinate(0.150, 0) - }); + try { + finset.setPoints(new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0.115, 0.072), + new Coordinate(0.255, 0.072), + new Coordinate(0.255, 0.037), + new Coordinate(0.150, 0) + }); + } catch (IllegalFinPointException e) { + e.printStackTrace(); + } finset.setThickness(0.003); finset.setFinCount(4); -- 2.30.2