From 26fb295554afd42b5a7f7f9cc6fe395b5dec682a Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Fri, 27 Sep 2013 10:10:38 -0500 Subject: [PATCH 01/47] Rework source structure to separate swing gui code from core code. Separate swing application code from core code. Moved a bunch of sources and libraries around. Created new eclipse project. Further refinements to make it build. Update gitignore. --- .gitignore | 6 - core/.classpath | 9 - core/build.xml | 135 +------ .../file/rocksim/export/InnerBodyTubeDTO.java | 202 +++++------ .../loader/RocksimComponentFileLoader.java | 92 +++-- .../openrocket/rocketcomponent/InnerTube.java | 21 +- .../net/sf/openrocket/ServicesForTesting.java | 155 ++++++++ .../file/openrocket/OpenRocketSaverTest.java | 7 +- .../net/sf/openrocket/plugin/PluginTest.java | 4 +- .../rocketcomponent/ComponentCompareTest.java | 5 +- .../rocketcomponent/FinSetTest.java | 137 ++++---- .../util/BaseTestCase/BaseTestCase.java | 4 +- swing/.classpath | 23 ++ swing/.gitignore | 2 + swing/.project | 17 + swing/.settings/org.eclipse.jdt.core.prefs | 89 +++++ swing/build.xml | 331 ++++++++++++++++++ {core => swing}/lib/OrangeExtensions-1.2.jar | Bin {core => swing}/lib/iText-5.0.2.jar | Bin {core => swing}/lib/jcommon-1.0.18.jar | Bin {core => swing}/lib/jfreechart-1.0.15.jar | Bin .../jogl/gluegen-rt-natives-linux-amd64.jar | Bin .../jogl/gluegen-rt-natives-linux-i586.jar | Bin .../gluegen-rt-natives-macosx-universal.jar | Bin .../jogl/gluegen-rt-natives-windows-amd64.jar | Bin .../jogl/gluegen-rt-natives-windows-i586.jar | Bin {core => swing}/lib/jogl/gluegen-rt.jar | Bin .../lib/jogl/jogl-all-natives-linux-amd64.jar | Bin .../lib/jogl/jogl-all-natives-linux-i586.jar | Bin .../jogl-all-natives-macosx-universal.jar | Bin .../jogl/jogl-all-natives-windows-amd64.jar | Bin .../jogl/jogl-all-natives-windows-i586.jar | Bin {core => swing}/lib/jogl/jogl-all.jar | Bin .../lib/logback-classic-1.0.12.jar | Bin {core => swing}/lib/logback-core-1.0.12.jar | Bin {core => swing}/lib/miglayout15-swing.jar | Bin .../reference/jfreechart-1.0.15-sources.jar | Bin .../reference/jogl-all-2.0.2-sources.jar | Bin .../resources-src/datafiles/presets/Estes.orc | 0 .../datafiles/presets/LocPrecision.orc | 0 .../resources-src/datafiles/presets/Quest.orc | 0 .../datafiles/presets/bluetube.orc | 0 .../resources-src/datafiles/presets/bms.orc | 0 .../datafiles/presets/fliskits.orc | 0 .../datafiles/presets/giantleaprocketry.orc | 0 .../datafiles/presets/publicmissiles.orc | 0 .../datafiles/presets/semroc.orc | 0 .../rocksim_components/bluetube/BTDATA.CSV | 0 .../rocksim_components/bluetube/MATERIAL.CSV | 0 .../rocksim_components/bluetube/TCDATA.CSV | 0 .../rocksim_components/bluetube/readme.txt | 0 .../rocksim_components/bms/BHdata.csv | 0 .../rocksim_components/bms/BTdata.csv | 0 .../rocksim_components/bms/CRdata.csv | 0 .../rocksim_components/bms/LLdata.csv | 0 .../rocksim_components/bms/MATERIAL.CSV | 0 .../rocksim_components/bms/NCdata.csv | 0 .../rocksim_components/bms/TCdata.csv | 0 .../rocksim_components/bms/TRdata.csv | 0 .../rocksim_components/bms/ebdata.csv | 0 .../rocksim_components/estes/BTDATA.CSV | 0 .../rocksim_components/estes/EBDATA.CSV | 0 .../rocksim_components/estes/LLDATA.CSV | 0 .../rocksim_components/estes/MATERIAL.CSV | 0 .../rocksim_components/estes/MODATA.CSV | 0 .../rocksim_components/estes/NCDATA.CSV | 0 .../rocksim_components/estes/PCDATA.CSV | 0 .../rocksim_components/estes/TCDATA.CSV | 0 .../rocksim_components/estes/TRDATA.CSV | 0 .../giantleaprocketry/BHDATA.CSV | 0 .../giantleaprocketry/BTDATA.CSV | 0 .../giantleaprocketry/CRDATA.CSV | 0 .../giantleaprocketry/LLDATA.CSV | 0 .../giantleaprocketry/MATERIAL.CSV | 0 .../giantleaprocketry/MODATA.CSV | 0 .../giantleaprocketry/NCDATA.CSV | 0 .../giantleaprocketry/PCDATA.CSV | 0 .../giantleaprocketry/TCDATA.CSV | 0 .../publicmissiles/BHDATA.CSV | 0 .../publicmissiles/BTDATA.CSV | 0 .../publicmissiles/CFDATA.CSV | 0 .../publicmissiles/CRDATA.CSV | 0 .../publicmissiles/EBDATA.CSV | 0 .../publicmissiles/FSDATA.CSV | 0 .../publicmissiles/GRAPHS.CSV | 0 .../publicmissiles/LLDATA.CSV | 0 .../publicmissiles/MATERIAL.CSV | 0 .../publicmissiles/MODATA.CSV | 0 .../publicmissiles/NCDATA.CSV | 0 .../publicmissiles/PCDATA.CSV | 0 .../publicmissiles/SLDATA.CSV | 0 .../publicmissiles/STDATA.CSV | 0 .../publicmissiles/TCDATA.CSV | 0 .../publicmissiles/TRDATA.CSV | 0 .../rocksim_components/quest/BTDATA.CSV | 0 .../rocksim_components/quest/CRDATA.CSV | 0 .../rocksim_components/quest/EBDATA.CSV | 0 .../rocksim_components/quest/MATERIAL.CSV | 0 .../rocksim_components/quest/NCDATA.CSV | 0 .../rocksim_components/quest/PCDATA.CSV | 0 .../rocksim_components/quest/STDATA.CSV | 0 .../rocksim_components/quest/TCDATA.CSV | 0 .../rocksim_components/quest/TRDATA.CSV | 0 .../rocksim_components/semroc/BHDATA.CSV | 0 .../rocksim_components/semroc/BTDATA.CSV | 0 .../rocksim_components/semroc/CRDATA.CSV | 0 .../rocksim_components/semroc/EBDATA.CSV | 0 .../rocksim_components/semroc/LLDATA.CSV | 0 .../rocksim_components/semroc/MATERIAL.CSV | 0 .../rocksim_components/semroc/NCDATA.CSV | 0 .../rocksim_components/semroc/PCDATA.CSV | 0 .../rocksim_components/semroc/STDATA.CSV | 0 .../rocksim_components/semroc/TCDATA.CSV | 0 .../rocksim_components/semroc/TRDATA.CSV | 0 .../datafiles/tours/convert-images.sh | 0 .../creating_design/dialog-1-nosecone.xcf.gz | Bin .../creating_design/dialog-2-bodytube.xcf.gz | Bin .../creating_design/dialog-3-finset.xcf.gz | Bin .../creating_design/dialog-4-innertube.xcf.gz | Bin .../dialog-5-centeringring.xcf.gz | Bin .../creating_design/dialog-6-parachute.xcf.gz | Bin .../tours/creating_design/main-0-initial.png | Bin .../creating_design/main-1-nosecone.xcf.gz | Bin .../creating_design/main-2-bodytube.xcf.gz | Bin .../creating_design/main-3-finset.xcf.gz | Bin .../creating_design/main-4-innertube.xcf.gz | Bin .../main-5-centeringring.xcf.gz | Bin .../creating_design/main-6-parachute.xcf.gz | Bin .../tours/creating_design/main-7-rest.xcf.gz | Bin .../tours/creating_design/main-8-final.png | Bin .../introduction/advanced_features.xcf.gz | Bin .../introduction/flight_simulations.xcf.gz | Bin .../tours/introduction/logo-MANUAL.xcf.gz | Bin .../tours/introduction/main_window.png | Bin .../introduction/main_window_bottom.xcf.gz | Bin .../tours/introduction/main_window_top.xcf.gz | Bin .../examples/A simple model rocket.ork | Bin .../examples/Apocalypse with decals.ork | Bin .../datafiles/examples/Boosted Dart.ork | Bin .../examples/Clustered rocket design.ork | Bin .../examples/High Power Airstart.ork | Bin ... rocket with dual parachute deployment.ork | Bin .../datafiles/examples/Preset Usage.ork | Bin .../examples/Roll-stabilized rocket.ork | Bin .../examples/Simulation listeners.ork | Bin .../datafiles/examples/TARC Payloader.ork | Bin .../datafiles/examples/Three-stage rocket.ork | Bin .../resources/datafiles/presets/system.ser | Bin 386639 -> 386639 bytes .../resources/datafiles/textures/balsa.jpg | Bin .../datafiles/textures/cardboard.jpg | Bin .../resources/datafiles/textures/chute.jpg | Bin .../datafiles/textures/hardboard.jpg | Bin .../datafiles/textures/motors/aerotech.png | Bin .../datafiles/textures/motors/estes.jpg | Bin .../datafiles/textures/motors/reusable.png | Bin .../datafiles/textures/spiral-wound-alpha.png | Bin .../resources/datafiles/textures/wadding.png | Bin .../resources/datafiles/textures/wood.jpg | Bin .../ComponentPresetDatabaseLoader.java | 0 .../database/MotorDatabaseLoader.java | 0 .../file/motor/MotorLoaderHelper.java | 0 .../sf/openrocket/gui/ExportDecalDialog.java | 0 .../src/net/sf/openrocket/gui/Resettable.java | 0 .../net/sf/openrocket/gui/SpinnerEditor.java | 0 .../openrocket/gui/StorageOptionChooser.java | 0 .../sf/openrocket/gui/TextFieldListener.java | 0 .../openrocket/gui/adaptors/BooleanModel.java | 0 .../sf/openrocket/gui/adaptors/Column.java | 0 .../gui/adaptors/ColumnTableModel.java | 0 .../openrocket/gui/adaptors/DecalModel.java | 0 .../openrocket/gui/adaptors/DoubleModel.java | 0 .../sf/openrocket/gui/adaptors/EnumModel.java | 0 .../adaptors/FlightConfigurationModel.java | 0 .../openrocket/gui/adaptors/IntegerModel.java | 0 .../gui/adaptors/MaterialModel.java | 0 .../openrocket/gui/adaptors/PresetModel.java | 0 .../gui/components/BasicSlider.java | 0 .../openrocket/gui/components/BasicTree.java | 0 .../gui/components/CollectionTable.java | 0 .../gui/components/ColorChooser.java | 0 .../gui/components/ColorChooserButton.java | 0 .../openrocket/gui/components/ColorIcon.java | 0 .../gui/components/CsvOptionPanel.java | 0 .../gui/components/DescriptionArea.java | 0 .../gui/components/DoubleCellEditor.java | 0 .../openrocket/gui/components/FlatButton.java | 0 .../openrocket/gui/components/HtmlLabel.java | 0 .../gui/components/ImageDisplayComponent.java | 0 .../gui/components/SelectableLabel.java | 0 .../gui/components/SimulationExportPanel.java | 0 .../gui/components/StageSelector.java | 0 .../gui/components/StarCheckBox.java | 0 .../gui/components/StyledLabel.java | 0 .../openrocket/gui/components/URLLabel.java | 0 .../gui/components/UnitCellEditor.java | 0 .../gui/components/UnitSelector.java | 0 .../components/compass/CompassPointer.java | 0 .../gui/components/compass/CompassRose.java | 0 .../compass/CompassSelectionButton.java | 0 .../components/compass/CompassSelector.java | 0 .../gui/components/compass/Tester.java | 0 .../gui/configdialog/AppearancePanel.java | 0 .../gui/configdialog/BodyTubeConfig.java | 0 .../gui/configdialog/BulkheadConfig.java | 0 .../gui/configdialog/CenteringRingConfig.java | 0 .../gui/configdialog/CommonStrings.java | 0 .../configdialog/ComponentConfigDialog.java | 0 .../configdialog/EllipticalFinSetConfig.java | 0 .../gui/configdialog/FinSetConfig.java | 0 .../configdialog/FreeformFinSetConfig.java | 0 .../gui/configdialog/InnerTubeConfig.java | 23 +- .../gui/configdialog/LaunchLugConfig.java | 0 .../gui/configdialog/MassComponentConfig.java | 0 .../gui/configdialog/MotorConfig.java | 0 .../gui/configdialog/NoseConeConfig.java | 0 .../gui/configdialog/ParachuteConfig.java | 0 .../configdialog/RecoveryDeviceConfig.java | 0 .../gui/configdialog/RingComponentConfig.java | 0 .../configdialog/RocketComponentConfig.java | 0 .../gui/configdialog/RocketConfig.java | 0 .../gui/configdialog/ShockCordConfig.java | 0 .../gui/configdialog/SleeveConfig.java | 0 .../gui/configdialog/StageConfig.java | 0 .../gui/configdialog/StreamerConfig.java | 0 .../ThicknessRingComponentConfig.java | 0 .../gui/configdialog/TransitionConfig.java | 0 .../configdialog/TrapezoidFinSetConfig.java | 0 .../CustomExpressionDialog.java | 0 .../CustomExpressionPanel.java | 0 .../ExpressionBuilderDialog.java | 0 .../customexpression/OperatorSelector.java | 0 .../customexpression/OperatorTableModel.java | 0 .../customexpression/VariableSelector.java | 0 .../customexpression/VariableTableModel.java | 0 .../openrocket/gui/dialogs/AboutDialog.java | 0 .../gui/dialogs/BugReportDialog.java | 0 .../gui/dialogs/ComponentAnalysisDialog.java | 0 .../gui/dialogs/CustomMaterialDialog.java | 0 .../gui/dialogs/DebugLogDialog.java | 0 .../openrocket/gui/dialogs/DetailDialog.java | 0 .../gui/dialogs/EditDecalDialog.java | 0 .../openrocket/gui/dialogs/LicenseDialog.java | 0 .../openrocket/gui/dialogs/PrintDialog.java | 0 .../gui/dialogs/PrintSettingsDialog.java | 0 .../openrocket/gui/dialogs/ScaleDialog.java | 0 .../gui/dialogs/SwingWorkerDialog.java | 0 .../gui/dialogs/UpdateInfoDialog.java | 0 .../openrocket/gui/dialogs/WarningDialog.java | 0 .../DeploymentSelectionDialog.java | 0 .../FlightConfigurationDialog.java | 0 .../IgnitionSelectionDialog.java | 0 .../MotorConfigurationPanel.java | 0 .../MotorConfigurationTableModel.java | 0 .../MotorMountTableModel.java | 0 .../RecoveryConfigurationPanel.java | 0 .../RenameConfigDialog.java | 0 .../SeparationConfigurationPanel.java | 0 .../SeparationSelectionDialog.java | 0 .../gui/dialogs/motor/CloseableDialog.java | 0 .../gui/dialogs/motor/MotorChooserDialog.java | 0 .../gui/dialogs/motor/MotorSelector.java | 0 .../dialogs/motor/thrustcurve/MotorClass.java | 0 .../motor/thrustcurve/MotorHolder.java | 0 .../thrustcurve/ThrustCurveMotorColumns.java | 0 .../ThrustCurveMotorComparator.java | 0 .../ThrustCurveMotorDatabaseModel.java | 0 .../ThrustCurveMotorPlotDialog.java | 0 .../ThrustCurveMotorSelectionPanel.java | 0 .../optimization/FunctionEvaluationData.java | 0 .../GeneralOptimizationDialog.java | 0 .../optimization/OptimizationPlotDialog.java | 0 .../optimization/OptimizationStepData.java | 0 .../optimization/OptimizationWorker.java | 0 .../optimization/SimulationModifierTree.java | 0 .../preferences/MaterialEditPanel.java | 0 .../preferences/PreferencesDialog.java | 0 .../preset/ComponentPresetChooserDialog.java | 0 .../preset/ComponentPresetRowFilter.java | 0 .../dialogs/preset/ComponentPresetTable.java | 0 .../preset/ComponentPresetTableColumn.java | 0 .../gui/dialogs/preset/XTableColumnModel.java | 0 .../gui/figure3d/FigureRenderer.java | 0 .../gui/figure3d/RealisticRenderer.java | 0 .../gui/figure3d/RocketFigure3d.java | 0 .../gui/figure3d/RocketRenderer.java | 0 .../gui/figure3d/UnfinishedRenderer.java | 0 .../figure3d/geometry/ComponentRenderer.java | 0 .../DisplayListComponentRenderer.java | 0 .../gui/figure3d/geometry/FinRenderer.java | 0 .../gui/figure3d/geometry/Geometry.java | 0 .../figure3d/geometry/MassObjectRenderer.java | 0 .../figure3d/geometry/TransitionRenderer.java | 0 .../gui/figureelements/CGCaret.java | 0 .../gui/figureelements/CPCaret.java | 0 .../openrocket/gui/figureelements/Caret.java | 0 .../gui/figureelements/FigureElement.java | 0 .../gui/figureelements/RocketInfo.java | 0 .../help/tours/GuidedTourSelectionDialog.java | 0 .../sf/openrocket/gui/help/tours/Slide.java | 0 .../openrocket/gui/help/tours/SlideSet.java | 0 .../gui/help/tours/SlideSetLoader.java | 0 .../gui/help/tours/SlideSetManager.java | 0 .../gui/help/tours/SlideShowComponent.java | 0 .../gui/help/tours/SlideShowDialog.java | 0 .../gui/help/tours/SlideShowLinkListener.java | 0 .../gui/help/tours/TextLineReader.java | 0 .../sf/openrocket/gui/main/BasicFrame.java | 0 .../gui/main/ClipboardListener.java | 0 .../gui/main/ComponentAddButtons.java | 0 .../openrocket/gui/main/ComponentIcons.java | 0 .../gui/main/DocumentSelectionListener.java | 0 .../gui/main/DocumentSelectionModel.java | 0 .../gui/main/ExampleDesignFile.java | 0 .../gui/main/ExampleDesignFileAction.java | 0 .../sf/openrocket/gui/main/MRUDesignFile.java | 0 .../gui/main/MRUDesignFileAction.java | 0 .../gui/main/OpenRocketClipboard.java | 0 .../sf/openrocket/gui/main/RocketActions.java | 0 .../openrocket/gui/main/SimulationPanel.java | 0 .../net/sf/openrocket/gui/main/Splash.java | 0 .../gui/main/SwingExceptionHandler.java | 0 .../openrocket/gui/main/UndoRedoAction.java | 0 .../gui/main/componenttree/ComponentTree.java | 0 .../componenttree/ComponentTreeModel.java | 0 .../componenttree/ComponentTreeRenderer.java | 0 .../ComponentTreeTransferHandler.java | 0 .../RocketComponentTransferable.java | 0 .../src/net/sf/openrocket/gui/plot/Axis.java | 0 .../sf/openrocket/gui/plot/EventGraphics.java | 0 .../gui/plot/PlotConfiguration.java | 0 .../openrocket/gui/plot/SimulationChart.java | 0 .../openrocket/gui/plot/SimulationPlot.java | 0 .../gui/plot/SimulationPlotDialog.java | 0 .../src/net/sf/openrocket/gui/plot/Util.java | 0 .../openrocket/gui/preset/ButtonColumn.java | 0 .../gui/preset/DeselectableComboBox.java | 0 .../gui/preset/ImagePreviewPanel.java | 0 .../openrocket/gui/preset/MaterialModel.java | 0 .../gui/preset/PresetEditorDialog.java | 0 .../gui/preset/PresetResultListener.java | 0 .../gui/print/AbstractPrintable.java | 0 .../sf/openrocket/gui/print/DesignReport.java | 0 .../openrocket/gui/print/FinMarkingGuide.java | 0 .../sf/openrocket/gui/print/ITextHelper.java | 0 .../gui/print/OpenRocketPrintable.java | 0 .../gui/print/PDFPrintStreamDoc.java | 0 .../gui/print/PaperOrientation.java | 0 .../sf/openrocket/gui/print/PaperSize.java | 0 .../openrocket/gui/print/PrintController.java | 0 .../sf/openrocket/gui/print/PrintFigure.java | 0 .../openrocket/gui/print/PrintSettings.java | 0 .../gui/print/PrintSimulationWorker.java | 0 .../sf/openrocket/gui/print/PrintUnit.java | 0 .../openrocket/gui/print/PrintUtilities.java | 0 .../gui/print/PrintableCenteringRing.java | 0 .../gui/print/PrintableComponent.java | 0 .../gui/print/PrintableContext.java | 0 .../openrocket/gui/print/PrintableFinSet.java | 0 .../gui/print/PrintableNoseCone.java | 0 .../gui/print/PrintableTransition.java | 0 .../gui/print/TemplateProperties.java | 0 .../gui/print/components/CheckBoxNode.java | 0 .../components/CheckTreeCellRenderer.java | 0 .../print/components/CheckTreeManager.java | 0 .../components/CheckTreeSelectionModel.java | 0 .../gui/print/components/RocketPrintTree.java | 0 .../openrocket/gui/print/components/Rule.java | 0 .../print/visitor/AbstractPrintStrategy.java | 0 .../print/visitor/CenteringRingStrategy.java | 0 .../gui/print/visitor/Dimension.java | 0 .../visitor/FinMarkingGuideStrategy.java | 0 .../print/visitor/FinSetPrintStrategy.java | 0 .../print/visitor/PageFitPrintStrategy.java | 0 .../visitor/PartsDetailVisitorStrategy.java | 0 .../visitor/PartsListVisitorStrategy.java | 0 .../gui/print/visitor/TransitionStrategy.java | 0 .../gui/rocketfigure/BodyTubeShapes.java | 0 .../gui/rocketfigure/FinSetShapes.java | 0 .../gui/rocketfigure/LaunchLugShapes.java | 0 .../gui/rocketfigure/MassObjectShapes.java | 0 .../gui/rocketfigure/RingComponentShapes.java | 0 .../rocketfigure/RocketComponentShapes.java | 0 .../SymmetricComponentShapes.java | 0 .../gui/rocketfigure/TransitionShapes.java | 0 .../gui/scalefigure/AbstractScaleFigure.java | 0 .../gui/scalefigure/FinPointFigure.java | 0 .../gui/scalefigure/RocketFigure.java | 0 .../gui/scalefigure/RocketPanel.java | 0 .../gui/scalefigure/ScaleFigure.java | 0 .../gui/scalefigure/ScaleScrollPane.java | 0 .../gui/scalefigure/ScaleSelector.java | 0 .../simulation/SimulationConditionsPanel.java | 0 .../gui/simulation/SimulationEditDialog.java | 0 .../gui/simulation/SimulationExportPanel.java | 0 .../simulation/SimulationOptionsPanel.java | 0 .../gui/simulation/SimulationPlotPanel.java | 0 .../gui/simulation/SimulationRunDialog.java | 0 .../simulation/SimulationWarningDialog.java | 0 .../gui/simulation/SimulationWorker.java | 0 .../openrocket/gui/util/ColorConversion.java | 0 .../gui/util/ConcurrentProgressMonitor.java | 0 .../ConcurrentProgressMonitorInputStream.java | 0 .../gui/util/CustomFinImporter.java | 0 .../openrocket/gui/util/EditDecalHelper.java | 0 .../sf/openrocket/gui/util/FileHelper.java | 0 .../net/sf/openrocket/gui/util/GUIUtil.java | 0 .../src/net/sf/openrocket/gui/util/Icons.java | 0 .../openrocket/gui/util/OpenFileWorker.java | 0 .../gui/util/ProgressOutputStream.java | 0 .../sf/openrocket/gui/util/SaveCSVWorker.java | 0 .../openrocket/gui/util/SaveFileWorker.java | 0 .../openrocket/gui/util/SwingPreferences.java | 0 .../openrocket/gui/watcher/FileWatcher.java | 0 .../sf/openrocket/gui/watcher/WatchEvent.java | 0 .../sf/openrocket/gui/watcher/WatchKey.java | 0 .../openrocket/gui/watcher/WatchService.java | 0 .../gui/watcher/WatchServiceImpl.java | 0 .../sf/openrocket/gui/watcher/Watchable.java | 0 .../sf/openrocket/logging/BufferLogger.java | 0 .../sf/openrocket/logging/CyclicBuffer.java | 0 .../openrocket/logging/DelegatorLogger.java | 0 .../net/sf/openrocket/logging/LogHelper.java | 0 .../net/sf/openrocket/logging/LogLevel.java | 0 .../logging/LogLevelBufferLogger.java | 0 .../net/sf/openrocket/logging/LogLine.java | 0 .../logging/LogbackBufferLoggerAdaptor.java | 0 .../logging/LoggingSystemSetup.java | 0 .../openrocket/logging/PrintStreamLogger.java | 0 .../logging/PrintStreamToSLF4J.java | 0 .../openrocket/logging/StackTraceWriter.java | 0 .../sf/openrocket/logging/TraceException.java | 0 .../net/sf/openrocket/startup/GuiModule.java | 0 .../net/sf/openrocket/startup/OSXSetup.java | 0 .../net/sf/openrocket/startup/Startup.java | 0 .../sf/openrocket/startup/SwingStartup.java | 0 .../startup/jij/ClasspathProvider.java | 0 .../jij/ClasspathUrlStreamHandler.java | 0 .../jij/ConfigurableStreamHandlerFactory.java | 0 .../startup/jij/CurrentClasspathProvider.java | 0 .../startup/jij/JarInJarStarter.java | 0 .../jij/ManifestClasspathProvider.java | 0 .../startup/jij/PluginClasspathProvider.java | 0 ...ockingComponentPresetDatabaseProvider.java | 0 .../BlockingMotorDatabaseProvider.java | 0 .../startup/providers/TranslatorProvider.java | 0 .../sf/openrocket/utils/BasicApplication.java | 0 .../utils/ComponentPresetEditor.java | 0 .../openrocket/utils/CoreServicesModule.java | 0 .../utils/GraphicalMotorSelector.java | 0 .../net/sf/openrocket/utils/MotorPlot.java | 0 .../utils/RocksimComponentFileTranslator.java | 0 .../sf/openrocket/utils/RocksimConverter.java | 0 .../sf/openrocket/utils/SerializePresets.java | 0 .../net/sf/openrocket/IntegrationTest.java | 0 .../test/net/sf/openrocket/gui/TestGUI.java | 0 .../gui/configdialog/FinSetConfigTest.java | 0 .../openrocket/gui/print/PrintUnitTest.java | 0 .../openrocket/gui/print/TestPaperSize.java | 0 .../openrocket/logging/CyclicBufferTest.java | 0 .../logging/LogLevelBufferLoggerTest.java | 0 .../sf/openrocket/logging/LogLevelTest.java | 0 461 files changed, 862 insertions(+), 400 deletions(-) create mode 100644 core/test/net/sf/openrocket/ServicesForTesting.java create mode 100644 swing/.classpath create mode 100644 swing/.gitignore create mode 100644 swing/.project create mode 100644 swing/.settings/org.eclipse.jdt.core.prefs create mode 100644 swing/build.xml rename {core => swing}/lib/OrangeExtensions-1.2.jar (100%) rename {core => swing}/lib/iText-5.0.2.jar (100%) rename {core => swing}/lib/jcommon-1.0.18.jar (100%) rename {core => swing}/lib/jfreechart-1.0.15.jar (100%) rename {core => swing}/lib/jogl/gluegen-rt-natives-linux-amd64.jar (100%) rename {core => swing}/lib/jogl/gluegen-rt-natives-linux-i586.jar (100%) rename {core => swing}/lib/jogl/gluegen-rt-natives-macosx-universal.jar (100%) rename {core => swing}/lib/jogl/gluegen-rt-natives-windows-amd64.jar (100%) rename {core => swing}/lib/jogl/gluegen-rt-natives-windows-i586.jar (100%) rename {core => swing}/lib/jogl/gluegen-rt.jar (100%) rename {core => swing}/lib/jogl/jogl-all-natives-linux-amd64.jar (100%) rename {core => swing}/lib/jogl/jogl-all-natives-linux-i586.jar (100%) rename {core => swing}/lib/jogl/jogl-all-natives-macosx-universal.jar (100%) rename {core => swing}/lib/jogl/jogl-all-natives-windows-amd64.jar (100%) rename {core => swing}/lib/jogl/jogl-all-natives-windows-i586.jar (100%) rename {core => swing}/lib/jogl/jogl-all.jar (100%) rename {core => swing}/lib/logback-classic-1.0.12.jar (100%) rename {core => swing}/lib/logback-core-1.0.12.jar (100%) rename {core => swing}/lib/miglayout15-swing.jar (100%) rename {core => swing}/reference/jfreechart-1.0.15-sources.jar (100%) rename {core => swing}/reference/jogl-all-2.0.2-sources.jar (100%) rename {core => swing}/resources-src/datafiles/presets/Estes.orc (100%) rename {core => swing}/resources-src/datafiles/presets/LocPrecision.orc (100%) rename {core => swing}/resources-src/datafiles/presets/Quest.orc (100%) rename {core => swing}/resources-src/datafiles/presets/bluetube.orc (100%) rename {core => swing}/resources-src/datafiles/presets/bms.orc (100%) rename {core => swing}/resources-src/datafiles/presets/fliskits.orc (100%) rename {core => swing}/resources-src/datafiles/presets/giantleaprocketry.orc (100%) rename {core => swing}/resources-src/datafiles/presets/publicmissiles.orc (100%) rename {core => swing}/resources-src/datafiles/presets/semroc.orc (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bluetube/BTDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bluetube/MATERIAL.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bluetube/TCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bluetube/readme.txt (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bms/BHdata.csv (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bms/BTdata.csv (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bms/CRdata.csv (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bms/LLdata.csv (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bms/MATERIAL.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bms/NCdata.csv (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bms/TCdata.csv (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bms/TRdata.csv (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/bms/ebdata.csv (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/estes/BTDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/estes/EBDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/estes/LLDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/estes/MATERIAL.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/estes/MODATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/estes/NCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/estes/PCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/estes/TCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/estes/TRDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/giantleaprocketry/BHDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/giantleaprocketry/BTDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/giantleaprocketry/CRDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/giantleaprocketry/LLDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/giantleaprocketry/MATERIAL.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/giantleaprocketry/MODATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/giantleaprocketry/NCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/giantleaprocketry/PCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/giantleaprocketry/TCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/BHDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/BTDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/CFDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/CRDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/EBDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/FSDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/GRAPHS.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/LLDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/MATERIAL.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/MODATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/NCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/PCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/SLDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/STDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/TCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/publicmissiles/TRDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/quest/BTDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/quest/CRDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/quest/EBDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/quest/MATERIAL.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/quest/NCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/quest/PCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/quest/STDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/quest/TCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/quest/TRDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/BHDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/BTDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/CRDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/EBDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/LLDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/MATERIAL.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/NCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/PCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/STDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/TCDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/rocksim_components/semroc/TRDATA.CSV (100%) rename {core => swing}/resources-src/datafiles/tours/convert-images.sh (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/dialog-1-nosecone.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/dialog-2-bodytube.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/dialog-3-finset.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/dialog-4-innertube.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/dialog-5-centeringring.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/dialog-6-parachute.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/main-0-initial.png (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/main-1-nosecone.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/main-2-bodytube.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/main-3-finset.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/main-4-innertube.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/main-5-centeringring.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/main-6-parachute.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/main-7-rest.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/creating_design/main-8-final.png (100%) rename {core => swing}/resources-src/datafiles/tours/introduction/advanced_features.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/introduction/flight_simulations.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/introduction/logo-MANUAL.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/introduction/main_window.png (100%) rename {core => swing}/resources-src/datafiles/tours/introduction/main_window_bottom.xcf.gz (100%) rename {core => swing}/resources-src/datafiles/tours/introduction/main_window_top.xcf.gz (100%) rename {core => swing}/resources/datafiles/examples/A simple model rocket.ork (100%) rename {core => swing}/resources/datafiles/examples/Apocalypse with decals.ork (100%) rename {core => swing}/resources/datafiles/examples/Boosted Dart.ork (100%) rename {core => swing}/resources/datafiles/examples/Clustered rocket design.ork (100%) rename {core => swing}/resources/datafiles/examples/High Power Airstart.ork (100%) rename {core => swing}/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork (100%) rename {core => swing}/resources/datafiles/examples/Preset Usage.ork (100%) rename {core => swing}/resources/datafiles/examples/Roll-stabilized rocket.ork (100%) rename {core => swing}/resources/datafiles/examples/Simulation listeners.ork (100%) rename {core => swing}/resources/datafiles/examples/TARC Payloader.ork (100%) rename {core => swing}/resources/datafiles/examples/Three-stage rocket.ork (100%) rename {core => swing}/resources/datafiles/presets/system.ser (97%) rename {core => swing}/resources/datafiles/textures/balsa.jpg (100%) rename {core => swing}/resources/datafiles/textures/cardboard.jpg (100%) rename {core => swing}/resources/datafiles/textures/chute.jpg (100%) rename {core => swing}/resources/datafiles/textures/hardboard.jpg (100%) rename {core => swing}/resources/datafiles/textures/motors/aerotech.png (100%) rename {core => swing}/resources/datafiles/textures/motors/estes.jpg (100%) rename {core => swing}/resources/datafiles/textures/motors/reusable.png (100%) rename {core => swing}/resources/datafiles/textures/spiral-wound-alpha.png (100%) rename {core => swing}/resources/datafiles/textures/wadding.png (100%) rename {core => swing}/resources/datafiles/textures/wood.jpg (100%) rename {core => swing}/src/net/sf/openrocket/database/ComponentPresetDatabaseLoader.java (100%) rename {core => swing}/src/net/sf/openrocket/database/MotorDatabaseLoader.java (100%) rename {core => swing}/src/net/sf/openrocket/file/motor/MotorLoaderHelper.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/ExportDecalDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/Resettable.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/SpinnerEditor.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/StorageOptionChooser.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/TextFieldListener.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/BooleanModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/Column.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/DecalModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/DoubleModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/EnumModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/FlightConfigurationModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/IntegerModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/MaterialModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/adaptors/PresetModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/BasicSlider.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/BasicTree.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/CollectionTable.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/ColorChooser.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/ColorChooserButton.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/ColorIcon.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/CsvOptionPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/DescriptionArea.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/DoubleCellEditor.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/FlatButton.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/HtmlLabel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/ImageDisplayComponent.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/SelectableLabel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/SimulationExportPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/StageSelector.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/StarCheckBox.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/StyledLabel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/URLLabel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/UnitCellEditor.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/UnitSelector.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/compass/CompassPointer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/compass/CompassRose.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/compass/CompassSelectionButton.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/compass/CompassSelector.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/components/compass/Tester.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/CommonStrings.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/EllipticalFinSetConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java (89%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/MotorConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/RecoveryDeviceConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/RocketConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/StageConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/customexpression/VariableSelector.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/AboutDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/DetailDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/PrintDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/PrintSettingsDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/SwingWorkerDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/WarningDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/DeploymentSelectionDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/FlightConfigurationDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/IgnitionSelectionDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationTableModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RecoveryConfigurationPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationConfigurationPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/CloseableDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/MotorSelector.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorClass.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorHolder.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorColumns.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorComparator.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorDatabaseModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/optimization/FunctionEvaluationData.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationStepData.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetChooserDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetRowFilter.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTable.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTableColumn.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/preset/XTableColumnModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/FigureRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/RocketRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/UnfinishedRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/geometry/ComponentRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/geometry/DisplayListComponentRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/geometry/Geometry.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/geometry/MassObjectRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figure3d/geometry/TransitionRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figureelements/CGCaret.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figureelements/CPCaret.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figureelements/Caret.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figureelements/FigureElement.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/figureelements/RocketInfo.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/help/tours/GuidedTourSelectionDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/help/tours/Slide.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/help/tours/SlideSet.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/help/tours/SlideSetLoader.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/help/tours/SlideSetManager.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/help/tours/SlideShowDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/help/tours/SlideShowLinkListener.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/help/tours/TextLineReader.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/BasicFrame.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/ClipboardListener.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/ComponentAddButtons.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/ComponentIcons.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/DocumentSelectionListener.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/DocumentSelectionModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/ExampleDesignFile.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/ExampleDesignFileAction.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/MRUDesignFile.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/MRUDesignFileAction.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/RocketActions.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/SimulationPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/Splash.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/SwingExceptionHandler.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/UndoRedoAction.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/componenttree/ComponentTree.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/main/componenttree/RocketComponentTransferable.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/plot/Axis.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/plot/EventGraphics.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/plot/PlotConfiguration.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/plot/SimulationChart.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/plot/SimulationPlot.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/plot/Util.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/preset/ButtonColumn.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/preset/DeselectableComboBox.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/preset/MaterialModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/preset/PresetResultListener.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/AbstractPrintable.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/DesignReport.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/FinMarkingGuide.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/ITextHelper.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PDFPrintStreamDoc.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PaperOrientation.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PaperSize.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintController.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintFigure.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintSettings.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintUnit.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintUtilities.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintableCenteringRing.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintableComponent.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintableContext.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintableFinSet.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintableNoseCone.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/PrintableTransition.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/TemplateProperties.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/components/CheckBoxNode.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/components/CheckTreeCellRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/components/CheckTreeManager.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/components/CheckTreeSelectionModel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/components/Rule.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/visitor/AbstractPrintStrategy.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/visitor/CenteringRingStrategy.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/visitor/Dimension.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/visitor/FinMarkingGuideStrategy.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/visitor/PageFitPrintStrategy.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/visitor/PartsListVisitorStrategy.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/rocketfigure/LaunchLugShapes.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/rocketfigure/MassObjectShapes.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/rocketfigure/RingComponentShapes.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/simulation/SimulationConditionsPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/simulation/SimulationExportPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/simulation/SimulationWarningDialog.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/simulation/SimulationWorker.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/ColorConversion.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitor.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitorInputStream.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/CustomFinImporter.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/EditDecalHelper.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/FileHelper.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/GUIUtil.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/Icons.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/OpenFileWorker.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/ProgressOutputStream.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/SaveCSVWorker.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/SaveFileWorker.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/SwingPreferences.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/watcher/FileWatcher.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/watcher/WatchEvent.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/watcher/WatchKey.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/watcher/WatchService.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/watcher/WatchServiceImpl.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/watcher/Watchable.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/BufferLogger.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/CyclicBuffer.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/DelegatorLogger.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/LogHelper.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/LogLevel.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/LogLevelBufferLogger.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/LogLine.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/LogbackBufferLoggerAdaptor.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/LoggingSystemSetup.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/PrintStreamLogger.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/PrintStreamToSLF4J.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/StackTraceWriter.java (100%) rename {core => swing}/src/net/sf/openrocket/logging/TraceException.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/GuiModule.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/OSXSetup.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/Startup.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/SwingStartup.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/jij/ClasspathProvider.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/jij/ClasspathUrlStreamHandler.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/jij/ConfigurableStreamHandlerFactory.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/jij/CurrentClasspathProvider.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/jij/JarInJarStarter.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/jij/ManifestClasspathProvider.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/jij/PluginClasspathProvider.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/providers/BlockingComponentPresetDatabaseProvider.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/providers/BlockingMotorDatabaseProvider.java (100%) rename {core => swing}/src/net/sf/openrocket/startup/providers/TranslatorProvider.java (100%) rename {core => swing}/src/net/sf/openrocket/utils/BasicApplication.java (100%) rename {core => swing}/src/net/sf/openrocket/utils/ComponentPresetEditor.java (100%) rename {core => swing}/src/net/sf/openrocket/utils/CoreServicesModule.java (100%) rename {core => swing}/src/net/sf/openrocket/utils/GraphicalMotorSelector.java (100%) rename {core => swing}/src/net/sf/openrocket/utils/MotorPlot.java (100%) rename {core => swing}/src/net/sf/openrocket/utils/RocksimComponentFileTranslator.java (100%) rename {core => swing}/src/net/sf/openrocket/utils/RocksimConverter.java (100%) rename {core => swing}/src/net/sf/openrocket/utils/SerializePresets.java (100%) rename {core => swing}/test/net/sf/openrocket/IntegrationTest.java (100%) rename {core => swing}/test/net/sf/openrocket/gui/TestGUI.java (100%) rename {core => swing}/test/net/sf/openrocket/gui/configdialog/FinSetConfigTest.java (100%) rename {core => swing}/test/net/sf/openrocket/gui/print/PrintUnitTest.java (100%) rename {core => swing}/test/net/sf/openrocket/gui/print/TestPaperSize.java (100%) rename {core => swing}/test/net/sf/openrocket/logging/CyclicBufferTest.java (100%) rename {core => swing}/test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java (100%) rename {core => swing}/test/net/sf/openrocket/logging/LogLevelTest.java (100%) diff --git a/.gitignore b/.gitignore index 896cbbf5f..91aeba86b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,9 +37,3 @@ /core/resources-src/pix/sormus.xcf.gz /core/resources-src/pix/splashscreen-sormus.png /core/resources-src/pix/splashscreen-sormus.xcf.gz -<<<<<<< HEAD - -/*/bin/ -/android-libraries/*/bin/ -======= ->>>>>>> Convert svn:ignore properties to .gitignore. diff --git a/core/.classpath b/core/.classpath index 40137a329..3345ea11a 100644 --- a/core/.classpath +++ b/core/.classpath @@ -10,8 +10,6 @@ - - @@ -20,19 +18,12 @@ - - - - - - - diff --git a/core/build.xml b/core/build.xml index 48f7aed46..9f41533e9 100644 --- a/core/build.xml +++ b/core/build.xml @@ -1,4 +1,4 @@ - + @@ -23,14 +23,6 @@ - - - - - - - - @@ -51,13 +43,6 @@ - - - - - - - @@ -74,56 +59,20 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Generating ORC file for vendor @{vendor} - - - - - - - - - - - - - - - - - - - - - - Building source distribution - - - - - - - - - - - - - - - - - - Testing source distribution - - - - - - - - - Source distribution test successful - - - - - - - - - - - - - Distribution ${build.version} (${build.source}) built into directory ${jar.dir} - - diff --git a/core/src/net/sf/openrocket/file/rocksim/export/InnerBodyTubeDTO.java b/core/src/net/sf/openrocket/file/rocksim/export/InnerBodyTubeDTO.java index 8b040395e..cf995a54d 100644 --- a/core/src/net/sf/openrocket/file/rocksim/export/InnerBodyTubeDTO.java +++ b/core/src/net/sf/openrocket/file/rocksim/export/InnerBodyTubeDTO.java @@ -1,7 +1,12 @@ package net.sf.openrocket.file.rocksim.export; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + import net.sf.openrocket.file.rocksim.RocksimCommonConstants; -import net.sf.openrocket.gui.configdialog.InnerTubeConfig; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.Bulkhead; import net.sf.openrocket.rocketcomponent.CenteringRing; @@ -15,110 +20,105 @@ import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.rocketcomponent.TubeCoupler; import net.sf.openrocket.util.Coordinate; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; -import java.util.List; - /** * This class models the XML element for a Rocksim inside tube. */ @XmlRootElement(name = RocksimCommonConstants.BODY_TUBE) @XmlAccessorType(XmlAccessType.FIELD) public class InnerBodyTubeDTO extends BodyTubeDTO implements AttachableParts { - - /** - * Constructor. - */ - public InnerBodyTubeDTO() { - super.setInsideTube(true); - } - - /** - * Full copy constructor. - * - * @param bt the corresponding OR inner body tube - * @param parent the attached parts (subcomponents in Rocksim speak) of the InnerTube's parent. This instance - * is a member of those attached parts, as well as all sibling components. This is passed in the - * event that the inner tube is a cluster. In that situation this instance will be removed and - * individual instances for each cluster member will be added. - */ - public InnerBodyTubeDTO(InnerTube bt, AttachableParts parent) { - super(bt); - setEngineOverhang(bt.getMotorOverhang() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH); - setID(bt.getInnerRadius() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_RADIUS); - setOD(bt.getOuterRadius() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_RADIUS); - setMotorDia((bt.getMotorMountDiameter() / 2) * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_RADIUS); - setMotorMount(bt.isMotorMount()); - setInsideTube(true); - setRadialAngle(bt.getRadialDirection()); - setRadialLoc(bt.getRadialPosition() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH); - - List children = bt.getChildren(); - for (int i = 0; i < children.size(); i++) { - RocketComponent rocketComponents = children.get(i); - if (rocketComponents instanceof InnerTube) { - final InnerTube innerTube = (InnerTube) rocketComponents; - //Only if the inner tube is NOT a cluster, then create the corresponding Rocksim DTO and add it - //to the list of attached parts. If it is a cluster, then it is handled specially outside of this - //loop. - if (innerTube.getClusterCount() == 1) { - attachedParts.add(new InnerBodyTubeDTO(innerTube, this)); - } - } else if (rocketComponents instanceof BodyTube) { - attachedParts.add(new BodyTubeDTO((BodyTube) rocketComponents)); - } else if (rocketComponents instanceof Transition) { - attachedParts.add(new TransitionDTO((Transition) rocketComponents)); - } else if (rocketComponents instanceof EngineBlock) { - attachedParts.add(new EngineBlockDTO((EngineBlock) rocketComponents)); - } else if (rocketComponents instanceof TubeCoupler) { - attachedParts.add(new TubeCouplerDTO((TubeCoupler) rocketComponents)); - } else if (rocketComponents instanceof CenteringRing) { - attachedParts.add(new CenteringRingDTO((CenteringRing) rocketComponents)); - } else if (rocketComponents instanceof Bulkhead) { - attachedParts.add(new BulkheadDTO((Bulkhead) rocketComponents)); - } else if (rocketComponents instanceof Streamer) { - attachedParts.add(new StreamerDTO((Streamer) rocketComponents)); - } else if (rocketComponents instanceof Parachute) { - attachedParts.add(new ParachuteDTO((Parachute) rocketComponents)); - } else if (rocketComponents instanceof MassObject) { - attachedParts.add(new MassObjectDTO((MassObject) rocketComponents)); - } - } - //Do the cluster. For now this splits the cluster into separate tubes, which is how Rocksim represents it. - //The import (from Rocksim to OR) could be augmented to be more intelligent and try to determine if the - //co-located tubes are a cluster. - if (bt.getClusterConfiguration().getClusterCount() > 1) { - handleCluster(bt, parent); - parent.removeAttachedPart(this); - } - } - - - /** - * Handle the inner tube as a cluster. This amounts to splitting it up so that each motor mount in the cluster - * is created individually to support Rocksim's view of clusters. - * - * @param it the clustered tube - * @param p the collection (parent's attached parts really) to which all cluster tubes will be added - */ - private void handleCluster(InnerTube it, AttachableParts p) { - - Coordinate[] coords = {Coordinate.NUL}; - coords = it.shiftCoordinates(coords); - for (int x = 0; x < coords.length; x++) { - InnerTube partialClone = InnerTubeConfig.makeIndividualClusterComponent(coords[x], it.getName() + " #" + (x + 1), it); - p.addAttachedPart(new InnerBodyTubeDTO(partialClone, p)); - } - } - - @Override - public void addAttachedPart(BasePartDTO part) { - attachedParts.add(part); - } - - @Override - public void removeAttachedPart(BasePartDTO part) { - attachedParts.remove(part); - } + + /** + * Constructor. + */ + public InnerBodyTubeDTO() { + super.setInsideTube(true); + } + + /** + * Full copy constructor. + * + * @param bt the corresponding OR inner body tube + * @param parent the attached parts (subcomponents in Rocksim speak) of the InnerTube's parent. This instance + * is a member of those attached parts, as well as all sibling components. This is passed in the + * event that the inner tube is a cluster. In that situation this instance will be removed and + * individual instances for each cluster member will be added. + */ + public InnerBodyTubeDTO(InnerTube bt, AttachableParts parent) { + super(bt); + setEngineOverhang(bt.getMotorOverhang() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH); + setID(bt.getInnerRadius() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_RADIUS); + setOD(bt.getOuterRadius() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_RADIUS); + setMotorDia((bt.getMotorMountDiameter() / 2) * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_RADIUS); + setMotorMount(bt.isMotorMount()); + setInsideTube(true); + setRadialAngle(bt.getRadialDirection()); + setRadialLoc(bt.getRadialPosition() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH); + + List children = bt.getChildren(); + for (int i = 0; i < children.size(); i++) { + RocketComponent rocketComponents = children.get(i); + if (rocketComponents instanceof InnerTube) { + final InnerTube innerTube = (InnerTube) rocketComponents; + //Only if the inner tube is NOT a cluster, then create the corresponding Rocksim DTO and add it + //to the list of attached parts. If it is a cluster, then it is handled specially outside of this + //loop. + if (innerTube.getClusterCount() == 1) { + attachedParts.add(new InnerBodyTubeDTO(innerTube, this)); + } + } else if (rocketComponents instanceof BodyTube) { + attachedParts.add(new BodyTubeDTO((BodyTube) rocketComponents)); + } else if (rocketComponents instanceof Transition) { + attachedParts.add(new TransitionDTO((Transition) rocketComponents)); + } else if (rocketComponents instanceof EngineBlock) { + attachedParts.add(new EngineBlockDTO((EngineBlock) rocketComponents)); + } else if (rocketComponents instanceof TubeCoupler) { + attachedParts.add(new TubeCouplerDTO((TubeCoupler) rocketComponents)); + } else if (rocketComponents instanceof CenteringRing) { + attachedParts.add(new CenteringRingDTO((CenteringRing) rocketComponents)); + } else if (rocketComponents instanceof Bulkhead) { + attachedParts.add(new BulkheadDTO((Bulkhead) rocketComponents)); + } else if (rocketComponents instanceof Streamer) { + attachedParts.add(new StreamerDTO((Streamer) rocketComponents)); + } else if (rocketComponents instanceof Parachute) { + attachedParts.add(new ParachuteDTO((Parachute) rocketComponents)); + } else if (rocketComponents instanceof MassObject) { + attachedParts.add(new MassObjectDTO((MassObject) rocketComponents)); + } + } + //Do the cluster. For now this splits the cluster into separate tubes, which is how Rocksim represents it. + //The import (from Rocksim to OR) could be augmented to be more intelligent and try to determine if the + //co-located tubes are a cluster. + if (bt.getClusterConfiguration().getClusterCount() > 1) { + handleCluster(bt, parent); + parent.removeAttachedPart(this); + } + } + + + /** + * Handle the inner tube as a cluster. This amounts to splitting it up so that each motor mount in the cluster + * is created individually to support Rocksim's view of clusters. + * + * @param it the clustered tube + * @param p the collection (parent's attached parts really) to which all cluster tubes will be added + */ + private void handleCluster(InnerTube it, AttachableParts p) { + + Coordinate[] coords = { Coordinate.NUL }; + coords = it.shiftCoordinates(coords); + for (int x = 0; x < coords.length; x++) { + InnerTube partialClone = InnerTube.makeIndividualClusterComponent(coords[x], it.getName() + " #" + (x + 1), it); + p.addAttachedPart(new InnerBodyTubeDTO(partialClone, p)); + } + } + + @Override + public void addAttachedPart(BasePartDTO part) { + attachedParts.add(part); + } + + @Override + public void removeAttachedPart(BasePartDTO part) { + attachedParts.remove(part); + } } diff --git a/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileLoader.java b/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileLoader.java index cb285a216..eef411558 100644 --- a/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileLoader.java +++ b/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileLoader.java @@ -1,13 +1,5 @@ package net.sf.openrocket.preset.loader; -import au.com.bytecode.opencsv.CSVReader; -import net.sf.openrocket.gui.print.PrintUnit; -import net.sf.openrocket.preset.TypedPropertyMap; -import net.sf.openrocket.unit.Unit; -import net.sf.openrocket.unit.UnitGroup; -import net.sf.openrocket.util.ArrayList; -import net.sf.openrocket.util.StringUtil; - import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -17,19 +9,26 @@ import java.io.InputStreamReader; import java.io.PrintStream; import java.util.List; +import net.sf.openrocket.preset.TypedPropertyMap; +import net.sf.openrocket.unit.Unit; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.ArrayList; +import net.sf.openrocket.util.StringUtil; +import au.com.bytecode.opencsv.CSVReader; + /** * Primary entry point for parsing component CSV files that are in Rocksim format. */ public abstract class RocksimComponentFileLoader { - - private static final PrintStream LOGGER = System.err; - + + private static final PrintStream LOGGER = System.err; + private String basePath = ""; - + private File dir; - + protected List fileColumns = new ArrayList(); - + /** * Constructor. * @@ -39,7 +38,7 @@ public abstract class RocksimComponentFileLoader { dir = theBasePathToLoadFrom; basePath = dir.getAbsolutePath(); } - + /** * Constructor. * @@ -49,17 +48,17 @@ public abstract class RocksimComponentFileLoader { dir = new File(basePath); basePath = theBasePathToLoadFrom; } - + protected abstract RocksimComponentFileType getFileType(); - + public void load() { try { - load(getFileType()); - } catch (FileNotFoundException fex ) { - LOGGER.println( fex.getLocalizedMessage() ); + load(getFileType()); + } catch (FileNotFoundException fex) { + LOGGER.println(fex.getLocalizedMessage()); } } - + /** * Read a comma separated component file and return the parsed contents as a list of string arrays. Not for * production use - just here for smoke testing. @@ -83,7 +82,7 @@ public abstract class RocksimComponentFileLoader { FileInputStream fis = new FileInputStream(new File(dir, type.getDefaultFileName())); load(fis); } - + /** * Read a comma separated component file and return the parsed contents as a list of string arrays. * @@ -97,7 +96,7 @@ public abstract class RocksimComponentFileLoader { private void load(File file) throws FileNotFoundException { load(new FileInputStream(file)); } - + /** * Read a comma separated component file and return the parsed contents as a list of string arrays. * @@ -114,13 +113,13 @@ public abstract class RocksimComponentFileLoader { InputStreamReader r = null; try { r = new InputStreamReader(is); - + // Create the CSV reader. Use comma separator. CSVReader reader = new CSVReader(r, ',', '\'', '\\'); - + //Read and throw away the header row. parseHeaders(reader.readNext()); - + String[] data = null; while ((data = reader.readNext()) != null) { // detect empty lines and skip: @@ -134,41 +133,38 @@ public abstract class RocksimComponentFileLoader { } //Read the rest of the file as data rows. return; - } - catch (IOException e) { - } - finally { + } catch (IOException e) { + } finally { if (r != null) { try { r.close(); - } - catch (IOException e) { + } catch (IOException e) { } } } - + } - + protected void parseHeaders(String[] headers) { for (RocksimComponentFileColumnParser column : fileColumns) { column.configure(headers); } } - + protected void parseData(String[] data) { if (data == null || data.length == 0) { return; } TypedPropertyMap props = new TypedPropertyMap(); - + preProcess(data); - + for (RocksimComponentFileColumnParser column : fileColumns) { column.parse(data, props); } postProcess(props); } - + protected void preProcess(String[] data) { for (int i = 0; i < data.length; i++) { String d = data[i]; @@ -177,13 +173,13 @@ public abstract class RocksimComponentFileLoader { } d = d.trim(); d = stripAll(d, '"'); - + data[i] = d; } } - + protected abstract void postProcess(TypedPropertyMap props); - + /** * Rocksim CSV units are either inches or mm. A value of 0 or "in." indicate inches. A value of 1 or "mm" indicate * millimeters. @@ -196,7 +192,7 @@ public abstract class RocksimComponentFileLoader { String tmp = units.trim().toLowerCase(); return "0".equals(tmp) || tmp.startsWith("in"); } - + /** * Convert inches or millimeters to meters. * @@ -207,13 +203,13 @@ public abstract class RocksimComponentFileLoader { */ protected static double convertLength(String units, double value) { if (isInches(units)) { - return PrintUnit.INCHES.toMeters(value); + return UnitGroup.UNITS_LENGTH.getUnit("in").fromUnit(value); } else { - return PrintUnit.MILLIMETERS.toMeters(value); + return UnitGroup.UNITS_LENGTH.getUnit("mm").fromUnit(value); } } - + protected static double convertMass(String units, double value) { if ("oz".equals(units)) { Unit u = UnitGroup.UNITS_MASS.getUnit(2); @@ -221,7 +217,7 @@ public abstract class RocksimComponentFileLoader { } return value; } - + /** * Remove all occurrences of the given character. Note: this is done because some manufacturers embed double quotes * in their descriptions or material names. Those are stripped away because they cause all sorts of matching/lookup @@ -242,7 +238,7 @@ public abstract class RocksimComponentFileLoader { } return sb.toString(); } - + /** * Convert all words in a given string to Camel Case (first letter capitalized). Words are assumed to be separated * by a space. Note: this is done because some manufacturers define their material name in Camel Case but the @@ -268,7 +264,7 @@ public abstract class RocksimComponentFileLoader { return target; } } - + } //Errata: diff --git a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java index 2f9ab99d2..4b9737028 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java @@ -309,6 +309,25 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra return copy; } - + /** + * For a given coordinate that represents one tube in a cluster, create an instance of that tube. Must be called + * once for each tube in the cluster. + * + * @param coord the coordinate of the clustered tube to create + * @param splitName the name of the individual tube + * @param theInnerTube the 'parent' from which this tube will be created. + * + * @return an instance of an inner tube that represents ONE of the clustered tubes in the cluster represented + * by theInnerTube + */ + public static InnerTube makeIndividualClusterComponent(Coordinate coord, String splitName, RocketComponent theInnerTube) { + InnerTube copy = (InnerTube) theInnerTube.copy(); + copy.setClusterConfiguration(ClusterConfiguration.SINGLE); + copy.setClusterRotation(0.0); + copy.setClusterScale(1.0); + copy.setRadialShift(coord.y, coord.z); + copy.setName(splitName); + return copy; + } } \ No newline at end of file diff --git a/core/test/net/sf/openrocket/ServicesForTesting.java b/core/test/net/sf/openrocket/ServicesForTesting.java new file mode 100644 index 000000000..2d6ec6743 --- /dev/null +++ b/core/test/net/sf/openrocket/ServicesForTesting.java @@ -0,0 +1,155 @@ +package net.sf.openrocket; + +import java.util.Collections; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import net.sf.openrocket.formatting.RocketDescriptor; +import net.sf.openrocket.formatting.RocketDescriptorImpl; +import net.sf.openrocket.l10n.DebugTranslator; +import net.sf.openrocket.l10n.ResourceBundleTranslator; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.preset.ComponentPreset; +import net.sf.openrocket.preset.ComponentPreset.Type; +import net.sf.openrocket.startup.Preferences; + +import com.google.inject.AbstractModule; +import com.google.inject.Provider; + +public class ServicesForTesting extends AbstractModule { + + @Override + protected void configure() { + bind(Preferences.class).to(PreferencesForTesting.class); + bind(Translator.class).toProvider(TranslatorProviderForTesting.class); + bind(RocketDescriptor.class).to(RocketDescriptorImpl.class); + } + + public static class TranslatorProviderForTesting implements Provider { + + private AtomicReference translator = new AtomicReference(); + + @Override + public Translator get() { + + Translator oldTranslator = translator.get(); + + if (oldTranslator != null) { + return oldTranslator; + } + + + Locale.setDefault(Locale.US); + + // Setup the translator + Translator newTranslator; + newTranslator = new ResourceBundleTranslator("l10n.messages"); + if (Locale.getDefault().getLanguage().equals("xx")) { + newTranslator = new DebugTranslator(newTranslator); + } + + if (translator.compareAndSet(null, newTranslator)) { + return newTranslator; + } else { + return translator.get(); + } + + } + + } + + public static class PreferencesForTesting extends Preferences { + + @Override + public boolean getBoolean(String key, boolean defaultValue) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void putBoolean(String key, boolean value) { + // TODO Auto-generated method stub + + } + + @Override + public int getInt(String key, int defaultValue) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void putInt(String key, int value) { + // TODO Auto-generated method stub + + } + + @Override + public double getDouble(String key, double defaultValue) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void putDouble(String key, double value) { + // TODO Auto-generated method stub + + } + + @Override + public String getString(String key, String defaultValue) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void putString(String key, String value) { + // TODO Auto-generated method stub + + } + + @Override + public String getString(String directory, String key, String defaultValue) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void putString(String directory, String key, String value) { + // TODO Auto-generated method stub + + } + + @Override + public void addUserMaterial(Material m) { + // TODO Auto-generated method stub + + } + + @Override + public Set getUserMaterials() { + return Collections. emptySet(); + } + + @Override + public void removeUserMaterial(Material m) { + // TODO Auto-generated method stub + + } + + @Override + public void setComponentFavorite(ComponentPreset preset, Type type, boolean favorite) { + // TODO Auto-generated method stub + + } + + @Override + public Set getComponentFavorites(Type type) { + // TODO Auto-generated method stub + return null; + } + + } +} diff --git a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java index 7d80a0cfd..96f826c08 100644 --- a/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java +++ b/core/test/net/sf/openrocket/file/openrocket/OpenRocketSaverTest.java @@ -13,7 +13,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import net.sf.openrocket.IntegrationTest; +import net.sf.openrocket.ServicesForTesting; import net.sf.openrocket.database.ComponentPresetDao; import net.sf.openrocket.database.ComponentPresetDatabase; import net.sf.openrocket.database.motor.MotorDatabase; @@ -30,7 +30,6 @@ import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.plugin.PluginModule; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.TestRockets; -import net.sf.openrocket.utils.CoreServicesModule; import org.junit.After; import org.junit.BeforeClass; @@ -50,7 +49,7 @@ public class OpenRocketSaverTest { @BeforeClass public static void setup() { - Module applicationModule = new CoreServicesModule(); + Module applicationModule = new ServicesForTesting(); Module pluginModule = new PluginModule(); Module dbOverrides = new AbstractModule() { @@ -307,7 +306,7 @@ public class OpenRocketSaverTest { private static ThrustCurveMotor readMotor() { GeneralMotorLoader loader = new GeneralMotorLoader(); - InputStream is = IntegrationTest.class.getResourceAsStream("Estes_A8.rse"); + InputStream is = OpenRocketSaverTest.class.getResourceAsStream("/net/sf/openrocket/Estes_A8.rse"); assertNotNull("Problem in unit test, cannot find Estes_A8.rse", is); try { for (Motor m : loader.load(is, "Estes_A8.rse")) { diff --git a/core/test/net/sf/openrocket/plugin/PluginTest.java b/core/test/net/sf/openrocket/plugin/PluginTest.java index bff5bf9c1..6dacbeae4 100644 --- a/core/test/net/sf/openrocket/plugin/PluginTest.java +++ b/core/test/net/sf/openrocket/plugin/PluginTest.java @@ -1,6 +1,6 @@ package net.sf.openrocket.plugin; -import net.sf.openrocket.utils.CoreServicesModule; +import net.sf.openrocket.ServicesForTesting; import org.junit.Test; @@ -20,7 +20,7 @@ public class PluginTest { @Test public void testPluginModule() { - Module applicationModule = new CoreServicesModule(); + Module applicationModule = new ServicesForTesting(); Injector injector = Guice.createInjector(applicationModule, new PluginModule()); PluginTester tester = injector.getInstance(PluginTester.class); diff --git a/core/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java b/core/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java index d1797c2c7..c954edbd6 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java @@ -4,10 +4,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.awt.Color; import java.util.Iterator; -import net.sf.openrocket.gui.util.ColorConversion; +import net.sf.openrocket.util.Color; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; @@ -41,7 +40,7 @@ public class ComponentCompareTest extends BaseTestCase { ComponentCompare.assertDeepSimilarity(r1, r2, false); - r1.setColor(ColorConversion.fromAwtColor(Color.YELLOW)); + r1.setColor(Color.BLACK); try { ComponentCompare.assertEquality(r1, r2); fail(); diff --git a/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java index 14299440c..9febe3256 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java @@ -3,16 +3,13 @@ package net.sf.openrocket.rocketcomponent; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; - -import java.awt.Color; - -import net.sf.openrocket.gui.util.ColorConversion; import net.sf.openrocket.material.Material; import net.sf.openrocket.material.Material.Type; import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; import net.sf.openrocket.rocketcomponent.FinSet.CrossSection; import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition; import net.sf.openrocket.rocketcomponent.RocketComponent.Position; +import net.sf.openrocket.util.Color; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.LineStyle; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; @@ -20,22 +17,22 @@ import net.sf.openrocket.util.BaseTestCase.BaseTestCase; import org.junit.Test; public class FinSetTest extends BaseTestCase { - + @Test public void testTrapezoidCGComputation() { - + { // This is a simple square fin with sides of 1.0. TrapezoidFinSet fins = new TrapezoidFinSet(); fins.setFinCount(1); fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); - + Coordinate coords = fins.getCG(); assertEquals(1.0, fins.getFinArea(), 0.001); assertEquals(0.5, coords.x, 0.001); assertEquals(0.5, coords.y, 0.001); } - + { // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. // It can be decomposed into a rectangle followed by a triangle @@ -46,18 +43,18 @@ public class FinSetTest extends BaseTestCase { TrapezoidFinSet fins = new TrapezoidFinSet(); fins.setFinCount(1); fins.setFinShape(1.0, 0.5, 0.0, 1.0, .005); - + Coordinate coords = fins.getCG(); assertEquals(0.75, fins.getFinArea(), 0.001); assertEquals(0.3889, coords.x, 0.001); assertEquals(0.4444, coords.y, 0.001); } - + } - + @Test public void testFreeformCGComputation() throws Exception { - + { // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. // It can be decomposed into a rectangle followed by a triangle @@ -68,10 +65,10 @@ public class FinSetTest extends BaseTestCase { FreeformFinSet fins = new FreeformFinSet(); fins.setFinCount(1); Coordinate[] points = new Coordinate[] { - new Coordinate(0,0), - new Coordinate(0,1), - new Coordinate(.5,1), - new Coordinate(1,0) + new Coordinate(0, 0), + new Coordinate(0, 1), + new Coordinate(.5, 1), + new Coordinate(1, 0) }; fins.setPoints(points); Coordinate coords = fins.getCG(); @@ -79,20 +76,20 @@ public class FinSetTest extends BaseTestCase { assertEquals(0.3889, coords.x, 0.001); assertEquals(0.4444, coords.y, 0.001); } - + { // This is the same trapezoid as previous free form, but it has // some extra points along the lines. FreeformFinSet fins = new FreeformFinSet(); fins.setFinCount(1); Coordinate[] points = new Coordinate[] { - new Coordinate(0,0), - new Coordinate(0,.5), - new Coordinate(0,1), - new Coordinate(.25,1), - new Coordinate(.5,1), - new Coordinate(.75,.5), - new Coordinate(1,0) + new Coordinate(0, 0), + new Coordinate(0, .5), + new Coordinate(0, 1), + new Coordinate(.25, 1), + new Coordinate(.5, 1), + new Coordinate(.75, .5), + new Coordinate(1, 0) }; fins.setPoints(points); Coordinate coords = fins.getCG(); @@ -100,7 +97,7 @@ public class FinSetTest extends BaseTestCase { assertEquals(0.3889, coords.x, 0.001); assertEquals(0.4444, coords.y, 0.001); } - + { // This is the same trapezoid as previous free form, but it has // some extra points which are very close to previous points. @@ -109,14 +106,14 @@ public class FinSetTest extends BaseTestCase { FreeformFinSet fins = new FreeformFinSet(); fins.setFinCount(1); Coordinate[] points = new Coordinate[] { - new Coordinate(0,0), - new Coordinate(0,1E-15), - new Coordinate(0,1), - new Coordinate(1E-15,1), - new Coordinate(.5,1), - new Coordinate(.5,1-1E-15), - new Coordinate(1,1E-15), - new Coordinate(1,0) + new Coordinate(0, 0), + new Coordinate(0, 1E-15), + new Coordinate(0, 1), + new Coordinate(1E-15, 1), + new Coordinate(.5, 1), + new Coordinate(.5, 1 - 1E-15), + new Coordinate(1, 1E-15), + new Coordinate(1, 0) }; fins.setPoints(points); Coordinate coords = fins.getCG(); @@ -124,9 +121,9 @@ public class FinSetTest extends BaseTestCase { assertEquals(0.3889, coords.x, 0.001); assertEquals(0.4444, coords.y, 0.001); } - + } - + @Test public void testFreeFormCGWithNegativeY() throws Exception { // This particular fin shape is currently not allowed in OR since the y values are negative @@ -151,38 +148,38 @@ public class FinSetTest extends BaseTestCase { FreeformFinSet fins = new FreeformFinSet(); fins.setFinCount(1); Coordinate[] points = new Coordinate[] { - new Coordinate(0,0), - new Coordinate(0,1), - new Coordinate(2,1), - new Coordinate(2,-1), - new Coordinate(1,-1), - new Coordinate(1,0) + new Coordinate(0, 0), + new Coordinate(0, 1), + new Coordinate(2, 1), + new Coordinate(2, -1), + new Coordinate(1, -1), + new Coordinate(1, 0) }; fins.setPoints(points); Coordinate coords = fins.getCG(); assertEquals(3.0, fins.getFinArea(), 0.001); - assertEquals(3.5/3.0, coords.x, 0.001); - assertEquals(0.5/3.0, coords.y, 0.001); - + assertEquals(3.5 / 3.0, coords.x, 0.001); + assertEquals(0.5 / 3.0, coords.y, 0.001); + } - - + + @Test public void testFreeformConvert() { testFreeformConvert(new TrapezoidFinSet()); testFreeformConvert(new EllipticalFinSet()); testFreeformConvert(new FreeformFinSet()); } - - + + private void testFreeformConvert(FinSet fin) { FreeformFinSet converted; Material mat = Material.newMaterial(Type.BULK, "foo", 0.1, true); - + fin.setBaseRotation(1.1); fin.setCantAngle(0.001); fin.setCGOverridden(true); - fin.setColor(ColorConversion.fromAwtColor(Color.YELLOW)); + fin.setColor(Color.BLACK); fin.setComment("cmt"); fin.setCrossSection(CrossSection.ROUNDED); fin.setFinCount(5); @@ -200,57 +197,57 @@ public class FinSetTest extends BaseTestCase { fin.setTabRelativePosition(TabRelativePosition.END); fin.setTabShift(0.015); fin.setThickness(0.005); - - + + converted = FreeformFinSet.convertFinSet((FinSet) fin.copy()); - + ComponentCompare.assertSimilarity(fin, converted, true); - + assertEquals(converted.getComponentName(), converted.getName()); - - + + // Create test rocket Rocket rocket = new Rocket(); Stage stage = new Stage(); BodyTube body = new BodyTube(); - + rocket.addChild(stage); stage.addChild(body); body.addChild(fin); - + Listener l1 = new Listener("l1"); rocket.addComponentChangeListener(l1); - + fin.setName("Custom name"); assertTrue(l1.changed); assertEquals(ComponentChangeEvent.NONFUNCTIONAL_CHANGE, l1.changetype); - - + + // Create copy RocketComponent rocketcopy = rocket.copy(); - + Listener l2 = new Listener("l2"); rocketcopy.addComponentChangeListener(l2); - + FinSet fincopy = (FinSet) rocketcopy.getChild(0).getChild(0).getChild(0); FreeformFinSet.convertFinSet(fincopy); - + assertTrue(l2.changed); assertEquals(ComponentChangeEvent.TREE_CHANGE, l2.changetype & ComponentChangeEvent.TREE_CHANGE); - + } - - + + private static class Listener implements ComponentChangeListener { private boolean changed = false; private int changetype = 0; private final String name; - + public Listener(String name) { this.name = name; } - + @Override public void componentChanged(ComponentChangeEvent e) { assertFalse("Ensuring listener " + name + " has not been called.", changed); @@ -258,5 +255,5 @@ public class FinSetTest extends BaseTestCase { changetype = e.getType(); } } - + } diff --git a/core/test/net/sf/openrocket/util/BaseTestCase/BaseTestCase.java b/core/test/net/sf/openrocket/util/BaseTestCase/BaseTestCase.java index db762dfab..454e133cb 100644 --- a/core/test/net/sf/openrocket/util/BaseTestCase/BaseTestCase.java +++ b/core/test/net/sf/openrocket/util/BaseTestCase/BaseTestCase.java @@ -1,10 +1,10 @@ package net.sf.openrocket.util.BaseTestCase; +import net.sf.openrocket.ServicesForTesting; import net.sf.openrocket.l10n.DebugTranslator; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.plugin.PluginModule; import net.sf.openrocket.startup.Application; -import net.sf.openrocket.utils.CoreServicesModule; import org.junit.BeforeClass; @@ -18,7 +18,7 @@ public class BaseTestCase { @BeforeClass public static void setUp() throws Exception { - Module applicationModule = new CoreServicesModule(); + Module applicationModule = new ServicesForTesting(); Module debugTranslator = new AbstractModule() { @Override diff --git a/swing/.classpath b/swing/.classpath new file mode 100644 index 000000000..d8d181fc9 --- /dev/null +++ b/swing/.classpath @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swing/.gitignore b/swing/.gitignore new file mode 100644 index 000000000..348c102af --- /dev/null +++ b/swing/.gitignore @@ -0,0 +1,2 @@ +/build +/bin diff --git a/swing/.project b/swing/.project new file mode 100644 index 000000000..f8a30912c --- /dev/null +++ b/swing/.project @@ -0,0 +1,17 @@ + + + OpenRocket Swing + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/swing/.settings/org.eclipse.jdt.core.prefs b/swing/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..779e3273d --- /dev/null +++ b/swing/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,89 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=error +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=error +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/swing/build.xml b/swing/build.xml new file mode 100644 index 000000000..380ece52e --- /dev/null +++ b/swing/build.xml @@ -0,0 +1,331 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Compiling main classes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generating ORC file for vendor @{vendor} + + + + + + + + + + + + + + + + + + + + + + Building source distribution + + + + + + + + + + + + + + + + + + Testing source distribution + + + + + + + + + Source distribution test successful + + + + + + + + + + + + + Distribution ${build.version} (${build.source}) built into directory ${jar.dir} + + + + + + + + + + + Checking project for FIXMEs. + + + + + + + + + + + + + + + + CRITICAL TODOs exist in project: +${criticaltodos} + No critical TODOs in project. + + + + + + + + Checking project for non-ASCII characters. + + + + + + + + + + + + + + + + Non-ASCII characters exist in project: +${nonascii} + No non-ASCII characters in project. + + + + + + Building unit tests + + + + Running unit tests + + + + + + + + + + + + + + + + + + + + + + + Unit tests passed successfully. + + + + + + Building unit tests + + + + Running unit tests + + + + + + + + + + + + + + + + + + + + + Unit tests passed successfully. + + + + + diff --git a/core/lib/OrangeExtensions-1.2.jar b/swing/lib/OrangeExtensions-1.2.jar similarity index 100% rename from core/lib/OrangeExtensions-1.2.jar rename to swing/lib/OrangeExtensions-1.2.jar diff --git a/core/lib/iText-5.0.2.jar b/swing/lib/iText-5.0.2.jar similarity index 100% rename from core/lib/iText-5.0.2.jar rename to swing/lib/iText-5.0.2.jar diff --git a/core/lib/jcommon-1.0.18.jar b/swing/lib/jcommon-1.0.18.jar similarity index 100% rename from core/lib/jcommon-1.0.18.jar rename to swing/lib/jcommon-1.0.18.jar diff --git a/core/lib/jfreechart-1.0.15.jar b/swing/lib/jfreechart-1.0.15.jar similarity index 100% rename from core/lib/jfreechart-1.0.15.jar rename to swing/lib/jfreechart-1.0.15.jar diff --git a/core/lib/jogl/gluegen-rt-natives-linux-amd64.jar b/swing/lib/jogl/gluegen-rt-natives-linux-amd64.jar similarity index 100% rename from core/lib/jogl/gluegen-rt-natives-linux-amd64.jar rename to swing/lib/jogl/gluegen-rt-natives-linux-amd64.jar diff --git a/core/lib/jogl/gluegen-rt-natives-linux-i586.jar b/swing/lib/jogl/gluegen-rt-natives-linux-i586.jar similarity index 100% rename from core/lib/jogl/gluegen-rt-natives-linux-i586.jar rename to swing/lib/jogl/gluegen-rt-natives-linux-i586.jar diff --git a/core/lib/jogl/gluegen-rt-natives-macosx-universal.jar b/swing/lib/jogl/gluegen-rt-natives-macosx-universal.jar similarity index 100% rename from core/lib/jogl/gluegen-rt-natives-macosx-universal.jar rename to swing/lib/jogl/gluegen-rt-natives-macosx-universal.jar diff --git a/core/lib/jogl/gluegen-rt-natives-windows-amd64.jar b/swing/lib/jogl/gluegen-rt-natives-windows-amd64.jar similarity index 100% rename from core/lib/jogl/gluegen-rt-natives-windows-amd64.jar rename to swing/lib/jogl/gluegen-rt-natives-windows-amd64.jar diff --git a/core/lib/jogl/gluegen-rt-natives-windows-i586.jar b/swing/lib/jogl/gluegen-rt-natives-windows-i586.jar similarity index 100% rename from core/lib/jogl/gluegen-rt-natives-windows-i586.jar rename to swing/lib/jogl/gluegen-rt-natives-windows-i586.jar diff --git a/core/lib/jogl/gluegen-rt.jar b/swing/lib/jogl/gluegen-rt.jar similarity index 100% rename from core/lib/jogl/gluegen-rt.jar rename to swing/lib/jogl/gluegen-rt.jar diff --git a/core/lib/jogl/jogl-all-natives-linux-amd64.jar b/swing/lib/jogl/jogl-all-natives-linux-amd64.jar similarity index 100% rename from core/lib/jogl/jogl-all-natives-linux-amd64.jar rename to swing/lib/jogl/jogl-all-natives-linux-amd64.jar diff --git a/core/lib/jogl/jogl-all-natives-linux-i586.jar b/swing/lib/jogl/jogl-all-natives-linux-i586.jar similarity index 100% rename from core/lib/jogl/jogl-all-natives-linux-i586.jar rename to swing/lib/jogl/jogl-all-natives-linux-i586.jar diff --git a/core/lib/jogl/jogl-all-natives-macosx-universal.jar b/swing/lib/jogl/jogl-all-natives-macosx-universal.jar similarity index 100% rename from core/lib/jogl/jogl-all-natives-macosx-universal.jar rename to swing/lib/jogl/jogl-all-natives-macosx-universal.jar diff --git a/core/lib/jogl/jogl-all-natives-windows-amd64.jar b/swing/lib/jogl/jogl-all-natives-windows-amd64.jar similarity index 100% rename from core/lib/jogl/jogl-all-natives-windows-amd64.jar rename to swing/lib/jogl/jogl-all-natives-windows-amd64.jar diff --git a/core/lib/jogl/jogl-all-natives-windows-i586.jar b/swing/lib/jogl/jogl-all-natives-windows-i586.jar similarity index 100% rename from core/lib/jogl/jogl-all-natives-windows-i586.jar rename to swing/lib/jogl/jogl-all-natives-windows-i586.jar diff --git a/core/lib/jogl/jogl-all.jar b/swing/lib/jogl/jogl-all.jar similarity index 100% rename from core/lib/jogl/jogl-all.jar rename to swing/lib/jogl/jogl-all.jar diff --git a/core/lib/logback-classic-1.0.12.jar b/swing/lib/logback-classic-1.0.12.jar similarity index 100% rename from core/lib/logback-classic-1.0.12.jar rename to swing/lib/logback-classic-1.0.12.jar diff --git a/core/lib/logback-core-1.0.12.jar b/swing/lib/logback-core-1.0.12.jar similarity index 100% rename from core/lib/logback-core-1.0.12.jar rename to swing/lib/logback-core-1.0.12.jar diff --git a/core/lib/miglayout15-swing.jar b/swing/lib/miglayout15-swing.jar similarity index 100% rename from core/lib/miglayout15-swing.jar rename to swing/lib/miglayout15-swing.jar diff --git a/core/reference/jfreechart-1.0.15-sources.jar b/swing/reference/jfreechart-1.0.15-sources.jar similarity index 100% rename from core/reference/jfreechart-1.0.15-sources.jar rename to swing/reference/jfreechart-1.0.15-sources.jar diff --git a/core/reference/jogl-all-2.0.2-sources.jar b/swing/reference/jogl-all-2.0.2-sources.jar similarity index 100% rename from core/reference/jogl-all-2.0.2-sources.jar rename to swing/reference/jogl-all-2.0.2-sources.jar diff --git a/core/resources-src/datafiles/presets/Estes.orc b/swing/resources-src/datafiles/presets/Estes.orc similarity index 100% rename from core/resources-src/datafiles/presets/Estes.orc rename to swing/resources-src/datafiles/presets/Estes.orc diff --git a/core/resources-src/datafiles/presets/LocPrecision.orc b/swing/resources-src/datafiles/presets/LocPrecision.orc similarity index 100% rename from core/resources-src/datafiles/presets/LocPrecision.orc rename to swing/resources-src/datafiles/presets/LocPrecision.orc diff --git a/core/resources-src/datafiles/presets/Quest.orc b/swing/resources-src/datafiles/presets/Quest.orc similarity index 100% rename from core/resources-src/datafiles/presets/Quest.orc rename to swing/resources-src/datafiles/presets/Quest.orc diff --git a/core/resources-src/datafiles/presets/bluetube.orc b/swing/resources-src/datafiles/presets/bluetube.orc similarity index 100% rename from core/resources-src/datafiles/presets/bluetube.orc rename to swing/resources-src/datafiles/presets/bluetube.orc diff --git a/core/resources-src/datafiles/presets/bms.orc b/swing/resources-src/datafiles/presets/bms.orc similarity index 100% rename from core/resources-src/datafiles/presets/bms.orc rename to swing/resources-src/datafiles/presets/bms.orc diff --git a/core/resources-src/datafiles/presets/fliskits.orc b/swing/resources-src/datafiles/presets/fliskits.orc similarity index 100% rename from core/resources-src/datafiles/presets/fliskits.orc rename to swing/resources-src/datafiles/presets/fliskits.orc diff --git a/core/resources-src/datafiles/presets/giantleaprocketry.orc b/swing/resources-src/datafiles/presets/giantleaprocketry.orc similarity index 100% rename from core/resources-src/datafiles/presets/giantleaprocketry.orc rename to swing/resources-src/datafiles/presets/giantleaprocketry.orc diff --git a/core/resources-src/datafiles/presets/publicmissiles.orc b/swing/resources-src/datafiles/presets/publicmissiles.orc similarity index 100% rename from core/resources-src/datafiles/presets/publicmissiles.orc rename to swing/resources-src/datafiles/presets/publicmissiles.orc diff --git a/core/resources-src/datafiles/presets/semroc.orc b/swing/resources-src/datafiles/presets/semroc.orc similarity index 100% rename from core/resources-src/datafiles/presets/semroc.orc rename to swing/resources-src/datafiles/presets/semroc.orc diff --git a/core/resources-src/datafiles/rocksim_components/bluetube/BTDATA.CSV b/swing/resources-src/datafiles/rocksim_components/bluetube/BTDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bluetube/BTDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/bluetube/BTDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/bluetube/MATERIAL.CSV b/swing/resources-src/datafiles/rocksim_components/bluetube/MATERIAL.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bluetube/MATERIAL.CSV rename to swing/resources-src/datafiles/rocksim_components/bluetube/MATERIAL.CSV diff --git a/core/resources-src/datafiles/rocksim_components/bluetube/TCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/bluetube/TCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bluetube/TCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/bluetube/TCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/bluetube/readme.txt b/swing/resources-src/datafiles/rocksim_components/bluetube/readme.txt similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bluetube/readme.txt rename to swing/resources-src/datafiles/rocksim_components/bluetube/readme.txt diff --git a/core/resources-src/datafiles/rocksim_components/bms/BHdata.csv b/swing/resources-src/datafiles/rocksim_components/bms/BHdata.csv similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bms/BHdata.csv rename to swing/resources-src/datafiles/rocksim_components/bms/BHdata.csv diff --git a/core/resources-src/datafiles/rocksim_components/bms/BTdata.csv b/swing/resources-src/datafiles/rocksim_components/bms/BTdata.csv similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bms/BTdata.csv rename to swing/resources-src/datafiles/rocksim_components/bms/BTdata.csv diff --git a/core/resources-src/datafiles/rocksim_components/bms/CRdata.csv b/swing/resources-src/datafiles/rocksim_components/bms/CRdata.csv similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bms/CRdata.csv rename to swing/resources-src/datafiles/rocksim_components/bms/CRdata.csv diff --git a/core/resources-src/datafiles/rocksim_components/bms/LLdata.csv b/swing/resources-src/datafiles/rocksim_components/bms/LLdata.csv similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bms/LLdata.csv rename to swing/resources-src/datafiles/rocksim_components/bms/LLdata.csv diff --git a/core/resources-src/datafiles/rocksim_components/bms/MATERIAL.CSV b/swing/resources-src/datafiles/rocksim_components/bms/MATERIAL.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bms/MATERIAL.CSV rename to swing/resources-src/datafiles/rocksim_components/bms/MATERIAL.CSV diff --git a/core/resources-src/datafiles/rocksim_components/bms/NCdata.csv b/swing/resources-src/datafiles/rocksim_components/bms/NCdata.csv similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bms/NCdata.csv rename to swing/resources-src/datafiles/rocksim_components/bms/NCdata.csv diff --git a/core/resources-src/datafiles/rocksim_components/bms/TCdata.csv b/swing/resources-src/datafiles/rocksim_components/bms/TCdata.csv similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bms/TCdata.csv rename to swing/resources-src/datafiles/rocksim_components/bms/TCdata.csv diff --git a/core/resources-src/datafiles/rocksim_components/bms/TRdata.csv b/swing/resources-src/datafiles/rocksim_components/bms/TRdata.csv similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bms/TRdata.csv rename to swing/resources-src/datafiles/rocksim_components/bms/TRdata.csv diff --git a/core/resources-src/datafiles/rocksim_components/bms/ebdata.csv b/swing/resources-src/datafiles/rocksim_components/bms/ebdata.csv similarity index 100% rename from core/resources-src/datafiles/rocksim_components/bms/ebdata.csv rename to swing/resources-src/datafiles/rocksim_components/bms/ebdata.csv diff --git a/core/resources-src/datafiles/rocksim_components/estes/BTDATA.CSV b/swing/resources-src/datafiles/rocksim_components/estes/BTDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/estes/BTDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/estes/BTDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/estes/EBDATA.CSV b/swing/resources-src/datafiles/rocksim_components/estes/EBDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/estes/EBDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/estes/EBDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/estes/LLDATA.CSV b/swing/resources-src/datafiles/rocksim_components/estes/LLDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/estes/LLDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/estes/LLDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/estes/MATERIAL.CSV b/swing/resources-src/datafiles/rocksim_components/estes/MATERIAL.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/estes/MATERIAL.CSV rename to swing/resources-src/datafiles/rocksim_components/estes/MATERIAL.CSV diff --git a/core/resources-src/datafiles/rocksim_components/estes/MODATA.CSV b/swing/resources-src/datafiles/rocksim_components/estes/MODATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/estes/MODATA.CSV rename to swing/resources-src/datafiles/rocksim_components/estes/MODATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/estes/NCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/estes/NCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/estes/NCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/estes/NCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/estes/PCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/estes/PCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/estes/PCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/estes/PCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/estes/TCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/estes/TCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/estes/TCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/estes/TCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/estes/TRDATA.CSV b/swing/resources-src/datafiles/rocksim_components/estes/TRDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/estes/TRDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/estes/TRDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/giantleaprocketry/BHDATA.CSV b/swing/resources-src/datafiles/rocksim_components/giantleaprocketry/BHDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/giantleaprocketry/BHDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/giantleaprocketry/BHDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/giantleaprocketry/BTDATA.CSV b/swing/resources-src/datafiles/rocksim_components/giantleaprocketry/BTDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/giantleaprocketry/BTDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/giantleaprocketry/BTDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/giantleaprocketry/CRDATA.CSV b/swing/resources-src/datafiles/rocksim_components/giantleaprocketry/CRDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/giantleaprocketry/CRDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/giantleaprocketry/CRDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/giantleaprocketry/LLDATA.CSV b/swing/resources-src/datafiles/rocksim_components/giantleaprocketry/LLDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/giantleaprocketry/LLDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/giantleaprocketry/LLDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/giantleaprocketry/MATERIAL.CSV b/swing/resources-src/datafiles/rocksim_components/giantleaprocketry/MATERIAL.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/giantleaprocketry/MATERIAL.CSV rename to swing/resources-src/datafiles/rocksim_components/giantleaprocketry/MATERIAL.CSV diff --git a/core/resources-src/datafiles/rocksim_components/giantleaprocketry/MODATA.CSV b/swing/resources-src/datafiles/rocksim_components/giantleaprocketry/MODATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/giantleaprocketry/MODATA.CSV rename to swing/resources-src/datafiles/rocksim_components/giantleaprocketry/MODATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/giantleaprocketry/NCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/giantleaprocketry/NCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/giantleaprocketry/NCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/giantleaprocketry/NCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/giantleaprocketry/PCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/giantleaprocketry/PCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/giantleaprocketry/PCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/giantleaprocketry/PCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/giantleaprocketry/TCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/giantleaprocketry/TCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/giantleaprocketry/TCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/giantleaprocketry/TCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/BHDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/BHDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/BHDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/BHDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/BTDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/BTDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/BTDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/BTDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/CFDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/CFDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/CFDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/CFDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/CRDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/CRDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/CRDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/CRDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/EBDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/EBDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/EBDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/EBDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/FSDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/FSDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/FSDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/FSDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/GRAPHS.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/GRAPHS.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/GRAPHS.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/GRAPHS.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/LLDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/LLDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/LLDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/LLDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/MATERIAL.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/MATERIAL.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/MATERIAL.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/MATERIAL.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/MODATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/MODATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/MODATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/MODATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/NCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/NCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/NCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/NCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/PCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/PCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/PCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/PCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/SLDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/SLDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/SLDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/SLDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/STDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/STDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/STDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/STDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/TCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/TCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/TCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/TCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/publicmissiles/TRDATA.CSV b/swing/resources-src/datafiles/rocksim_components/publicmissiles/TRDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/publicmissiles/TRDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/publicmissiles/TRDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/quest/BTDATA.CSV b/swing/resources-src/datafiles/rocksim_components/quest/BTDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/quest/BTDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/quest/BTDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/quest/CRDATA.CSV b/swing/resources-src/datafiles/rocksim_components/quest/CRDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/quest/CRDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/quest/CRDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/quest/EBDATA.CSV b/swing/resources-src/datafiles/rocksim_components/quest/EBDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/quest/EBDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/quest/EBDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/quest/MATERIAL.CSV b/swing/resources-src/datafiles/rocksim_components/quest/MATERIAL.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/quest/MATERIAL.CSV rename to swing/resources-src/datafiles/rocksim_components/quest/MATERIAL.CSV diff --git a/core/resources-src/datafiles/rocksim_components/quest/NCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/quest/NCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/quest/NCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/quest/NCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/quest/PCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/quest/PCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/quest/PCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/quest/PCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/quest/STDATA.CSV b/swing/resources-src/datafiles/rocksim_components/quest/STDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/quest/STDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/quest/STDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/quest/TCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/quest/TCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/quest/TCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/quest/TCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/quest/TRDATA.CSV b/swing/resources-src/datafiles/rocksim_components/quest/TRDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/quest/TRDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/quest/TRDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/BHDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/BHDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/BHDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/BHDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/BTDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/BTDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/BTDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/BTDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/CRDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/CRDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/CRDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/CRDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/EBDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/EBDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/EBDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/EBDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/LLDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/LLDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/LLDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/LLDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/MATERIAL.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/MATERIAL.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/MATERIAL.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/MATERIAL.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/NCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/NCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/NCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/NCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/PCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/PCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/PCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/PCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/STDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/STDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/STDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/STDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/TCDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/TCDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/TCDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/TCDATA.CSV diff --git a/core/resources-src/datafiles/rocksim_components/semroc/TRDATA.CSV b/swing/resources-src/datafiles/rocksim_components/semroc/TRDATA.CSV similarity index 100% rename from core/resources-src/datafiles/rocksim_components/semroc/TRDATA.CSV rename to swing/resources-src/datafiles/rocksim_components/semroc/TRDATA.CSV diff --git a/core/resources-src/datafiles/tours/convert-images.sh b/swing/resources-src/datafiles/tours/convert-images.sh similarity index 100% rename from core/resources-src/datafiles/tours/convert-images.sh rename to swing/resources-src/datafiles/tours/convert-images.sh diff --git a/core/resources-src/datafiles/tours/creating_design/dialog-1-nosecone.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/dialog-1-nosecone.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/dialog-1-nosecone.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/dialog-1-nosecone.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/dialog-2-bodytube.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/dialog-2-bodytube.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/dialog-2-bodytube.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/dialog-2-bodytube.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/dialog-3-finset.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/dialog-3-finset.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/dialog-3-finset.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/dialog-3-finset.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/dialog-4-innertube.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/dialog-4-innertube.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/dialog-4-innertube.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/dialog-4-innertube.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/dialog-5-centeringring.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/dialog-5-centeringring.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/dialog-5-centeringring.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/dialog-5-centeringring.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/dialog-6-parachute.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/dialog-6-parachute.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/dialog-6-parachute.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/dialog-6-parachute.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/main-0-initial.png b/swing/resources-src/datafiles/tours/creating_design/main-0-initial.png similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/main-0-initial.png rename to swing/resources-src/datafiles/tours/creating_design/main-0-initial.png diff --git a/core/resources-src/datafiles/tours/creating_design/main-1-nosecone.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/main-1-nosecone.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/main-1-nosecone.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/main-1-nosecone.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/main-2-bodytube.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/main-2-bodytube.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/main-2-bodytube.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/main-2-bodytube.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/main-3-finset.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/main-3-finset.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/main-3-finset.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/main-3-finset.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/main-4-innertube.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/main-4-innertube.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/main-4-innertube.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/main-4-innertube.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/main-5-centeringring.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/main-5-centeringring.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/main-5-centeringring.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/main-5-centeringring.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/main-6-parachute.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/main-6-parachute.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/main-6-parachute.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/main-6-parachute.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/main-7-rest.xcf.gz b/swing/resources-src/datafiles/tours/creating_design/main-7-rest.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/main-7-rest.xcf.gz rename to swing/resources-src/datafiles/tours/creating_design/main-7-rest.xcf.gz diff --git a/core/resources-src/datafiles/tours/creating_design/main-8-final.png b/swing/resources-src/datafiles/tours/creating_design/main-8-final.png similarity index 100% rename from core/resources-src/datafiles/tours/creating_design/main-8-final.png rename to swing/resources-src/datafiles/tours/creating_design/main-8-final.png diff --git a/core/resources-src/datafiles/tours/introduction/advanced_features.xcf.gz b/swing/resources-src/datafiles/tours/introduction/advanced_features.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/introduction/advanced_features.xcf.gz rename to swing/resources-src/datafiles/tours/introduction/advanced_features.xcf.gz diff --git a/core/resources-src/datafiles/tours/introduction/flight_simulations.xcf.gz b/swing/resources-src/datafiles/tours/introduction/flight_simulations.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/introduction/flight_simulations.xcf.gz rename to swing/resources-src/datafiles/tours/introduction/flight_simulations.xcf.gz diff --git a/core/resources-src/datafiles/tours/introduction/logo-MANUAL.xcf.gz b/swing/resources-src/datafiles/tours/introduction/logo-MANUAL.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/introduction/logo-MANUAL.xcf.gz rename to swing/resources-src/datafiles/tours/introduction/logo-MANUAL.xcf.gz diff --git a/core/resources-src/datafiles/tours/introduction/main_window.png b/swing/resources-src/datafiles/tours/introduction/main_window.png similarity index 100% rename from core/resources-src/datafiles/tours/introduction/main_window.png rename to swing/resources-src/datafiles/tours/introduction/main_window.png diff --git a/core/resources-src/datafiles/tours/introduction/main_window_bottom.xcf.gz b/swing/resources-src/datafiles/tours/introduction/main_window_bottom.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/introduction/main_window_bottom.xcf.gz rename to swing/resources-src/datafiles/tours/introduction/main_window_bottom.xcf.gz diff --git a/core/resources-src/datafiles/tours/introduction/main_window_top.xcf.gz b/swing/resources-src/datafiles/tours/introduction/main_window_top.xcf.gz similarity index 100% rename from core/resources-src/datafiles/tours/introduction/main_window_top.xcf.gz rename to swing/resources-src/datafiles/tours/introduction/main_window_top.xcf.gz diff --git a/core/resources/datafiles/examples/A simple model rocket.ork b/swing/resources/datafiles/examples/A simple model rocket.ork similarity index 100% rename from core/resources/datafiles/examples/A simple model rocket.ork rename to swing/resources/datafiles/examples/A simple model rocket.ork diff --git a/core/resources/datafiles/examples/Apocalypse with decals.ork b/swing/resources/datafiles/examples/Apocalypse with decals.ork similarity index 100% rename from core/resources/datafiles/examples/Apocalypse with decals.ork rename to swing/resources/datafiles/examples/Apocalypse with decals.ork diff --git a/core/resources/datafiles/examples/Boosted Dart.ork b/swing/resources/datafiles/examples/Boosted Dart.ork similarity index 100% rename from core/resources/datafiles/examples/Boosted Dart.ork rename to swing/resources/datafiles/examples/Boosted Dart.ork diff --git a/core/resources/datafiles/examples/Clustered rocket design.ork b/swing/resources/datafiles/examples/Clustered rocket design.ork similarity index 100% rename from core/resources/datafiles/examples/Clustered rocket design.ork rename to swing/resources/datafiles/examples/Clustered rocket design.ork diff --git a/core/resources/datafiles/examples/High Power Airstart.ork b/swing/resources/datafiles/examples/High Power Airstart.ork similarity index 100% rename from core/resources/datafiles/examples/High Power Airstart.ork rename to swing/resources/datafiles/examples/High Power Airstart.ork diff --git a/core/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork b/swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork similarity index 100% rename from core/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork rename to swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork diff --git a/core/resources/datafiles/examples/Preset Usage.ork b/swing/resources/datafiles/examples/Preset Usage.ork similarity index 100% rename from core/resources/datafiles/examples/Preset Usage.ork rename to swing/resources/datafiles/examples/Preset Usage.ork diff --git a/core/resources/datafiles/examples/Roll-stabilized rocket.ork b/swing/resources/datafiles/examples/Roll-stabilized rocket.ork similarity index 100% rename from core/resources/datafiles/examples/Roll-stabilized rocket.ork rename to swing/resources/datafiles/examples/Roll-stabilized rocket.ork diff --git a/core/resources/datafiles/examples/Simulation listeners.ork b/swing/resources/datafiles/examples/Simulation listeners.ork similarity index 100% rename from core/resources/datafiles/examples/Simulation listeners.ork rename to swing/resources/datafiles/examples/Simulation listeners.ork diff --git a/core/resources/datafiles/examples/TARC Payloader.ork b/swing/resources/datafiles/examples/TARC Payloader.ork similarity index 100% rename from core/resources/datafiles/examples/TARC Payloader.ork rename to swing/resources/datafiles/examples/TARC Payloader.ork diff --git a/core/resources/datafiles/examples/Three-stage rocket.ork b/swing/resources/datafiles/examples/Three-stage rocket.ork similarity index 100% rename from core/resources/datafiles/examples/Three-stage rocket.ork rename to swing/resources/datafiles/examples/Three-stage rocket.ork diff --git a/core/resources/datafiles/presets/system.ser b/swing/resources/datafiles/presets/system.ser similarity index 97% rename from core/resources/datafiles/presets/system.ser rename to swing/resources/datafiles/presets/system.ser index d1da35045b1d7cae123d0d3c68f3e98862a39a3d..bd1b8da4eb962a74a223a157d34c51e832e33aa0 100644 GIT binary patch delta 2274 zcmZ`)YfM~46wcn63uOzexIB`jbfIi1RHaKJu5F?r)flv^EwolbO|3svV$?1gHHsK* zf7DlN%}qK#Oa)B{p+b4uvyv@aY=M^7Qr@pht*J>xY#WV*LdA1md*_n)=Vre1ojK<_ zXU@zG^(KXSlNyfHN>TAPskE*|O7*Uij@0%wuRmo=w8Tld=R=k_X4^@flB=Ty4HF&1 z_&wDre-CvHA=y@jZ`v~rINmSbS!-dX-_{I*@tiAGBPCd{*au#;4m7h2b$5a_Yx+MvIj@;D-NLsHggNf zg&{v0nubx1lt1N^sz&79Dbr5YIYaHNd=IrtUymL|+s3g=_+65}EGUXjX>Pm`4HXju zmbgQ{N$^ctR&|oAA4#mBPDp@bmBMO4hhF&oJ7LIT!00Mrf`boM3nLsXsxdjg|GhA* zuz-=BT+m>>$?um&6C^FBpc_t`;C8<-%BA$RYB^-}UXTL9G{?5Jnc(%aCg<3|XrZma8O_$pi$0WCQ2?$$7Np{zNhjKS0|~6)LB7vy;4RK!(r*RTs%7%_a+9 z?jhS%h$6jgZy#~9duB-v%bOy0hzXHK^^A~x)9{0XLnMQPw@36k3J=s=Cs~?J?#SI)QlREQ zDQ}sb?Qr9!-Y;am@abPhq%-zIrHQ-)#cmGG?T@o5-Y1)CJxrH zfSMl-pyr?FvDf<-Nv)w&86nQuxg;MfIJQi7aWei$!tsk0=wqC%*nuqx zCx=Q(Q#iIF&BR7WQf)}EyIJgLmzWNpMbUNY4y-7Xz3!kc7PL|~t85i)EHF>ju@_P> z?}2El)uGg@_+`h?2=K*H2bWU5BWjcrHjJXwrUZC8P7fg88^s0k?KD;0i-I&8B=98w z7QM#^4_NQB24u)S49jci8qS7AdzgEzEX_%i3<8jNPK8RTbA@l9xx zLID<2VnDgJhv3~Fk$bY_PTFChPt@*bSTj5`AZi0@*!l-`n^H7tiH98{`d;0Jzm_p^ fH>YaLq^?SK0epotk;)GFQIb;Ht+0DWT#Wb+z;utp delta 2270 zcmZ`)YfM~46wcn6%eGr!)h&=Lr3+@6N-C)Cl~Vnyq!U$b4I9te6D?LL=R(9{W%fPPCAr%h(J{ zDi!tCpyQDjk@oeWk$vzMF0B!n4@TtddB@RE(cgfE1D7#@lr=DdUvUsMu<4sfE(`|I zP(Ornq@9y4seD-8oigQO9n;joO7>HS^ySD&v~3=}jNc{s%fh1Ql4i&1&`>(wZ?PT= zOh915vZ{m3g_~JkwU7X(%7it74*l@^x5A*sfU)JmI0ql75QaHeP-$|0?>k{gVF4q% zxS-)0li$yECP*4hK{uTS6f(G(rw1fm!l0hBN`#ssI+FJY3B{xW(Fd#$dhOs8Hk+XfaK-?@oL}DPn znXFd{%P+*kWCFrrvWav4=mOeuek2~l574$ph01B&=pZi{kRkLz`6aSNv&q7jyU8vU zqDVhG&`Z4Rt{IZeawmxc;v%F@JtJh_F#O=?Aj#n1tzo?mIWPP7H{xKQH)1jOk77Lg zBzX*`$Fxw&AaO2b<5=&t2|ZpOs*-#a39R(C#SVp6NS3Bn9>20_a!`SQZ8@S6xbm~3 zzha)bFzP-W!rVQZamvqRkTtOP8j+$$o&c!qIU}B|!UvVtNv39#J91}+&}roq|$JPQ_Ey8C*zMK9KU#hKFZk&o!F9SawwxT zg=0(8Ol+)`YD0qE&15G##dP>&HQk`@z=}NVH79kmu#I|IS(9LAp?R`_J)eSk564oi z4y9hjFDs75Kp>tvxs>u9QKNii$0$l|N`R;C)&t1*Msb0i4w|a&MM0Vk68I7Ti{5X9 z2dwuQ12SYEg5`B|9cRO$eayRFmgb^K1_4Mst3svJ*?c^*NFq&Uwsg9MtM^I+&RR)2 zO@j$H{ggWotgV2HH|x=%@)88@rH`wuQXoP%h*?y739xH^c-N~zK=?Z6T=D{H35wJS z2XnNRz=uAnO_*`}qJF2h(U{{qwo}-#U3?{=ztwoX(&$>&T|`#1u+%2rXY>VfJH(fbP=Li0 z8Bnh6L3pQItheInnerTube - */ - public static InnerTube makeIndividualClusterComponent(Coordinate coord, String splitName, RocketComponent theInnerTube) { - InnerTube copy = (InnerTube) theInnerTube.copy(); - copy.setClusterConfiguration(ClusterConfiguration.SINGLE); - copy.setClusterRotation(0.0); - copy.setClusterScale(1.0); - copy.setRadialShift(coord.y, coord.z); - copy.setName(splitName); - return copy; - } + } diff --git a/core/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/MotorConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/MotorConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/RecoveryDeviceConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RecoveryDeviceConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/RecoveryDeviceConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/RecoveryDeviceConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/RocketConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/RocketConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/RocketConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/StageConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/StageConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/StageConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/StageConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java diff --git a/core/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java similarity index 100% rename from core/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java rename to swing/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java diff --git a/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java b/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java rename to swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java diff --git a/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java b/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java rename to swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java diff --git a/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java b/swing/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java rename to swing/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java diff --git a/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java b/swing/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java similarity index 100% rename from core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java rename to swing/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java diff --git a/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java b/swing/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java rename to swing/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java diff --git a/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java b/swing/src/net/sf/openrocket/gui/customexpression/VariableSelector.java similarity index 100% rename from core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java rename to swing/src/net/sf/openrocket/gui/customexpression/VariableSelector.java diff --git a/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java b/swing/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java rename to swing/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/AboutDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/AboutDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/DetailDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/DetailDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/DetailDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/DetailDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/PrintDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/PrintDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/PrintDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/PrintDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/PrintSettingsDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/PrintSettingsDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/PrintSettingsDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/PrintSettingsDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/SwingWorkerDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/SwingWorkerDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/SwingWorkerDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/SwingWorkerDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/WarningDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/WarningDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/DeploymentSelectionDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/DeploymentSelectionDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/DeploymentSelectionDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/DeploymentSelectionDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/FlightConfigurationDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/FlightConfigurationDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/FlightConfigurationDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/FlightConfigurationDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/IgnitionSelectionDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/IgnitionSelectionDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/IgnitionSelectionDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/IgnitionSelectionDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationTableModel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationTableModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationTableModel.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationTableModel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RecoveryConfigurationPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RecoveryConfigurationPanel.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RecoveryConfigurationPanel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationConfigurationPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationConfigurationPanel.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationConfigurationPanel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/CloseableDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/CloseableDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/CloseableDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/CloseableDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/MotorSelector.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorSelector.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/MotorSelector.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/MotorSelector.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorClass.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorClass.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorClass.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorClass.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorHolder.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorHolder.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorHolder.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorHolder.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorColumns.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorColumns.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorColumns.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorColumns.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorComparator.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorComparator.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorComparator.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorComparator.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorDatabaseModel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorDatabaseModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorDatabaseModel.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorDatabaseModel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/optimization/FunctionEvaluationData.java b/swing/src/net/sf/openrocket/gui/dialogs/optimization/FunctionEvaluationData.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/optimization/FunctionEvaluationData.java rename to swing/src/net/sf/openrocket/gui/dialogs/optimization/FunctionEvaluationData.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationStepData.java b/swing/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationStepData.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationStepData.java rename to swing/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationStepData.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java b/swing/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java rename to swing/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java b/swing/src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java rename to swing/src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java rename to swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetChooserDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetChooserDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetChooserDialog.java rename to swing/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetChooserDialog.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetRowFilter.java b/swing/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetRowFilter.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetRowFilter.java rename to swing/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetRowFilter.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTable.java b/swing/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTable.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTable.java rename to swing/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTable.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTableColumn.java b/swing/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTableColumn.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTableColumn.java rename to swing/src/net/sf/openrocket/gui/dialogs/preset/ComponentPresetTableColumn.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/preset/XTableColumnModel.java b/swing/src/net/sf/openrocket/gui/dialogs/preset/XTableColumnModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/preset/XTableColumnModel.java rename to swing/src/net/sf/openrocket/gui/dialogs/preset/XTableColumnModel.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/FigureRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/FigureRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/FigureRenderer.java rename to swing/src/net/sf/openrocket/gui/figure3d/FigureRenderer.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java rename to swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java b/swing/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java rename to swing/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/RocketRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/RocketRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/RocketRenderer.java rename to swing/src/net/sf/openrocket/gui/figure3d/RocketRenderer.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/UnfinishedRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/UnfinishedRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/UnfinishedRenderer.java rename to swing/src/net/sf/openrocket/gui/figure3d/UnfinishedRenderer.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/geometry/ComponentRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/ComponentRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/geometry/ComponentRenderer.java rename to swing/src/net/sf/openrocket/gui/figure3d/geometry/ComponentRenderer.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/geometry/DisplayListComponentRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/DisplayListComponentRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/geometry/DisplayListComponentRenderer.java rename to swing/src/net/sf/openrocket/gui/figure3d/geometry/DisplayListComponentRenderer.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java rename to swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/geometry/Geometry.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/Geometry.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/geometry/Geometry.java rename to swing/src/net/sf/openrocket/gui/figure3d/geometry/Geometry.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/geometry/MassObjectRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/MassObjectRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/geometry/MassObjectRenderer.java rename to swing/src/net/sf/openrocket/gui/figure3d/geometry/MassObjectRenderer.java diff --git a/core/src/net/sf/openrocket/gui/figure3d/geometry/TransitionRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/TransitionRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figure3d/geometry/TransitionRenderer.java rename to swing/src/net/sf/openrocket/gui/figure3d/geometry/TransitionRenderer.java diff --git a/core/src/net/sf/openrocket/gui/figureelements/CGCaret.java b/swing/src/net/sf/openrocket/gui/figureelements/CGCaret.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figureelements/CGCaret.java rename to swing/src/net/sf/openrocket/gui/figureelements/CGCaret.java diff --git a/core/src/net/sf/openrocket/gui/figureelements/CPCaret.java b/swing/src/net/sf/openrocket/gui/figureelements/CPCaret.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figureelements/CPCaret.java rename to swing/src/net/sf/openrocket/gui/figureelements/CPCaret.java diff --git a/core/src/net/sf/openrocket/gui/figureelements/Caret.java b/swing/src/net/sf/openrocket/gui/figureelements/Caret.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figureelements/Caret.java rename to swing/src/net/sf/openrocket/gui/figureelements/Caret.java diff --git a/core/src/net/sf/openrocket/gui/figureelements/FigureElement.java b/swing/src/net/sf/openrocket/gui/figureelements/FigureElement.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figureelements/FigureElement.java rename to swing/src/net/sf/openrocket/gui/figureelements/FigureElement.java diff --git a/core/src/net/sf/openrocket/gui/figureelements/RocketInfo.java b/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java similarity index 100% rename from core/src/net/sf/openrocket/gui/figureelements/RocketInfo.java rename to swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java diff --git a/core/src/net/sf/openrocket/gui/help/tours/GuidedTourSelectionDialog.java b/swing/src/net/sf/openrocket/gui/help/tours/GuidedTourSelectionDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/help/tours/GuidedTourSelectionDialog.java rename to swing/src/net/sf/openrocket/gui/help/tours/GuidedTourSelectionDialog.java diff --git a/core/src/net/sf/openrocket/gui/help/tours/Slide.java b/swing/src/net/sf/openrocket/gui/help/tours/Slide.java similarity index 100% rename from core/src/net/sf/openrocket/gui/help/tours/Slide.java rename to swing/src/net/sf/openrocket/gui/help/tours/Slide.java diff --git a/core/src/net/sf/openrocket/gui/help/tours/SlideSet.java b/swing/src/net/sf/openrocket/gui/help/tours/SlideSet.java similarity index 100% rename from core/src/net/sf/openrocket/gui/help/tours/SlideSet.java rename to swing/src/net/sf/openrocket/gui/help/tours/SlideSet.java diff --git a/core/src/net/sf/openrocket/gui/help/tours/SlideSetLoader.java b/swing/src/net/sf/openrocket/gui/help/tours/SlideSetLoader.java similarity index 100% rename from core/src/net/sf/openrocket/gui/help/tours/SlideSetLoader.java rename to swing/src/net/sf/openrocket/gui/help/tours/SlideSetLoader.java diff --git a/core/src/net/sf/openrocket/gui/help/tours/SlideSetManager.java b/swing/src/net/sf/openrocket/gui/help/tours/SlideSetManager.java similarity index 100% rename from core/src/net/sf/openrocket/gui/help/tours/SlideSetManager.java rename to swing/src/net/sf/openrocket/gui/help/tours/SlideSetManager.java diff --git a/core/src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java b/swing/src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java similarity index 100% rename from core/src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java rename to swing/src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java diff --git a/core/src/net/sf/openrocket/gui/help/tours/SlideShowDialog.java b/swing/src/net/sf/openrocket/gui/help/tours/SlideShowDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/help/tours/SlideShowDialog.java rename to swing/src/net/sf/openrocket/gui/help/tours/SlideShowDialog.java diff --git a/core/src/net/sf/openrocket/gui/help/tours/SlideShowLinkListener.java b/swing/src/net/sf/openrocket/gui/help/tours/SlideShowLinkListener.java similarity index 100% rename from core/src/net/sf/openrocket/gui/help/tours/SlideShowLinkListener.java rename to swing/src/net/sf/openrocket/gui/help/tours/SlideShowLinkListener.java diff --git a/core/src/net/sf/openrocket/gui/help/tours/TextLineReader.java b/swing/src/net/sf/openrocket/gui/help/tours/TextLineReader.java similarity index 100% rename from core/src/net/sf/openrocket/gui/help/tours/TextLineReader.java rename to swing/src/net/sf/openrocket/gui/help/tours/TextLineReader.java diff --git a/core/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/BasicFrame.java rename to swing/src/net/sf/openrocket/gui/main/BasicFrame.java diff --git a/core/src/net/sf/openrocket/gui/main/ClipboardListener.java b/swing/src/net/sf/openrocket/gui/main/ClipboardListener.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/ClipboardListener.java rename to swing/src/net/sf/openrocket/gui/main/ClipboardListener.java diff --git a/core/src/net/sf/openrocket/gui/main/ComponentAddButtons.java b/swing/src/net/sf/openrocket/gui/main/ComponentAddButtons.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/ComponentAddButtons.java rename to swing/src/net/sf/openrocket/gui/main/ComponentAddButtons.java diff --git a/core/src/net/sf/openrocket/gui/main/ComponentIcons.java b/swing/src/net/sf/openrocket/gui/main/ComponentIcons.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/ComponentIcons.java rename to swing/src/net/sf/openrocket/gui/main/ComponentIcons.java diff --git a/core/src/net/sf/openrocket/gui/main/DocumentSelectionListener.java b/swing/src/net/sf/openrocket/gui/main/DocumentSelectionListener.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/DocumentSelectionListener.java rename to swing/src/net/sf/openrocket/gui/main/DocumentSelectionListener.java diff --git a/core/src/net/sf/openrocket/gui/main/DocumentSelectionModel.java b/swing/src/net/sf/openrocket/gui/main/DocumentSelectionModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/DocumentSelectionModel.java rename to swing/src/net/sf/openrocket/gui/main/DocumentSelectionModel.java diff --git a/core/src/net/sf/openrocket/gui/main/ExampleDesignFile.java b/swing/src/net/sf/openrocket/gui/main/ExampleDesignFile.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/ExampleDesignFile.java rename to swing/src/net/sf/openrocket/gui/main/ExampleDesignFile.java diff --git a/core/src/net/sf/openrocket/gui/main/ExampleDesignFileAction.java b/swing/src/net/sf/openrocket/gui/main/ExampleDesignFileAction.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/ExampleDesignFileAction.java rename to swing/src/net/sf/openrocket/gui/main/ExampleDesignFileAction.java diff --git a/core/src/net/sf/openrocket/gui/main/MRUDesignFile.java b/swing/src/net/sf/openrocket/gui/main/MRUDesignFile.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/MRUDesignFile.java rename to swing/src/net/sf/openrocket/gui/main/MRUDesignFile.java diff --git a/core/src/net/sf/openrocket/gui/main/MRUDesignFileAction.java b/swing/src/net/sf/openrocket/gui/main/MRUDesignFileAction.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/MRUDesignFileAction.java rename to swing/src/net/sf/openrocket/gui/main/MRUDesignFileAction.java diff --git a/core/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java b/swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java rename to swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java diff --git a/core/src/net/sf/openrocket/gui/main/RocketActions.java b/swing/src/net/sf/openrocket/gui/main/RocketActions.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/RocketActions.java rename to swing/src/net/sf/openrocket/gui/main/RocketActions.java diff --git a/core/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/SimulationPanel.java rename to swing/src/net/sf/openrocket/gui/main/SimulationPanel.java diff --git a/core/src/net/sf/openrocket/gui/main/Splash.java b/swing/src/net/sf/openrocket/gui/main/Splash.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/Splash.java rename to swing/src/net/sf/openrocket/gui/main/Splash.java diff --git a/core/src/net/sf/openrocket/gui/main/SwingExceptionHandler.java b/swing/src/net/sf/openrocket/gui/main/SwingExceptionHandler.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/SwingExceptionHandler.java rename to swing/src/net/sf/openrocket/gui/main/SwingExceptionHandler.java diff --git a/core/src/net/sf/openrocket/gui/main/UndoRedoAction.java b/swing/src/net/sf/openrocket/gui/main/UndoRedoAction.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/UndoRedoAction.java rename to swing/src/net/sf/openrocket/gui/main/UndoRedoAction.java diff --git a/core/src/net/sf/openrocket/gui/main/componenttree/ComponentTree.java b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTree.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/componenttree/ComponentTree.java rename to swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTree.java diff --git a/core/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java rename to swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java diff --git a/core/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java rename to swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java diff --git a/core/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java rename to swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java diff --git a/core/src/net/sf/openrocket/gui/main/componenttree/RocketComponentTransferable.java b/swing/src/net/sf/openrocket/gui/main/componenttree/RocketComponentTransferable.java similarity index 100% rename from core/src/net/sf/openrocket/gui/main/componenttree/RocketComponentTransferable.java rename to swing/src/net/sf/openrocket/gui/main/componenttree/RocketComponentTransferable.java diff --git a/core/src/net/sf/openrocket/gui/plot/Axis.java b/swing/src/net/sf/openrocket/gui/plot/Axis.java similarity index 100% rename from core/src/net/sf/openrocket/gui/plot/Axis.java rename to swing/src/net/sf/openrocket/gui/plot/Axis.java diff --git a/core/src/net/sf/openrocket/gui/plot/EventGraphics.java b/swing/src/net/sf/openrocket/gui/plot/EventGraphics.java similarity index 100% rename from core/src/net/sf/openrocket/gui/plot/EventGraphics.java rename to swing/src/net/sf/openrocket/gui/plot/EventGraphics.java diff --git a/core/src/net/sf/openrocket/gui/plot/PlotConfiguration.java b/swing/src/net/sf/openrocket/gui/plot/PlotConfiguration.java similarity index 100% rename from core/src/net/sf/openrocket/gui/plot/PlotConfiguration.java rename to swing/src/net/sf/openrocket/gui/plot/PlotConfiguration.java diff --git a/core/src/net/sf/openrocket/gui/plot/SimulationChart.java b/swing/src/net/sf/openrocket/gui/plot/SimulationChart.java similarity index 100% rename from core/src/net/sf/openrocket/gui/plot/SimulationChart.java rename to swing/src/net/sf/openrocket/gui/plot/SimulationChart.java diff --git a/core/src/net/sf/openrocket/gui/plot/SimulationPlot.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java similarity index 100% rename from core/src/net/sf/openrocket/gui/plot/SimulationPlot.java rename to swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java diff --git a/core/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java rename to swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java diff --git a/core/src/net/sf/openrocket/gui/plot/Util.java b/swing/src/net/sf/openrocket/gui/plot/Util.java similarity index 100% rename from core/src/net/sf/openrocket/gui/plot/Util.java rename to swing/src/net/sf/openrocket/gui/plot/Util.java diff --git a/core/src/net/sf/openrocket/gui/preset/ButtonColumn.java b/swing/src/net/sf/openrocket/gui/preset/ButtonColumn.java similarity index 100% rename from core/src/net/sf/openrocket/gui/preset/ButtonColumn.java rename to swing/src/net/sf/openrocket/gui/preset/ButtonColumn.java diff --git a/core/src/net/sf/openrocket/gui/preset/DeselectableComboBox.java b/swing/src/net/sf/openrocket/gui/preset/DeselectableComboBox.java similarity index 100% rename from core/src/net/sf/openrocket/gui/preset/DeselectableComboBox.java rename to swing/src/net/sf/openrocket/gui/preset/DeselectableComboBox.java diff --git a/core/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java b/swing/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java rename to swing/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java diff --git a/core/src/net/sf/openrocket/gui/preset/MaterialModel.java b/swing/src/net/sf/openrocket/gui/preset/MaterialModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/preset/MaterialModel.java rename to swing/src/net/sf/openrocket/gui/preset/MaterialModel.java diff --git a/core/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java b/swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java rename to swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java diff --git a/core/src/net/sf/openrocket/gui/preset/PresetResultListener.java b/swing/src/net/sf/openrocket/gui/preset/PresetResultListener.java similarity index 100% rename from core/src/net/sf/openrocket/gui/preset/PresetResultListener.java rename to swing/src/net/sf/openrocket/gui/preset/PresetResultListener.java diff --git a/core/src/net/sf/openrocket/gui/print/AbstractPrintable.java b/swing/src/net/sf/openrocket/gui/print/AbstractPrintable.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/AbstractPrintable.java rename to swing/src/net/sf/openrocket/gui/print/AbstractPrintable.java diff --git a/core/src/net/sf/openrocket/gui/print/DesignReport.java b/swing/src/net/sf/openrocket/gui/print/DesignReport.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/DesignReport.java rename to swing/src/net/sf/openrocket/gui/print/DesignReport.java diff --git a/core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java b/swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java rename to swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java diff --git a/core/src/net/sf/openrocket/gui/print/ITextHelper.java b/swing/src/net/sf/openrocket/gui/print/ITextHelper.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/ITextHelper.java rename to swing/src/net/sf/openrocket/gui/print/ITextHelper.java diff --git a/core/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java b/swing/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java rename to swing/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java diff --git a/core/src/net/sf/openrocket/gui/print/PDFPrintStreamDoc.java b/swing/src/net/sf/openrocket/gui/print/PDFPrintStreamDoc.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PDFPrintStreamDoc.java rename to swing/src/net/sf/openrocket/gui/print/PDFPrintStreamDoc.java diff --git a/core/src/net/sf/openrocket/gui/print/PaperOrientation.java b/swing/src/net/sf/openrocket/gui/print/PaperOrientation.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PaperOrientation.java rename to swing/src/net/sf/openrocket/gui/print/PaperOrientation.java diff --git a/core/src/net/sf/openrocket/gui/print/PaperSize.java b/swing/src/net/sf/openrocket/gui/print/PaperSize.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PaperSize.java rename to swing/src/net/sf/openrocket/gui/print/PaperSize.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintController.java b/swing/src/net/sf/openrocket/gui/print/PrintController.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintController.java rename to swing/src/net/sf/openrocket/gui/print/PrintController.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintFigure.java b/swing/src/net/sf/openrocket/gui/print/PrintFigure.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintFigure.java rename to swing/src/net/sf/openrocket/gui/print/PrintFigure.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintSettings.java b/swing/src/net/sf/openrocket/gui/print/PrintSettings.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintSettings.java rename to swing/src/net/sf/openrocket/gui/print/PrintSettings.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java b/swing/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java rename to swing/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintUnit.java b/swing/src/net/sf/openrocket/gui/print/PrintUnit.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintUnit.java rename to swing/src/net/sf/openrocket/gui/print/PrintUnit.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintUtilities.java b/swing/src/net/sf/openrocket/gui/print/PrintUtilities.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintUtilities.java rename to swing/src/net/sf/openrocket/gui/print/PrintUtilities.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintableCenteringRing.java b/swing/src/net/sf/openrocket/gui/print/PrintableCenteringRing.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintableCenteringRing.java rename to swing/src/net/sf/openrocket/gui/print/PrintableCenteringRing.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintableComponent.java b/swing/src/net/sf/openrocket/gui/print/PrintableComponent.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintableComponent.java rename to swing/src/net/sf/openrocket/gui/print/PrintableComponent.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintableContext.java b/swing/src/net/sf/openrocket/gui/print/PrintableContext.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintableContext.java rename to swing/src/net/sf/openrocket/gui/print/PrintableContext.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintableFinSet.java b/swing/src/net/sf/openrocket/gui/print/PrintableFinSet.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintableFinSet.java rename to swing/src/net/sf/openrocket/gui/print/PrintableFinSet.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintableNoseCone.java b/swing/src/net/sf/openrocket/gui/print/PrintableNoseCone.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintableNoseCone.java rename to swing/src/net/sf/openrocket/gui/print/PrintableNoseCone.java diff --git a/core/src/net/sf/openrocket/gui/print/PrintableTransition.java b/swing/src/net/sf/openrocket/gui/print/PrintableTransition.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/PrintableTransition.java rename to swing/src/net/sf/openrocket/gui/print/PrintableTransition.java diff --git a/core/src/net/sf/openrocket/gui/print/TemplateProperties.java b/swing/src/net/sf/openrocket/gui/print/TemplateProperties.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/TemplateProperties.java rename to swing/src/net/sf/openrocket/gui/print/TemplateProperties.java diff --git a/core/src/net/sf/openrocket/gui/print/components/CheckBoxNode.java b/swing/src/net/sf/openrocket/gui/print/components/CheckBoxNode.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/components/CheckBoxNode.java rename to swing/src/net/sf/openrocket/gui/print/components/CheckBoxNode.java diff --git a/core/src/net/sf/openrocket/gui/print/components/CheckTreeCellRenderer.java b/swing/src/net/sf/openrocket/gui/print/components/CheckTreeCellRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/components/CheckTreeCellRenderer.java rename to swing/src/net/sf/openrocket/gui/print/components/CheckTreeCellRenderer.java diff --git a/core/src/net/sf/openrocket/gui/print/components/CheckTreeManager.java b/swing/src/net/sf/openrocket/gui/print/components/CheckTreeManager.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/components/CheckTreeManager.java rename to swing/src/net/sf/openrocket/gui/print/components/CheckTreeManager.java diff --git a/core/src/net/sf/openrocket/gui/print/components/CheckTreeSelectionModel.java b/swing/src/net/sf/openrocket/gui/print/components/CheckTreeSelectionModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/components/CheckTreeSelectionModel.java rename to swing/src/net/sf/openrocket/gui/print/components/CheckTreeSelectionModel.java diff --git a/core/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java b/swing/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java rename to swing/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java diff --git a/core/src/net/sf/openrocket/gui/print/components/Rule.java b/swing/src/net/sf/openrocket/gui/print/components/Rule.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/components/Rule.java rename to swing/src/net/sf/openrocket/gui/print/components/Rule.java diff --git a/core/src/net/sf/openrocket/gui/print/visitor/AbstractPrintStrategy.java b/swing/src/net/sf/openrocket/gui/print/visitor/AbstractPrintStrategy.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/visitor/AbstractPrintStrategy.java rename to swing/src/net/sf/openrocket/gui/print/visitor/AbstractPrintStrategy.java diff --git a/core/src/net/sf/openrocket/gui/print/visitor/CenteringRingStrategy.java b/swing/src/net/sf/openrocket/gui/print/visitor/CenteringRingStrategy.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/visitor/CenteringRingStrategy.java rename to swing/src/net/sf/openrocket/gui/print/visitor/CenteringRingStrategy.java diff --git a/core/src/net/sf/openrocket/gui/print/visitor/Dimension.java b/swing/src/net/sf/openrocket/gui/print/visitor/Dimension.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/visitor/Dimension.java rename to swing/src/net/sf/openrocket/gui/print/visitor/Dimension.java diff --git a/core/src/net/sf/openrocket/gui/print/visitor/FinMarkingGuideStrategy.java b/swing/src/net/sf/openrocket/gui/print/visitor/FinMarkingGuideStrategy.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/visitor/FinMarkingGuideStrategy.java rename to swing/src/net/sf/openrocket/gui/print/visitor/FinMarkingGuideStrategy.java diff --git a/core/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java b/swing/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java rename to swing/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java diff --git a/core/src/net/sf/openrocket/gui/print/visitor/PageFitPrintStrategy.java b/swing/src/net/sf/openrocket/gui/print/visitor/PageFitPrintStrategy.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/visitor/PageFitPrintStrategy.java rename to swing/src/net/sf/openrocket/gui/print/visitor/PageFitPrintStrategy.java diff --git a/core/src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java b/swing/src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java rename to swing/src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java diff --git a/core/src/net/sf/openrocket/gui/print/visitor/PartsListVisitorStrategy.java b/swing/src/net/sf/openrocket/gui/print/visitor/PartsListVisitorStrategy.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/visitor/PartsListVisitorStrategy.java rename to swing/src/net/sf/openrocket/gui/print/visitor/PartsListVisitorStrategy.java diff --git a/core/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java b/swing/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java similarity index 100% rename from core/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java rename to swing/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java diff --git a/core/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java similarity index 100% rename from core/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java rename to swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java diff --git a/core/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java similarity index 100% rename from core/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java rename to swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java diff --git a/core/src/net/sf/openrocket/gui/rocketfigure/LaunchLugShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/LaunchLugShapes.java similarity index 100% rename from core/src/net/sf/openrocket/gui/rocketfigure/LaunchLugShapes.java rename to swing/src/net/sf/openrocket/gui/rocketfigure/LaunchLugShapes.java diff --git a/core/src/net/sf/openrocket/gui/rocketfigure/MassObjectShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/MassObjectShapes.java similarity index 100% rename from core/src/net/sf/openrocket/gui/rocketfigure/MassObjectShapes.java rename to swing/src/net/sf/openrocket/gui/rocketfigure/MassObjectShapes.java diff --git a/core/src/net/sf/openrocket/gui/rocketfigure/RingComponentShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/RingComponentShapes.java similarity index 100% rename from core/src/net/sf/openrocket/gui/rocketfigure/RingComponentShapes.java rename to swing/src/net/sf/openrocket/gui/rocketfigure/RingComponentShapes.java diff --git a/core/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java similarity index 100% rename from core/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java rename to swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShapes.java diff --git a/core/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java similarity index 100% rename from core/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java rename to swing/src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java diff --git a/core/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java similarity index 100% rename from core/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java rename to swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java diff --git a/core/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java similarity index 100% rename from core/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java rename to swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java diff --git a/core/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java similarity index 100% rename from core/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java rename to swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java diff --git a/core/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java similarity index 100% rename from core/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java rename to swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java diff --git a/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java rename to swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java diff --git a/core/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java similarity index 100% rename from core/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java rename to swing/src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java diff --git a/core/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java similarity index 100% rename from core/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java rename to swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java diff --git a/core/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java similarity index 100% rename from core/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java rename to swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java diff --git a/core/src/net/sf/openrocket/gui/simulation/SimulationConditionsPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationConditionsPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/simulation/SimulationConditionsPanel.java rename to swing/src/net/sf/openrocket/gui/simulation/SimulationConditionsPanel.java diff --git a/core/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java rename to swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java diff --git a/core/src/net/sf/openrocket/gui/simulation/SimulationExportPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationExportPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/simulation/SimulationExportPanel.java rename to swing/src/net/sf/openrocket/gui/simulation/SimulationExportPanel.java diff --git a/core/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java rename to swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java diff --git a/core/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java rename to swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java diff --git a/core/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java rename to swing/src/net/sf/openrocket/gui/simulation/SimulationRunDialog.java diff --git a/core/src/net/sf/openrocket/gui/simulation/SimulationWarningDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationWarningDialog.java similarity index 100% rename from core/src/net/sf/openrocket/gui/simulation/SimulationWarningDialog.java rename to swing/src/net/sf/openrocket/gui/simulation/SimulationWarningDialog.java diff --git a/core/src/net/sf/openrocket/gui/simulation/SimulationWorker.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationWorker.java similarity index 100% rename from core/src/net/sf/openrocket/gui/simulation/SimulationWorker.java rename to swing/src/net/sf/openrocket/gui/simulation/SimulationWorker.java diff --git a/core/src/net/sf/openrocket/gui/util/ColorConversion.java b/swing/src/net/sf/openrocket/gui/util/ColorConversion.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/ColorConversion.java rename to swing/src/net/sf/openrocket/gui/util/ColorConversion.java diff --git a/core/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitor.java b/swing/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitor.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitor.java rename to swing/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitor.java diff --git a/core/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitorInputStream.java b/swing/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitorInputStream.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitorInputStream.java rename to swing/src/net/sf/openrocket/gui/util/ConcurrentProgressMonitorInputStream.java diff --git a/core/src/net/sf/openrocket/gui/util/CustomFinImporter.java b/swing/src/net/sf/openrocket/gui/util/CustomFinImporter.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/CustomFinImporter.java rename to swing/src/net/sf/openrocket/gui/util/CustomFinImporter.java diff --git a/core/src/net/sf/openrocket/gui/util/EditDecalHelper.java b/swing/src/net/sf/openrocket/gui/util/EditDecalHelper.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/EditDecalHelper.java rename to swing/src/net/sf/openrocket/gui/util/EditDecalHelper.java diff --git a/core/src/net/sf/openrocket/gui/util/FileHelper.java b/swing/src/net/sf/openrocket/gui/util/FileHelper.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/FileHelper.java rename to swing/src/net/sf/openrocket/gui/util/FileHelper.java diff --git a/core/src/net/sf/openrocket/gui/util/GUIUtil.java b/swing/src/net/sf/openrocket/gui/util/GUIUtil.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/GUIUtil.java rename to swing/src/net/sf/openrocket/gui/util/GUIUtil.java diff --git a/core/src/net/sf/openrocket/gui/util/Icons.java b/swing/src/net/sf/openrocket/gui/util/Icons.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/Icons.java rename to swing/src/net/sf/openrocket/gui/util/Icons.java diff --git a/core/src/net/sf/openrocket/gui/util/OpenFileWorker.java b/swing/src/net/sf/openrocket/gui/util/OpenFileWorker.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/OpenFileWorker.java rename to swing/src/net/sf/openrocket/gui/util/OpenFileWorker.java diff --git a/core/src/net/sf/openrocket/gui/util/ProgressOutputStream.java b/swing/src/net/sf/openrocket/gui/util/ProgressOutputStream.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/ProgressOutputStream.java rename to swing/src/net/sf/openrocket/gui/util/ProgressOutputStream.java diff --git a/core/src/net/sf/openrocket/gui/util/SaveCSVWorker.java b/swing/src/net/sf/openrocket/gui/util/SaveCSVWorker.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/SaveCSVWorker.java rename to swing/src/net/sf/openrocket/gui/util/SaveCSVWorker.java diff --git a/core/src/net/sf/openrocket/gui/util/SaveFileWorker.java b/swing/src/net/sf/openrocket/gui/util/SaveFileWorker.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/SaveFileWorker.java rename to swing/src/net/sf/openrocket/gui/util/SaveFileWorker.java diff --git a/core/src/net/sf/openrocket/gui/util/SwingPreferences.java b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/SwingPreferences.java rename to swing/src/net/sf/openrocket/gui/util/SwingPreferences.java diff --git a/core/src/net/sf/openrocket/gui/watcher/FileWatcher.java b/swing/src/net/sf/openrocket/gui/watcher/FileWatcher.java similarity index 100% rename from core/src/net/sf/openrocket/gui/watcher/FileWatcher.java rename to swing/src/net/sf/openrocket/gui/watcher/FileWatcher.java diff --git a/core/src/net/sf/openrocket/gui/watcher/WatchEvent.java b/swing/src/net/sf/openrocket/gui/watcher/WatchEvent.java similarity index 100% rename from core/src/net/sf/openrocket/gui/watcher/WatchEvent.java rename to swing/src/net/sf/openrocket/gui/watcher/WatchEvent.java diff --git a/core/src/net/sf/openrocket/gui/watcher/WatchKey.java b/swing/src/net/sf/openrocket/gui/watcher/WatchKey.java similarity index 100% rename from core/src/net/sf/openrocket/gui/watcher/WatchKey.java rename to swing/src/net/sf/openrocket/gui/watcher/WatchKey.java diff --git a/core/src/net/sf/openrocket/gui/watcher/WatchService.java b/swing/src/net/sf/openrocket/gui/watcher/WatchService.java similarity index 100% rename from core/src/net/sf/openrocket/gui/watcher/WatchService.java rename to swing/src/net/sf/openrocket/gui/watcher/WatchService.java diff --git a/core/src/net/sf/openrocket/gui/watcher/WatchServiceImpl.java b/swing/src/net/sf/openrocket/gui/watcher/WatchServiceImpl.java similarity index 100% rename from core/src/net/sf/openrocket/gui/watcher/WatchServiceImpl.java rename to swing/src/net/sf/openrocket/gui/watcher/WatchServiceImpl.java diff --git a/core/src/net/sf/openrocket/gui/watcher/Watchable.java b/swing/src/net/sf/openrocket/gui/watcher/Watchable.java similarity index 100% rename from core/src/net/sf/openrocket/gui/watcher/Watchable.java rename to swing/src/net/sf/openrocket/gui/watcher/Watchable.java diff --git a/core/src/net/sf/openrocket/logging/BufferLogger.java b/swing/src/net/sf/openrocket/logging/BufferLogger.java similarity index 100% rename from core/src/net/sf/openrocket/logging/BufferLogger.java rename to swing/src/net/sf/openrocket/logging/BufferLogger.java diff --git a/core/src/net/sf/openrocket/logging/CyclicBuffer.java b/swing/src/net/sf/openrocket/logging/CyclicBuffer.java similarity index 100% rename from core/src/net/sf/openrocket/logging/CyclicBuffer.java rename to swing/src/net/sf/openrocket/logging/CyclicBuffer.java diff --git a/core/src/net/sf/openrocket/logging/DelegatorLogger.java b/swing/src/net/sf/openrocket/logging/DelegatorLogger.java similarity index 100% rename from core/src/net/sf/openrocket/logging/DelegatorLogger.java rename to swing/src/net/sf/openrocket/logging/DelegatorLogger.java diff --git a/core/src/net/sf/openrocket/logging/LogHelper.java b/swing/src/net/sf/openrocket/logging/LogHelper.java similarity index 100% rename from core/src/net/sf/openrocket/logging/LogHelper.java rename to swing/src/net/sf/openrocket/logging/LogHelper.java diff --git a/core/src/net/sf/openrocket/logging/LogLevel.java b/swing/src/net/sf/openrocket/logging/LogLevel.java similarity index 100% rename from core/src/net/sf/openrocket/logging/LogLevel.java rename to swing/src/net/sf/openrocket/logging/LogLevel.java diff --git a/core/src/net/sf/openrocket/logging/LogLevelBufferLogger.java b/swing/src/net/sf/openrocket/logging/LogLevelBufferLogger.java similarity index 100% rename from core/src/net/sf/openrocket/logging/LogLevelBufferLogger.java rename to swing/src/net/sf/openrocket/logging/LogLevelBufferLogger.java diff --git a/core/src/net/sf/openrocket/logging/LogLine.java b/swing/src/net/sf/openrocket/logging/LogLine.java similarity index 100% rename from core/src/net/sf/openrocket/logging/LogLine.java rename to swing/src/net/sf/openrocket/logging/LogLine.java diff --git a/core/src/net/sf/openrocket/logging/LogbackBufferLoggerAdaptor.java b/swing/src/net/sf/openrocket/logging/LogbackBufferLoggerAdaptor.java similarity index 100% rename from core/src/net/sf/openrocket/logging/LogbackBufferLoggerAdaptor.java rename to swing/src/net/sf/openrocket/logging/LogbackBufferLoggerAdaptor.java diff --git a/core/src/net/sf/openrocket/logging/LoggingSystemSetup.java b/swing/src/net/sf/openrocket/logging/LoggingSystemSetup.java similarity index 100% rename from core/src/net/sf/openrocket/logging/LoggingSystemSetup.java rename to swing/src/net/sf/openrocket/logging/LoggingSystemSetup.java diff --git a/core/src/net/sf/openrocket/logging/PrintStreamLogger.java b/swing/src/net/sf/openrocket/logging/PrintStreamLogger.java similarity index 100% rename from core/src/net/sf/openrocket/logging/PrintStreamLogger.java rename to swing/src/net/sf/openrocket/logging/PrintStreamLogger.java diff --git a/core/src/net/sf/openrocket/logging/PrintStreamToSLF4J.java b/swing/src/net/sf/openrocket/logging/PrintStreamToSLF4J.java similarity index 100% rename from core/src/net/sf/openrocket/logging/PrintStreamToSLF4J.java rename to swing/src/net/sf/openrocket/logging/PrintStreamToSLF4J.java diff --git a/core/src/net/sf/openrocket/logging/StackTraceWriter.java b/swing/src/net/sf/openrocket/logging/StackTraceWriter.java similarity index 100% rename from core/src/net/sf/openrocket/logging/StackTraceWriter.java rename to swing/src/net/sf/openrocket/logging/StackTraceWriter.java diff --git a/core/src/net/sf/openrocket/logging/TraceException.java b/swing/src/net/sf/openrocket/logging/TraceException.java similarity index 100% rename from core/src/net/sf/openrocket/logging/TraceException.java rename to swing/src/net/sf/openrocket/logging/TraceException.java diff --git a/core/src/net/sf/openrocket/startup/GuiModule.java b/swing/src/net/sf/openrocket/startup/GuiModule.java similarity index 100% rename from core/src/net/sf/openrocket/startup/GuiModule.java rename to swing/src/net/sf/openrocket/startup/GuiModule.java diff --git a/core/src/net/sf/openrocket/startup/OSXSetup.java b/swing/src/net/sf/openrocket/startup/OSXSetup.java similarity index 100% rename from core/src/net/sf/openrocket/startup/OSXSetup.java rename to swing/src/net/sf/openrocket/startup/OSXSetup.java diff --git a/core/src/net/sf/openrocket/startup/Startup.java b/swing/src/net/sf/openrocket/startup/Startup.java similarity index 100% rename from core/src/net/sf/openrocket/startup/Startup.java rename to swing/src/net/sf/openrocket/startup/Startup.java diff --git a/core/src/net/sf/openrocket/startup/SwingStartup.java b/swing/src/net/sf/openrocket/startup/SwingStartup.java similarity index 100% rename from core/src/net/sf/openrocket/startup/SwingStartup.java rename to swing/src/net/sf/openrocket/startup/SwingStartup.java diff --git a/core/src/net/sf/openrocket/startup/jij/ClasspathProvider.java b/swing/src/net/sf/openrocket/startup/jij/ClasspathProvider.java similarity index 100% rename from core/src/net/sf/openrocket/startup/jij/ClasspathProvider.java rename to swing/src/net/sf/openrocket/startup/jij/ClasspathProvider.java diff --git a/core/src/net/sf/openrocket/startup/jij/ClasspathUrlStreamHandler.java b/swing/src/net/sf/openrocket/startup/jij/ClasspathUrlStreamHandler.java similarity index 100% rename from core/src/net/sf/openrocket/startup/jij/ClasspathUrlStreamHandler.java rename to swing/src/net/sf/openrocket/startup/jij/ClasspathUrlStreamHandler.java diff --git a/core/src/net/sf/openrocket/startup/jij/ConfigurableStreamHandlerFactory.java b/swing/src/net/sf/openrocket/startup/jij/ConfigurableStreamHandlerFactory.java similarity index 100% rename from core/src/net/sf/openrocket/startup/jij/ConfigurableStreamHandlerFactory.java rename to swing/src/net/sf/openrocket/startup/jij/ConfigurableStreamHandlerFactory.java diff --git a/core/src/net/sf/openrocket/startup/jij/CurrentClasspathProvider.java b/swing/src/net/sf/openrocket/startup/jij/CurrentClasspathProvider.java similarity index 100% rename from core/src/net/sf/openrocket/startup/jij/CurrentClasspathProvider.java rename to swing/src/net/sf/openrocket/startup/jij/CurrentClasspathProvider.java diff --git a/core/src/net/sf/openrocket/startup/jij/JarInJarStarter.java b/swing/src/net/sf/openrocket/startup/jij/JarInJarStarter.java similarity index 100% rename from core/src/net/sf/openrocket/startup/jij/JarInJarStarter.java rename to swing/src/net/sf/openrocket/startup/jij/JarInJarStarter.java diff --git a/core/src/net/sf/openrocket/startup/jij/ManifestClasspathProvider.java b/swing/src/net/sf/openrocket/startup/jij/ManifestClasspathProvider.java similarity index 100% rename from core/src/net/sf/openrocket/startup/jij/ManifestClasspathProvider.java rename to swing/src/net/sf/openrocket/startup/jij/ManifestClasspathProvider.java diff --git a/core/src/net/sf/openrocket/startup/jij/PluginClasspathProvider.java b/swing/src/net/sf/openrocket/startup/jij/PluginClasspathProvider.java similarity index 100% rename from core/src/net/sf/openrocket/startup/jij/PluginClasspathProvider.java rename to swing/src/net/sf/openrocket/startup/jij/PluginClasspathProvider.java diff --git a/core/src/net/sf/openrocket/startup/providers/BlockingComponentPresetDatabaseProvider.java b/swing/src/net/sf/openrocket/startup/providers/BlockingComponentPresetDatabaseProvider.java similarity index 100% rename from core/src/net/sf/openrocket/startup/providers/BlockingComponentPresetDatabaseProvider.java rename to swing/src/net/sf/openrocket/startup/providers/BlockingComponentPresetDatabaseProvider.java diff --git a/core/src/net/sf/openrocket/startup/providers/BlockingMotorDatabaseProvider.java b/swing/src/net/sf/openrocket/startup/providers/BlockingMotorDatabaseProvider.java similarity index 100% rename from core/src/net/sf/openrocket/startup/providers/BlockingMotorDatabaseProvider.java rename to swing/src/net/sf/openrocket/startup/providers/BlockingMotorDatabaseProvider.java diff --git a/core/src/net/sf/openrocket/startup/providers/TranslatorProvider.java b/swing/src/net/sf/openrocket/startup/providers/TranslatorProvider.java similarity index 100% rename from core/src/net/sf/openrocket/startup/providers/TranslatorProvider.java rename to swing/src/net/sf/openrocket/startup/providers/TranslatorProvider.java diff --git a/core/src/net/sf/openrocket/utils/BasicApplication.java b/swing/src/net/sf/openrocket/utils/BasicApplication.java similarity index 100% rename from core/src/net/sf/openrocket/utils/BasicApplication.java rename to swing/src/net/sf/openrocket/utils/BasicApplication.java diff --git a/core/src/net/sf/openrocket/utils/ComponentPresetEditor.java b/swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java similarity index 100% rename from core/src/net/sf/openrocket/utils/ComponentPresetEditor.java rename to swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java diff --git a/core/src/net/sf/openrocket/utils/CoreServicesModule.java b/swing/src/net/sf/openrocket/utils/CoreServicesModule.java similarity index 100% rename from core/src/net/sf/openrocket/utils/CoreServicesModule.java rename to swing/src/net/sf/openrocket/utils/CoreServicesModule.java diff --git a/core/src/net/sf/openrocket/utils/GraphicalMotorSelector.java b/swing/src/net/sf/openrocket/utils/GraphicalMotorSelector.java similarity index 100% rename from core/src/net/sf/openrocket/utils/GraphicalMotorSelector.java rename to swing/src/net/sf/openrocket/utils/GraphicalMotorSelector.java diff --git a/core/src/net/sf/openrocket/utils/MotorPlot.java b/swing/src/net/sf/openrocket/utils/MotorPlot.java similarity index 100% rename from core/src/net/sf/openrocket/utils/MotorPlot.java rename to swing/src/net/sf/openrocket/utils/MotorPlot.java diff --git a/core/src/net/sf/openrocket/utils/RocksimComponentFileTranslator.java b/swing/src/net/sf/openrocket/utils/RocksimComponentFileTranslator.java similarity index 100% rename from core/src/net/sf/openrocket/utils/RocksimComponentFileTranslator.java rename to swing/src/net/sf/openrocket/utils/RocksimComponentFileTranslator.java diff --git a/core/src/net/sf/openrocket/utils/RocksimConverter.java b/swing/src/net/sf/openrocket/utils/RocksimConverter.java similarity index 100% rename from core/src/net/sf/openrocket/utils/RocksimConverter.java rename to swing/src/net/sf/openrocket/utils/RocksimConverter.java diff --git a/core/src/net/sf/openrocket/utils/SerializePresets.java b/swing/src/net/sf/openrocket/utils/SerializePresets.java similarity index 100% rename from core/src/net/sf/openrocket/utils/SerializePresets.java rename to swing/src/net/sf/openrocket/utils/SerializePresets.java diff --git a/core/test/net/sf/openrocket/IntegrationTest.java b/swing/test/net/sf/openrocket/IntegrationTest.java similarity index 100% rename from core/test/net/sf/openrocket/IntegrationTest.java rename to swing/test/net/sf/openrocket/IntegrationTest.java diff --git a/core/test/net/sf/openrocket/gui/TestGUI.java b/swing/test/net/sf/openrocket/gui/TestGUI.java similarity index 100% rename from core/test/net/sf/openrocket/gui/TestGUI.java rename to swing/test/net/sf/openrocket/gui/TestGUI.java diff --git a/core/test/net/sf/openrocket/gui/configdialog/FinSetConfigTest.java b/swing/test/net/sf/openrocket/gui/configdialog/FinSetConfigTest.java similarity index 100% rename from core/test/net/sf/openrocket/gui/configdialog/FinSetConfigTest.java rename to swing/test/net/sf/openrocket/gui/configdialog/FinSetConfigTest.java diff --git a/core/test/net/sf/openrocket/gui/print/PrintUnitTest.java b/swing/test/net/sf/openrocket/gui/print/PrintUnitTest.java similarity index 100% rename from core/test/net/sf/openrocket/gui/print/PrintUnitTest.java rename to swing/test/net/sf/openrocket/gui/print/PrintUnitTest.java diff --git a/core/test/net/sf/openrocket/gui/print/TestPaperSize.java b/swing/test/net/sf/openrocket/gui/print/TestPaperSize.java similarity index 100% rename from core/test/net/sf/openrocket/gui/print/TestPaperSize.java rename to swing/test/net/sf/openrocket/gui/print/TestPaperSize.java diff --git a/core/test/net/sf/openrocket/logging/CyclicBufferTest.java b/swing/test/net/sf/openrocket/logging/CyclicBufferTest.java similarity index 100% rename from core/test/net/sf/openrocket/logging/CyclicBufferTest.java rename to swing/test/net/sf/openrocket/logging/CyclicBufferTest.java diff --git a/core/test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java b/swing/test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java similarity index 100% rename from core/test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java rename to swing/test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java diff --git a/core/test/net/sf/openrocket/logging/LogLevelTest.java b/swing/test/net/sf/openrocket/logging/LogLevelTest.java similarity index 100% rename from core/test/net/sf/openrocket/logging/LogLevelTest.java rename to swing/test/net/sf/openrocket/logging/LogLevelTest.java From f3bcdfcfc2cd3d359109e353f52735d45703b1f1 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Fri, 27 Sep 2013 20:59:35 -0500 Subject: [PATCH 02/47] Changed eclipse project name to OpenRocket Core. --- core/.project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/.project b/core/.project index 977f74894..c92bdfc1f 100644 --- a/core/.project +++ b/core/.project @@ -1,6 +1,6 @@ - OpenRocket + OpenRocket Core From 5e27ea578fe22b947875fc3b5d1406a8446e29aa Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Fri, 27 Sep 2013 21:37:46 -0500 Subject: [PATCH 03/47] Added core/resources and swing/resources to classpath. --- swing/.classpath | 2 ++ 1 file changed, 2 insertions(+) diff --git a/swing/.classpath b/swing/.classpath index d8d181fc9..2c8da4720 100644 --- a/swing/.classpath +++ b/swing/.classpath @@ -19,5 +19,7 @@ + + From 84af78ccf403b216724163f7cbc1bfa03bec7ca5 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Tue, 10 Sep 2013 21:46:38 -0500 Subject: [PATCH 04/47] Extract the MotorRowFilter out of the ThrustCurveMotorSelectionPanel to facilitate adding more filters. --- .../motor/thrustcurve/MotorRowFilter.java | 83 +++++++++++++++++ .../ThrustCurveMotorSelectionPanel.java | 92 +++---------------- 2 files changed, 94 insertions(+), 81 deletions(-) create mode 100644 core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java b/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java new file mode 100644 index 000000000..142571e04 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java @@ -0,0 +1,83 @@ +package net.sf.openrocket.gui.dialogs.motor.thrustcurve; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import javax.swing.RowFilter; +import javax.swing.table.TableModel; + +import net.sf.openrocket.database.motor.ThrustCurveMotorSet; + +//////// Row filters + +/** + * Abstract adapter class. + */ +class MotorRowFilter extends RowFilter { + + public enum DiameterFilterControl { + ALL, + EXACT, + SMALLER + }; + + private final ThrustCurveMotorDatabaseModel model; + private final double diameter; + + private List searchTerms = Collections. emptyList(); + private DiameterFilterControl diameterControl = DiameterFilterControl.ALL; + + public MotorRowFilter(ThrustCurveMotorDatabaseModel model, double diameter) { + super(); + this.model = model; + this.diameter = diameter; + } + + public void setSearchTerms(final List searchTerms) { + this.searchTerms = new ArrayList(); + for (String s : searchTerms) { + s = s.trim().toLowerCase(Locale.getDefault()); + if (s.length() > 0) { + this.searchTerms.add(s); + } + } + } + + void setDiameterControl(DiameterFilterControl diameterControl) { + this.diameterControl = diameterControl; + } + + @Override + public boolean include(RowFilter.Entry entry) { + int index = entry.getIdentifier(); + ThrustCurveMotorSet m = model.getMotorSet(index); + return filterByDiameter(m) && filterByString(m); + } + + public boolean filterByDiameter(ThrustCurveMotorSet m) { + switch (diameterControl) { + default: + case ALL: + return true; + case EXACT: + return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015)); + case SMALLER: + return (m.getDiameter() <= diameter + 0.0004); + } + } + + + public boolean filterByString(ThrustCurveMotorSet m) { + main: for (String s : searchTerms) { + for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) { + String str = col.getValue(m).toString().toLowerCase(Locale.getDefault()); + if (str.indexOf(s) >= 0) + continue main; + } + return false; + } + return true; + } +} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index e9781b210..3456fa65b 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -34,7 +34,6 @@ import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; -import javax.swing.RowFilter; import javax.swing.RowSorter; import javax.swing.SortOrder; import javax.swing.SwingUtilities; @@ -109,18 +108,17 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec private final List database; - private final double diameter; private CloseableDialog dialog = null; - private final ThrustCurveMotorDatabaseModel model; + final ThrustCurveMotorDatabaseModel model; private final JTable table; private final TableRowSorter sorter; private final JCheckBox hideSimilarBox; private final JTextField searchField; - private String[] searchTerms = new String[0]; + String[] searchTerms = new String[0]; private final JLabel curveSelectionLabel; @@ -162,7 +160,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec public ThrustCurveMotorSelectionPanel(ThrustCurveMotor current, double delay, double diameter) { super(new MigLayout("fill", "[grow][]")); - this.diameter = diameter; // Construct the database (adding the current motor if not in the db already) @@ -189,6 +186,8 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } database = db; + model = new ThrustCurveMotorDatabaseModel(database); + final MotorRowFilter rowFilter = new MotorRowFilter(model, diameter); //// GUI @@ -217,20 +216,21 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec sel = SHOW_ALL; switch (sel) { case SHOW_ALL: - sorter.setRowFilter(new MotorRowFilterAll()); + rowFilter.setDiameterControl(MotorRowFilter.DiameterFilterControl.ALL); break; case SHOW_SMALLER: - sorter.setRowFilter(new MotorRowFilterSmaller()); + rowFilter.setDiameterControl(MotorRowFilter.DiameterFilterControl.SMALLER); break; case SHOW_EXACT: - sorter.setRowFilter(new MotorRowFilterExact()); + rowFilter.setDiameterControl(MotorRowFilter.DiameterFilterControl.EXACT); break; default: throw new BugException("Invalid selection mode sel=" + sel); } + sorter.sort(); Application.getPreferences().putChoice("MotorDiameterMatch", sel); scrollSelectionVisible(); } @@ -252,7 +252,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec // Motor selection table - model = new ThrustCurveMotorDatabaseModel(database); table = new JTable(model); @@ -275,6 +274,8 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec sorter.setSortKeys(Arrays.asList(sortKeys)); } + sorter.setRowFilter(rowFilter); + // Set selection and double-click listeners table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override @@ -344,14 +345,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec 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(Locale.getDefault()); - if (s.length() > 0) { - list.add(s); - } - } - searchTerms = list.toArray(new String[0]); + rowFilter.setSearchTerms( Arrays.asList(split) ); sorter.sort(); scrollSelectionVisible(); } @@ -386,10 +380,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec }); panel.add(curveSelectionBox, "growx, wrap para"); - - - - // Ejection charge delay: panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Ejectionchargedelay"))); @@ -936,66 +926,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } - //////// Row filters - - /** - * Abstract adapter class. - */ - private abstract class MotorRowFilter extends RowFilter { - @Override - public boolean include(RowFilter.Entry entry) { - int index = entry.getIdentifier(); - ThrustCurveMotorSet m = model.getMotorSet(index); - return filterByDiameter(m) && filterByString(m); - } - - public abstract boolean filterByDiameter(ThrustCurveMotorSet m); - - - public boolean filterByString(ThrustCurveMotorSet m) { - main: for (String s : searchTerms) { - for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) { - String str = col.getValue(m).toString().toLowerCase(Locale.getDefault()); - if (str.indexOf(s) >= 0) - continue main; - } - return false; - } - return true; - } - } - - /** - * Show all motors. - */ - private class MotorRowFilterAll extends MotorRowFilter { - @Override - public boolean filterByDiameter(ThrustCurveMotorSet m) { - return true; - } - } - - /** - * Show motors smaller than the mount. - */ - private class MotorRowFilterSmaller extends MotorRowFilter { - @Override - public boolean filterByDiameter(ThrustCurveMotorSet m) { - return (m.getDiameter() <= diameter + 0.0004); - } - } - - /** - * Show motors that fit the mount. - */ - private class MotorRowFilterExact extends MotorRowFilter { - @Override - public boolean filterByDiameter(ThrustCurveMotorSet m) { - return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015)); - } - } - - /** * Custom layered pane that sets the bounds of the components on every layout. */ From 64015d72bef565704426e6c7cb668dfdb8418923 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 11 Sep 2013 13:27:08 -0500 Subject: [PATCH 05/47] Refactor the api so the motor mount goes into the ThrustCurveMotorSelectionPanel. This allows greater flexibility in filter functionality. --- .../gui/configdialog/MotorConfig.java | 3 +-- .../MotorConfigurationPanel.java | 6 +---- .../gui/dialogs/motor/MotorChooserDialog.java | 6 ++--- .../ThrustCurveMotorSelectionPanel.java | 23 +++++++++++-------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java index 03882cce5..564ba3954 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java @@ -184,8 +184,7 @@ public class MotorConfig extends JPanel { public void actionPerformed(ActionEvent e) { String id = configuration.getFlightConfigurationID(); - MotorChooserDialog dialog = new MotorChooserDialog(mount.getMotor(id), - mount.getMotorDelay(id), mount.getMotorMountDiameter(), + MotorChooserDialog dialog = new MotorChooserDialog(mount, id, SwingUtilities.getWindowAncestor(MotorConfig.this)); dialog.setVisible(true); Motor m = dialog.getSelectedMotor(); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java index 811169dd0..1cb7178f6 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java @@ -207,11 +207,7 @@ public class MotorConfigurationPanel extends JPanel { MotorConfiguration config = mount.getMotorConfiguration().get(id); - MotorChooserDialog dialog = new MotorChooserDialog( - config.getMotor(), - config.getEjectionDelay(), - mount.getMotorMountDiameter(), - flightConfigurationDialog); + MotorChooserDialog dialog = new MotorChooserDialog(mount, id, flightConfigurationDialog); dialog.setVisible(true); Motor m = dialog.getSelectedMotor(); double d = dialog.getSelectedDelay(); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java index 761d4dab6..898964af0 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java @@ -16,7 +16,7 @@ import net.sf.openrocket.gui.dialogs.motor.thrustcurve.ThrustCurveMotorSelection import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.motor.Motor; -import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.startup.Application; public class MotorChooserDialog extends JDialog implements CloseableDialog { @@ -27,13 +27,13 @@ public class MotorChooserDialog extends JDialog implements CloseableDialog { private static final Translator trans = Application.getTranslator(); - public MotorChooserDialog(Motor current, double delay, double diameter, Window owner) { + public MotorChooserDialog(MotorMount mount, String currentConfig, Window owner) { super(owner, trans.get("MotorChooserDialog.title"), Dialog.ModalityType.APPLICATION_MODAL); JPanel panel = new JPanel(new MigLayout("fill")); - selectionPanel = new ThrustCurveMotorSelectionPanel((ThrustCurveMotor) current, delay, diameter); + selectionPanel = new ThrustCurveMotorSelectionPanel(mount, currentConfig); panel.add(selectionPanel, "grow, wrap para"); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index 3456fa65b..731a5f5ba 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -15,7 +15,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.prefs.Preferences; import javax.swing.BorderFactory; @@ -57,6 +56,8 @@ import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.Markers; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.rocketcomponent.MotorConfiguration; +import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.BugException; @@ -157,20 +158,25 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec * @param delay the currently selected ejection charge delay. * @param diameter the diameter of the motor mount. */ - public ThrustCurveMotorSelectionPanel(ThrustCurveMotor current, double delay, double diameter) { + public ThrustCurveMotorSelectionPanel(MotorMount mount, String currentConfig) { super(new MigLayout("fill", "[grow][]")); - + double diameter = 0; + if (currentConfig != null && mount != null) { + MotorConfiguration motorConf = mount.getMotorConfiguration().get(currentConfig); + selectedMotor = (ThrustCurveMotor) motorConf.getMotor(); + selectedDelay = motorConf.getEjectionDelay(); + diameter = mount.getMotorMountDiameter(); + } // Construct the database (adding the current motor if not in the db already) List db; db = Application.getThrustCurveMotorSetDatabase().getMotorSets(); // If current motor is not found in db, add a new ThrustCurveMotorSet containing it - if (current != null) { - selectedMotor = current; + if (selectedMotor != null) { for (ThrustCurveMotorSet motorSet : db) { - if (motorSet.getMotors().contains(current)) { + if (motorSet.getMotors().contains(selectedMotor)) { selectedMotorSet = motorSet; break; } @@ -178,7 +184,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec if (selectedMotorSet == null) { db = new ArrayList(db); ThrustCurveMotorSet extra = new ThrustCurveMotorSet(); - extra.addMotor(current); + extra.addMotor(selectedMotor); selectedMotorSet = extra; db.add(extra); Collections.sort(db); @@ -345,7 +351,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec private void update() { String text = searchField.getText().trim(); String[] split = text.split("\\s+"); - rowFilter.setSearchTerms( Arrays.asList(split) ); + rowFilter.setSearchTerms(Arrays.asList(split)); sorter.sort(); scrollSelectionVisible(); } @@ -555,7 +561,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec // Update the panel data updateData(); - selectedDelay = delay; setDelays(false); } From ea6aa3e414af36d2f1c1ed57094d18dbeb86ac2d Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Thu, 12 Sep 2013 15:10:31 -0500 Subject: [PATCH 06/47] Added manufacturer selection boxes and exclude motors currently used filters. --- .../ManufacturerPopupSelector.java | 96 ++++++++ .../motor/thrustcurve/MotorRowFilter.java | 60 ++++- .../net/sf/openrocket/gui/util/CheckList.java | 177 ++++++++++++++ .../openrocket/gui/util/CheckListEditor.java | 86 +++++++ .../gui/util/CheckListRenderer.java | 228 ++++++++++++++++++ .../gui/util/DefaultCheckListModel.java | 124 ++++++++++ .../rocketcomponent/FlightConfiguration.java | 2 +- .../FlightConfigurationImpl.java | 5 + .../ThrustCurveMotorSelectionPanel.java | 77 +++++- .../openrocket/gui/util/SwingPreferences.java | 64 +++-- 10 files changed, 892 insertions(+), 27 deletions(-) create mode 100644 core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java create mode 100644 core/src/net/sf/openrocket/gui/util/CheckList.java create mode 100644 core/src/net/sf/openrocket/gui/util/CheckListEditor.java create mode 100644 core/src/net/sf/openrocket/gui/util/CheckListRenderer.java create mode 100644 core/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java b/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java new file mode 100644 index 000000000..447f864e2 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java @@ -0,0 +1,96 @@ +package net.sf.openrocket.gui.dialogs.motor.thrustcurve; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.UIManager; + +import net.sf.openrocket.gui.util.CheckList; +import net.sf.openrocket.motor.Manufacturer; + +public abstract class ManufacturerPopupSelector extends JPopupMenu implements ActionListener { + + Map componentMap = new HashMap(); + CheckList list; + + public ManufacturerPopupSelector(Collection allManufacturers, Collection unselectedManufacturers) { + + JPanel root = new JPanel(new BorderLayout(3, 3)); + root.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); + root.setPreferredSize(new Dimension(250, 150)); // default popup size + + Box commands = new Box(BoxLayout.LINE_AXIS); + + commands.add(Box.createHorizontalStrut(5)); + commands.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); + commands.setBackground(UIManager.getColor("Panel.background")); + commands.setOpaque(true); + + JButton closeButton = new JButton("close"); + closeButton.addActionListener(this); + commands.add(closeButton); + + List manufacturers = new ArrayList(); + for (Manufacturer m : allManufacturers) { + manufacturers.add(m.getSimpleName()); + componentMap.put(m.getSimpleName(), m); + } + + Collections.sort(manufacturers); + + list = new CheckList.Builder().build(); + list.setData(manufacturers); + + if (unselectedManufacturers != null) + { + for (Manufacturer m : unselectedManufacturers) { + manufacturers.remove(m.getSimpleName()); + } + } + list.setCheckedItems(manufacturers); + + root.add(new JScrollPane(list.getList()), BorderLayout.CENTER); + root.add(commands, BorderLayout.SOUTH); + + this.add(root); + + } + + @Override + public void actionPerformed(ActionEvent e) { + + List selectedManufacturers = new ArrayList(); + List unselectedManufacturers = new ArrayList(); + + Collection selected = list.getCheckedItems(); + for (String s : selected) { + selectedManufacturers.add(componentMap.get(s)); + } + + Collection unselected = list.getUncheckedItems(); + for (String s : unselected) { + unselectedManufacturers.add(componentMap.get(s)); + } + + onDismissed(selectedManufacturers, unselectedManufacturers); + setVisible(false); + } + + public abstract void onDismissed(List selectedManufacturers, List unselectedManufacturers); + +} diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java b/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java index 142571e04..1713c36ef 100644 --- a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java +++ b/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java @@ -9,6 +9,10 @@ import javax.swing.RowFilter; import javax.swing.table.TableModel; import net.sf.openrocket.database.motor.ThrustCurveMotorSet; +import net.sf.openrocket.motor.Manufacturer; +import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.rocketcomponent.MotorConfiguration; +import net.sf.openrocket.rocketcomponent.MotorMount; //////// Row filters @@ -23,16 +27,28 @@ class MotorRowFilter extends RowFilter { SMALLER }; + // configuration data used in the filter process private final ThrustCurveMotorDatabaseModel model; - private final double diameter; + private final Double diameter; + private List usedMotors = new ArrayList(); + // things which can be changed to modify filter behavior private List searchTerms = Collections. emptyList(); private DiameterFilterControl diameterControl = DiameterFilterControl.ALL; + private boolean hideUsedMotors = false; + private List excludedManufacturers = new ArrayList(); - public MotorRowFilter(ThrustCurveMotorDatabaseModel model, double diameter) { + public MotorRowFilter(MotorMount mount, ThrustCurveMotorDatabaseModel model) { super(); this.model = model; - this.diameter = diameter; + if (mount != null) { + this.diameter = mount.getMotorMountDiameter(); + for (MotorConfiguration m : mount.getMotorConfiguration()) { + this.usedMotors.add((ThrustCurveMotor) m.getMotor()); + } + } else { + this.diameter = null; + } } public void setSearchTerms(final List searchTerms) { @@ -49,14 +65,46 @@ class MotorRowFilter extends RowFilter { this.diameterControl = diameterControl; } + void setHideUsedMotors(boolean hideUsedMotors) { + this.hideUsedMotors = hideUsedMotors; + } + + void setExcludedManufacturers(List excludedManufacturers) { + this.excludedManufacturers.clear(); + this.excludedManufacturers.addAll(excludedManufacturers); + } + @Override public boolean include(RowFilter.Entry entry) { int index = entry.getIdentifier(); ThrustCurveMotorSet m = model.getMotorSet(index); - return filterByDiameter(m) && filterByString(m); + return filterManufacturers(m) && filterUsed(m) && filterByDiameter(m) && filterByString(m); } - public boolean filterByDiameter(ThrustCurveMotorSet m) { + private boolean filterManufacturers(ThrustCurveMotorSet m) { + if (excludedManufacturers.contains(m.getManufacturer())) { + return false; + } else { + return true; + } + } + + private boolean filterUsed(ThrustCurveMotorSet m) { + if (!hideUsedMotors) { + return true; + } + for (ThrustCurveMotor motor : usedMotors) { + if (m.matches(motor)) { + return false; + } + } + return true; + } + + private boolean filterByDiameter(ThrustCurveMotorSet m) { + if (diameter == null) { + return true; + } switch (diameterControl) { default: case ALL: @@ -69,7 +117,7 @@ class MotorRowFilter extends RowFilter { } - public boolean filterByString(ThrustCurveMotorSet m) { + private boolean filterByString(ThrustCurveMotorSet m) { main: for (String s : searchTerms) { for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) { String str = col.getValue(m).toString().toLowerCase(Locale.getDefault()); diff --git a/core/src/net/sf/openrocket/gui/util/CheckList.java b/core/src/net/sf/openrocket/gui/util/CheckList.java new file mode 100644 index 000000000..6eb233092 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/util/CheckList.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2009-2011, EzWare + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer.Redistributions + * in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution.Neither the name of the + * EzWare nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +package net.sf.openrocket.gui.util; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.JList; +import javax.swing.KeyStroke; +import javax.swing.ListSelectionModel; + +/** + * The decorator for JList which makes it work like check list + * UI can be designed using JList and which can be later decorated to become a check list + * @author Eugene Ryzhikov + * + * @param list item type + */ +public class CheckList { + + private final JList list; + private static final MouseAdapter checkBoxEditor = new CheckListEditor(); + + public static class Builder { + + private JList list; + + public Builder(JList list) { + this.list = list == null ? new JList() : list; + } + + public Builder() { + this(null); + } + + public CheckList build() { + return new CheckList(list); + } + + } + + + /** + * Wraps the standard JList and makes it work like check list + * @param list + */ + private CheckList(final JList list) { + + if (list == null) + throw new NullPointerException(); + this.list = list; + this.list.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + if (!isEditorAttached()) + list.addMouseListener(checkBoxEditor); + this.list.setCellRenderer(new CheckListRenderer()); + + setupKeyboardActions(list); + + } + + @SuppressWarnings("serial") + private void setupKeyboardActions(final JList list) { + String actionKey = "toggle-check"; + list.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), actionKey); + list.getActionMap().put(actionKey, new AbstractAction() { + + @Override + public void actionPerformed(ActionEvent e) { + toggleIndex(list.getSelectedIndex()); + } + }); + } + + private boolean isEditorAttached() { + + for (MouseListener ml : list.getMouseListeners()) { + if (ml instanceof CheckListEditor) + return true; + } + return false; + + } + + public JList getList() { + return list; + } + + /** + * Sets data to a check list. Simplification for setting new the model + * @param data + */ + public void setData(Collection data) { + setModel(new DefaultCheckListModel(data)); + } + + /** + * Sets the model for check list. + * @param model + */ + public void setModel(DefaultCheckListModel model) { + list.setModel(model); + } + + @SuppressWarnings("unchecked") + public DefaultCheckListModel getModel() { + return (DefaultCheckListModel) list.getModel(); + } + + /** + * Returns a collection of checked items. + * @return collection of checked items. Empty collection if nothing is selected + */ + public Collection getCheckedItems() { + return getModel().getCheckedItems(); + } + + public Collection getUncheckedItems() { + List unchecked = new ArrayList(); + for (int i = getModel().getSize() - 1; i >= 0; i--) { + unchecked.add((T) getModel().getElementAt(i)); + } + unchecked.removeAll(getCheckedItems()); + return unchecked; + } + + /** + * Resets checked elements + * @param elements + */ + public void setCheckedItems(Collection elements) { + getModel().setCheckedItems(elements); + } + + public void toggleIndex(int index) { + if (index >= 0 && index < list.getModel().getSize()) { + DefaultCheckListModel model = getModel(); + model.setCheckedIndex(index, !model.isCheckedIndex(index)); + } + } + +} diff --git a/core/src/net/sf/openrocket/gui/util/CheckListEditor.java b/core/src/net/sf/openrocket/gui/util/CheckListEditor.java new file mode 100644 index 000000000..0dafdc111 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/util/CheckListEditor.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009-2011, EzWare + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer.Redistributions + * in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution.Neither the name of the + * EzWare nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +package net.sf.openrocket.gui.util; + +import java.awt.Rectangle; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Arrays; + +import javax.swing.JList; +import javax.swing.SwingUtilities; + + +/** + * Determines mouse click and + * 1. Toggles the check on selected item if clicked once + * 2. Clears checks and checks selected item if clicked more then once + * + * Created on Feb 4, 2011 + * @author Eugene Ryzhikov + * + */ +final class CheckListEditor extends MouseAdapter { + @Override + public void mouseClicked(MouseEvent e) { + + if (!SwingUtilities.isLeftMouseButton(e)) + return; + + JList list = (JList) e.getSource(); + if (!list.isEnabled() || (!(list.getModel() instanceof DefaultCheckListModel))) + return; + + int index = list.locationToIndex(e.getPoint()); + if (index < 0) + return; + + Rectangle bounds = list.getCellBounds(index, index); + + if (bounds.contains(e.getPoint())) { + + @SuppressWarnings("unchecked") + DefaultCheckListModel model = (DefaultCheckListModel) list.getModel(); + + if (e.getClickCount() > 1) { + // clear all and check selected for more then 1 clicks + model.setCheckedItems(Arrays.asList(model.getElementAt(index))); + } else { + // simple toggle for 1 click + model.setCheckedIndex(index, !model.isCheckedIndex(index)); + } + e.consume(); + } + + } + +} diff --git a/core/src/net/sf/openrocket/gui/util/CheckListRenderer.java b/core/src/net/sf/openrocket/gui/util/CheckListRenderer.java new file mode 100644 index 000000000..e0777c4f9 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/util/CheckListRenderer.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2009-2011, EzWare + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer.Redistributions + * in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution.Neither the name of the + * EzWare nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +package net.sf.openrocket.gui.util; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Rectangle; +import java.io.Serializable; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.Icon; +import javax.swing.JCheckBox; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +public class CheckListRenderer extends JCheckBox implements ListCellRenderer, Serializable { + + private static final long serialVersionUID = 1L; + + private static final Border NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); + private static final Border SAFE_NO_FOCUS_BORDER = NO_FOCUS_BORDER; // may change in the feature + + /** + * Constructs a default renderer object for an item in a list. + */ + public CheckListRenderer() { + super(); + setOpaque(true); + setBorder(getNoFocusBorder()); + } + + private static Border getNoFocusBorder() { + if (System.getSecurityManager() != null) { + return SAFE_NO_FOCUS_BORDER; + } else { + return NO_FOCUS_BORDER; + } + } + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { + + setComponentOrientation(list.getComponentOrientation()); + + Color bg = null; + Color fg = null; + + JList.DropLocation dropLocation = list.getDropLocation(); + if (dropLocation != null && !dropLocation.isInsert() && dropLocation.getIndex() == index) { + + bg = UIManager.getColor("List.dropCellBackground"); + fg = UIManager.getColor("List.dropCellForeground"); + + isSelected = true; + } + + if (isSelected) { + setBackground(bg == null ? list.getSelectionBackground() : bg); + setForeground(fg == null ? list.getSelectionForeground() : fg); + } else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + if (value instanceof Icon) { + setIcon((Icon) value); + setText(""); + } else { + setIcon(null); + setText(getObjectAsText(value)); + } + + setSelected(isChecked(list, index)); + + setEnabled(list.isEnabled()); + setFont(list.getFont()); + + Border border = null; + if (cellHasFocus) { + if (isSelected) { + border = UIManager.getBorder("List.focusSelectedCellHighlightBorder"); + } + if (border == null) { + border = UIManager.getBorder("List.focusCellHighlightBorder"); + } + } else { + border = getNoFocusBorder(); + } + setBorder(border); + + return this; + } + + protected String getObjectAsText(Object obj) { + return (obj == null) ? "" : obj.toString(); + } + + private boolean isChecked(JList list, int index) { + + if (list.getModel() instanceof DefaultCheckListModel) { + return ((DefaultCheckListModel) list.getModel()).isCheckedIndex(index); + } else { + return false; + } + + } + + /** + * @return true if the background is opaque and differs from the JList's background; false otherwise + */ + @Override + public boolean isOpaque() { + Color back = getBackground(); + Component p = getParent(); + if (p != null) { + p = p.getParent(); + } + // p should now be the JList. + boolean colorMatch = (back != null) && (p != null) && back.equals(p.getBackground()) && p.isOpaque(); + return !colorMatch && super.isOpaque(); + } + + @Override + protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + + if ("text".equals(propertyName) || + (("font".equals(propertyName) || "foreground".equals(propertyName)) && + oldValue != newValue && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) { + + super.firePropertyChange(propertyName, oldValue, newValue); + } + } + + // Methods below are overridden for performance reasons. + + @Override + public void validate() { + } + + @Override + public void invalidate() { + } + + @Override + public void repaint() { + } + + @Override + public void revalidate() { + } + + @Override + public void repaint(long tm, int x, int y, int width, int height) { + } + + @Override + public void repaint(Rectangle r) { + } + + @Override + public void firePropertyChange(String propertyName, byte oldValue, byte newValue) { + } + + @Override + public void firePropertyChange(String propertyName, char oldValue, char newValue) { + } + + @Override + public void firePropertyChange(String propertyName, short oldValue, short newValue) { + } + + @Override + public void firePropertyChange(String propertyName, int oldValue, int newValue) { + } + + @Override + public void firePropertyChange(String propertyName, long oldValue, long newValue) { + } + + @Override + public void firePropertyChange(String propertyName, float oldValue, float newValue) { + } + + @Override + public void firePropertyChange(String propertyName, double oldValue, double newValue) { + } + + @Override + public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { + } + + @SuppressWarnings("serial") + public static class UIResource extends DefaultListCellRenderer implements javax.swing.plaf.UIResource { + } + +} \ No newline at end of file diff --git a/core/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java b/core/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java new file mode 100644 index 000000000..647019400 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009-2011, EzWare + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer.Redistributions + * in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution.Neither the name of the + * EzWare nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +package net.sf.openrocket.gui.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.AbstractListModel; + +/** + * Default model for check list. It is based on the list of items + * Implementation of checks is based on HashSet of checked items + * + * @author Eugene Ryzhikov + * + * @param list element type + */ +public class DefaultCheckListModel extends AbstractListModel { + + private static final long serialVersionUID = 1L; + + private final List data = new ArrayList(); + private final Set checks = new HashSet(); + + public DefaultCheckListModel(Collection data) { + + if (data == null) + return; + for (T object : data) { + this.data.add(object); + checks.clear(); + } + } + + public DefaultCheckListModel(T... data) { + this(Arrays.asList(data)); + } + + /* (non-Javadoc) + * @see org.oxbow.swingbits.list.ICheckListModel#getSize() + */ + @Override + public int getSize() { + return data().size(); + } + + private List data() { + return data; + } + + + @Override + public Object getElementAt(int index) { + return data().get(index); + } + + public boolean isCheckedIndex(int index) { + return checks.contains(data().get(index)); + } + + public void setCheckedIndex(int index, boolean value) { + T o = data().get(index); + if (value) + checks.add(o); + else + checks.remove(o); + fireContentsChanged(this, index, index); + } + + public Collection getCheckedItems() { + List items = new ArrayList(checks); + items.retainAll(data); + return Collections.unmodifiableList(items); + } + + public void setCheckedItems(Collection items) { + + // if ( CollectionUtils.isEmpty(items)) return; + + List correctedItems = new ArrayList(items); + correctedItems.retainAll(data); + + checks.clear(); + checks.addAll(correctedItems); + fireContentsChanged(this, 0, checks.size() - 1); + + + } + +} diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index d99c81b9e..054f50cf3 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -11,7 +11,7 @@ import net.sf.openrocket.util.ChangeSource; * * @param the parameter type */ -public interface FlightConfiguration extends FlightConfigurableComponent { +public interface FlightConfiguration extends FlightConfigurableComponent, Iterable { /** * Return the default parameter value for this FlightConfiguration. diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfigurationImpl.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfigurationImpl.java index f69fa6b22..91fa99d60 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfigurationImpl.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfigurationImpl.java @@ -2,6 +2,7 @@ package net.sf.openrocket.rocketcomponent; import java.util.EventObject; import java.util.HashMap; +import java.util.Iterator; import net.sf.openrocket.util.StateChangeListener; import net.sf.openrocket.util.Utils; @@ -79,6 +80,10 @@ class FlightConfigurationImpl> implemen fireEvent(); } + @Override + public Iterator iterator() { + return map.values().iterator(); + } @Override diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index 731a5f5ba..bdd644588 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -11,14 +11,18 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.prefs.Preferences; import javax.swing.BorderFactory; import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; @@ -54,6 +58,7 @@ import net.sf.openrocket.gui.util.Icons; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.Markers; +import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.rocketcomponent.MotorConfiguration; @@ -105,13 +110,10 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec private static final ThrustCurveMotorComparator MOTOR_COMPARATOR = new ThrustCurveMotorComparator(); - - private final List database; private CloseableDialog dialog = null; - final ThrustCurveMotorDatabaseModel model; private final JTable table; private final TableRowSorter sorter; @@ -192,9 +194,11 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } database = db; - model = new ThrustCurveMotorDatabaseModel(database); - final MotorRowFilter rowFilter = new MotorRowFilter(model, diameter); + List unselectedManusFromPreferences = ((SwingPreferences) Application.getPreferences()).getExcludedMotorManufacturers(); + model = new ThrustCurveMotorDatabaseModel(database); + final MotorRowFilter rowFilter = new MotorRowFilter(mount, model); + rowFilter.setExcludedManufacturers(unselectedManusFromPreferences); //// GUI @@ -256,6 +260,69 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec }); panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para"); + { + final JCheckBox hideUsedBox = new JCheckBox("Hide motors already used in the mount"); + GUIUtil.changeFontSize(hideUsedBox, -1); + hideUsedBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + rowFilter.setHideUsedMotors(hideUsedBox.isSelected()); + sorter.sort(); + scrollSelectionVisible(); + } + }); + panel.add(hideUsedBox, "gapleft para, spanx, growx, wrap para"); + } + + { + + // Find all the manufacturers: + Set manus = new HashSet(); + for (ThrustCurveMotorSet s : database) { + manus.add(s.getManufacturer()); + } + final ManufacturerPopupSelector popup = new ManufacturerPopupSelector(manus, unselectedManusFromPreferences) { + + @Override + public void onDismissed(List selectedManufacturers, List unselectedManufacturers) { + ((SwingPreferences) Application.getPreferences()).setExcludedMotorManufacturers(unselectedManufacturers); + rowFilter.setExcludedManufacturers(unselectedManufacturers); + sorter.sort(); + scrollSelectionVisible(); + System.out.println("Here I am"); + } + + }; + + JButton manuFilter = new JButton("Manufacturer Filter"); + manuFilter.addMouseListener(new MouseListener() { + + @Override + public void mouseClicked(MouseEvent e) { + } + + @Override + public void mousePressed(MouseEvent e) { + } + + @Override + public void mouseReleased(MouseEvent e) { + popup.show(e.getComponent(), e.getX(), e.getY()); + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + }); + panel.add(manuFilter, "gapleft para, spanx, growx, wrap para"); + + + } // Motor selection table table = new JTable(model); diff --git a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java index d752c260a..9bdb5f2e9 100644 --- a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java +++ b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java @@ -6,6 +6,7 @@ import java.awt.Point; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -14,12 +15,10 @@ import java.util.Set; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import net.sf.openrocket.arch.SystemInfo; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.material.Material; +import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.simulation.FlightDataType; @@ -30,6 +29,9 @@ import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.BuildProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class SwingPreferences extends net.sf.openrocket.startup.Preferences { private static final Logger log = LoggerFactory.getLogger(SwingPreferences.class); @@ -575,9 +577,9 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { return materials; } - + //// Preset Component Favorites - + @Override public void setComponentFavorite(ComponentPreset preset, ComponentPreset.Type type, boolean favorite) { Preferences prefs = PREFNODE.node("favoritePresets").node(type.name()); @@ -599,35 +601,67 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } return collection; } - + //// Decal Editor Setting private final static String DECAL_EDITOR_PREFERNCE_NODE = "decalEditorPreference"; private final static String DECAL_EDITOR_USE_SYSTEM_DEFAULT = ""; - public void clearDecalEditorPreference( ) { - putString(DECAL_EDITOR_PREFERNCE_NODE,null); + public void clearDecalEditorPreference() { + putString(DECAL_EDITOR_PREFERNCE_NODE, null); } + public void setDecalEditorPreference(boolean useSystem, String commandLine) { - if ( useSystem ) { - putString(DECAL_EDITOR_PREFERNCE_NODE,DECAL_EDITOR_USE_SYSTEM_DEFAULT); - } else if ( commandLine != null ) { + if (useSystem) { + putString(DECAL_EDITOR_PREFERNCE_NODE, DECAL_EDITOR_USE_SYSTEM_DEFAULT); + } else if (commandLine != null) { putString(DECAL_EDITOR_PREFERNCE_NODE, commandLine); } else { clearDecalEditorPreference(); } } - + public boolean isDecalEditorPreferenceSet() { - String s = getString(DECAL_EDITOR_PREFERNCE_NODE,null); + String s = getString(DECAL_EDITOR_PREFERNCE_NODE, null); return s != null; } public boolean isDecalEditorPreferenceSystem() { - String s = getString(DECAL_EDITOR_PREFERNCE_NODE,null); + String s = getString(DECAL_EDITOR_PREFERNCE_NODE, null); return DECAL_EDITOR_USE_SYSTEM_DEFAULT.equals(s); } + public String getDecalEditorCommandLine() { - return getString(DECAL_EDITOR_PREFERNCE_NODE,null); + return getString(DECAL_EDITOR_PREFERNCE_NODE, null); } + public List getExcludedMotorManufacturers() { + Preferences prefs = PREFNODE.node("excludedMotorManufacturers"); + List collection = new ArrayList(); + try { + String[] manuShortNames = prefs.keys(); + for (String s : manuShortNames) { + Manufacturer m = Manufacturer.getManufacturer(s); + if (m != null) { + collection.add(m); + } + } + } catch (BackingStoreException e) { + } + + return collection; + + } + + public void setExcludedMotorManufacturers(Collection manus) { + Preferences prefs = PREFNODE.node("excludedMotorManufacturers"); + try { + for (String s : prefs.keys()) { + prefs.remove(s); + } + } catch (BackingStoreException e) { + } + for (Manufacturer m : manus) { + prefs.putBoolean(m.getSimpleName(), true); + } + } } From 246b9a6823ded2e71edbc4acd7dd367877747059 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Fri, 27 Sep 2013 21:54:05 -0500 Subject: [PATCH 07/47] Relocate the swing files into the swing/ project. They were misplaced with the rebase. --- .../gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java | 0 .../openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java | 0 {core => swing}/src/net/sf/openrocket/gui/util/CheckList.java | 0 .../src/net/sf/openrocket/gui/util/CheckListEditor.java | 0 .../src/net/sf/openrocket/gui/util/CheckListRenderer.java | 0 .../src/net/sf/openrocket/gui/util/DefaultCheckListModel.java | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/CheckList.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/CheckListEditor.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/CheckListRenderer.java (100%) rename {core => swing}/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java (100%) diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java diff --git a/core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java similarity index 100% rename from core/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java diff --git a/core/src/net/sf/openrocket/gui/util/CheckList.java b/swing/src/net/sf/openrocket/gui/util/CheckList.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/CheckList.java rename to swing/src/net/sf/openrocket/gui/util/CheckList.java diff --git a/core/src/net/sf/openrocket/gui/util/CheckListEditor.java b/swing/src/net/sf/openrocket/gui/util/CheckListEditor.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/CheckListEditor.java rename to swing/src/net/sf/openrocket/gui/util/CheckListEditor.java diff --git a/core/src/net/sf/openrocket/gui/util/CheckListRenderer.java b/swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/CheckListRenderer.java rename to swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java diff --git a/core/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java b/swing/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java similarity index 100% rename from core/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java rename to swing/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java From bae9d38a5172e95e0141fd00b0bdb59c4b34d494 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Sat, 28 Sep 2013 22:01:14 -0500 Subject: [PATCH 08/47] Further refinements to motor filter. Moved diameter configuration into popup. Added impulse class filters. Improved interface to CheckList and removed the double-click functionality from CheckListEditor. --- .../motor/thrustcurve/ImpulseClass.java | 45 ++++ .../ManufacturerPopupSelector.java | 96 ------- .../thrustcurve/MotorFilterPopupMenu.java | 251 ++++++++++++++++++ .../motor/thrustcurve/MotorRowFilter.java | 39 ++- .../ThrustCurveMotorSelectionPanel.java | 183 ++++--------- .../net/sf/openrocket/gui/util/CheckList.java | 12 + .../openrocket/gui/util/CheckListEditor.java | 3 +- .../gui/util/DefaultCheckListModel.java | 20 ++ 8 files changed, 426 insertions(+), 223 deletions(-) create mode 100644 swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ImpulseClass.java delete mode 100644 swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java create mode 100644 swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ImpulseClass.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ImpulseClass.java new file mode 100644 index 000000000..6b8fc6a9f --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ImpulseClass.java @@ -0,0 +1,45 @@ +package net.sf.openrocket.gui.dialogs.motor.thrustcurve; + +import java.text.DecimalFormat; +import java.text.NumberFormat; + +import net.sf.openrocket.database.motor.ThrustCurveMotorSet; + +public enum ImpulseClass { + + A("A",0.0, 2.5 ), + B("B",2.5, 5.0 ), + C("C",5.0, 10.0), + D("D",10.0, 20.0), + E("E",20.0, 40.0), + F("F", 40.0, 80.0), + G("G", 80.0, 160.0), + H("H", 160.0, 320.0), + I("I", 320.0, 640.0), + J("J", 640.0, 1280.0), + K("K", 1280.0, 2560.0), + L("L", 2560.0, 5120.0), + M("M", 5120.0, 10240.0), + N("N", 10240.0, 20480.0), + O("O", 20480.0, Double.MAX_VALUE); + + private ImpulseClass( String name, double low, double high ) { + this.name = name; + this.low = low; + this.high = high; + } + + public String toString() { + return name; + } + + public boolean isIn( ThrustCurveMotorSet m ) { + long motorImpulse = m.getTotalImpuse(); + return motorImpulse >= low && motorImpulse <= high; + } + + private double low; + private double high; + private String name; + +} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java deleted file mode 100644 index 447f864e2..000000000 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ManufacturerPopupSelector.java +++ /dev/null @@ -1,96 +0,0 @@ -package net.sf.openrocket.gui.dialogs.motor.thrustcurve; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.UIManager; - -import net.sf.openrocket.gui.util.CheckList; -import net.sf.openrocket.motor.Manufacturer; - -public abstract class ManufacturerPopupSelector extends JPopupMenu implements ActionListener { - - Map componentMap = new HashMap(); - CheckList list; - - public ManufacturerPopupSelector(Collection allManufacturers, Collection unselectedManufacturers) { - - JPanel root = new JPanel(new BorderLayout(3, 3)); - root.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); - root.setPreferredSize(new Dimension(250, 150)); // default popup size - - Box commands = new Box(BoxLayout.LINE_AXIS); - - commands.add(Box.createHorizontalStrut(5)); - commands.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); - commands.setBackground(UIManager.getColor("Panel.background")); - commands.setOpaque(true); - - JButton closeButton = new JButton("close"); - closeButton.addActionListener(this); - commands.add(closeButton); - - List manufacturers = new ArrayList(); - for (Manufacturer m : allManufacturers) { - manufacturers.add(m.getSimpleName()); - componentMap.put(m.getSimpleName(), m); - } - - Collections.sort(manufacturers); - - list = new CheckList.Builder().build(); - list.setData(manufacturers); - - if (unselectedManufacturers != null) - { - for (Manufacturer m : unselectedManufacturers) { - manufacturers.remove(m.getSimpleName()); - } - } - list.setCheckedItems(manufacturers); - - root.add(new JScrollPane(list.getList()), BorderLayout.CENTER); - root.add(commands, BorderLayout.SOUTH); - - this.add(root); - - } - - @Override - public void actionPerformed(ActionEvent e) { - - List selectedManufacturers = new ArrayList(); - List unselectedManufacturers = new ArrayList(); - - Collection selected = list.getCheckedItems(); - for (String s : selected) { - selectedManufacturers.add(componentMap.get(s)); - } - - Collection unselected = list.getUncheckedItems(); - for (String s : unselected) { - unselectedManufacturers.add(componentMap.get(s)); - } - - onDismissed(selectedManufacturers, unselectedManufacturers); - setVisible(false); - } - - public abstract void onDismissed(List selectedManufacturers, List unselectedManufacturers); - -} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java new file mode 100644 index 000000000..e49733413 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java @@ -0,0 +1,251 @@ +package net.sf.openrocket.gui.dialogs.motor.thrustcurve; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.border.TitledBorder; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.util.CheckList; +import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.gui.util.SwingPreferences; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.motor.Manufacturer; +import net.sf.openrocket.startup.Application; + +import com.itextpdf.text.Font; + +public abstract class MotorFilterPopupMenu extends JPopupMenu { + + private static final Translator trans = Application.getTranslator(); + + private final CheckList manufacturerCheckList; + + private final CheckList impulseCheckList; + + private final MotorRowFilter filter; + + private int showMode = SHOW_ALL; + + private static final int SHOW_ALL = 0; + private static final int SHOW_SMALLER = 1; + private static final int SHOW_EXACT = 2; + private static final int SHOW_MAX = 2; + + + public MotorFilterPopupMenu(Collection allManufacturers, MotorRowFilter filter ) { + + this.filter = filter; + + showMode = Application.getPreferences().getChoice(net.sf.openrocket.startup.Preferences.MOTOR_DIAMETER_FILTER, MotorFilterPopupMenu.SHOW_MAX, MotorFilterPopupMenu.SHOW_EXACT); + List unselectedManusFromPreferences = ((SwingPreferences) Application.getPreferences()).getExcludedMotorManufacturers(); + + // Manufacturer selection + JPanel sub = new JPanel(new MigLayout("fill")); + TitledBorder border = BorderFactory.createTitledBorder("Manufacturer"); + GUIUtil.changeFontStyle(border, Font.BOLD); + sub.setBorder(border); + + JPanel root = new JPanel(new MigLayout("fill", "[grow]")); + root.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); + + List manufacturers = new ArrayList(); + for (Manufacturer m : allManufacturers) { + manufacturers.add(m); + } + + Collections.sort(manufacturers, new Comparator() { + @Override + public int compare(Manufacturer o1, Manufacturer o2) { + return o1.getSimpleName().compareTo( o2.getSimpleName()); + } + + }); + + manufacturerCheckList = new CheckList.Builder().build(); + manufacturerCheckList.setData(manufacturers); + + manufacturerCheckList.setUncheckedItems(unselectedManusFromPreferences); + filter.setExcludedManufacturers(unselectedManusFromPreferences); + manufacturerCheckList.getModel().addListDataListener( new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + } + @Override + public void intervalRemoved(ListDataEvent e) { + } + + @Override + public void contentsChanged(ListDataEvent e) { + MotorFilterPopupMenu.this.filter.setExcludedManufacturers( manufacturerCheckList.getUncheckedItems() ); + onSelectionChanged(); + } + }); + + sub.add(new JScrollPane(manufacturerCheckList.getList()), "grow,wrap"); + + JButton clearMotors = new JButton("clear"); + clearMotors.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPopupMenu.this.manufacturerCheckList.clearAll(); + + } + }); + + sub.add(clearMotors,"split 2"); + + JButton selectMotors = new JButton("all"); + selectMotors.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPopupMenu.this.manufacturerCheckList.checkAll(); + + } + }); + + sub.add(selectMotors,"wrap"); + + root.add(sub,"grow, wrap"); + + // Impulse selection + sub = new JPanel(new MigLayout("fill")); + border = BorderFactory.createTitledBorder("Impulse"); + GUIUtil.changeFontStyle(border, Font.BOLD); + sub.setBorder(border); + + impulseCheckList = new CheckList.Builder().build(); + impulseCheckList.setData(Arrays.asList(ImpulseClass.values())); + impulseCheckList.checkAll(); + impulseCheckList.getModel().addListDataListener( new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + } + @Override + public void intervalRemoved(ListDataEvent e) { + } + @Override + public void contentsChanged(ListDataEvent e) { + MotorFilterPopupMenu.this.filter.setExcludedImpulseClasses( impulseCheckList.getUncheckedItems() ); + onSelectionChanged(); + } + + }); + + sub.add(new JScrollPane(impulseCheckList.getList()), "grow,wrap"); + + JButton clearImpulse = new JButton("clear"); + clearImpulse.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPopupMenu.this.impulseCheckList.clearAll(); + + } + }); + sub.add(clearImpulse,"split 2"); + + JButton selectImpulse = new JButton("all"); + selectImpulse.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPopupMenu.this.impulseCheckList.checkAll(); + + } + }); + sub.add(selectImpulse,"wrap"); + + root.add(sub,"grow, wrap"); + + // Diameter selection + + sub = new JPanel(new MigLayout("fill")); + border = BorderFactory.createTitledBorder("Diameter"); + GUIUtil.changeFontStyle(border, Font.BOLD); + sub.setBorder(border); + + JRadioButton showAllDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc1") ); + showAllDiametersButton.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showMode = SHOW_ALL; + MotorFilterPopupMenu.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.ALL); + onSelectionChanged(); + } + }); + showAllDiametersButton.setSelected( showMode == SHOW_ALL); + sub.add(showAllDiametersButton, "growx,wrap"); + + JRadioButton showSmallerDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2") ); + showSmallerDiametersButton.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showMode = SHOW_SMALLER; + MotorFilterPopupMenu.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.SMALLER); + onSelectionChanged(); + } + }); + showSmallerDiametersButton.setSelected( showMode == SHOW_SMALLER); + sub.add(showSmallerDiametersButton, "growx,wrap"); + + JRadioButton showExactDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3") ); + showExactDiametersButton.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showMode = SHOW_EXACT; + MotorFilterPopupMenu.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.EXACT); + onSelectionChanged(); + } + }); + showExactDiametersButton.setSelected( showMode == SHOW_EXACT ); + sub.add(showExactDiametersButton, "growx,wrap"); + + root.add(sub, "grow,wrap"); + ButtonGroup comboGroup = new ButtonGroup(); + comboGroup.add( showAllDiametersButton ); + comboGroup.add( showSmallerDiametersButton ); + comboGroup.add( showExactDiametersButton ); + + + // Close button + JButton closeButton = new JButton("close"); + closeButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPopupMenu.this.onClose(); + } + + }); + root.add(closeButton, "split 2"); + + this.add(root); + + } + + public void onClose() { + + ((SwingPreferences) Application.getPreferences()).setExcludedMotorManufacturers(filter.getExcludedManufacturers()); + + Application.getPreferences().putChoice("MotorDiameterMatch", showMode ); + + setVisible(false); + } + + public abstract void onSelectionChanged(); + +} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java index 1713c36ef..82d7483c9 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java @@ -1,6 +1,7 @@ package net.sf.openrocket.gui.dialogs.motor.thrustcurve; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -33,11 +34,22 @@ class MotorRowFilter extends RowFilter { private List usedMotors = new ArrayList(); // things which can be changed to modify filter behavior + + // Collection of strings which match text in the moto private List searchTerms = Collections. emptyList(); + + // Limit motors based on diameter of the motor mount private DiameterFilterControl diameterControl = DiameterFilterControl.ALL; + + // Boolean which hides motors in the usedMotors list private boolean hideUsedMotors = false; + + // List of manufacturers to exclude. private List excludedManufacturers = new ArrayList(); + // List of ImpulseClasses to exclude. + private List excludedImpulseClass = new ArrayList(); + public MotorRowFilter(MotorMount mount, ThrustCurveMotorDatabaseModel model) { super(); this.model = model; @@ -61,6 +73,10 @@ class MotorRowFilter extends RowFilter { } } + DiameterFilterControl getDiameterControl() { + return diameterControl; + } + void setDiameterControl(DiameterFilterControl diameterControl) { this.diameterControl = diameterControl; } @@ -69,16 +85,25 @@ class MotorRowFilter extends RowFilter { this.hideUsedMotors = hideUsedMotors; } - void setExcludedManufacturers(List excludedManufacturers) { + List getExcludedManufacturers() { + return excludedManufacturers; + } + + void setExcludedManufacturers(Collection excludedManufacturers) { this.excludedManufacturers.clear(); this.excludedManufacturers.addAll(excludedManufacturers); } + void setExcludedImpulseClasses(Collection excludedImpulseClasses ) { + this.excludedImpulseClass.clear(); + this.excludedImpulseClass.addAll(excludedImpulseClasses); + } + @Override public boolean include(RowFilter.Entry entry) { int index = entry.getIdentifier(); ThrustCurveMotorSet m = model.getMotorSet(index); - return filterManufacturers(m) && filterUsed(m) && filterByDiameter(m) && filterByString(m); + return filterManufacturers(m) && filterUsed(m) && filterByDiameter(m) && filterByString(m) && filterByImpulseClass(m); } private boolean filterManufacturers(ThrustCurveMotorSet m) { @@ -128,4 +153,14 @@ class MotorRowFilter extends RowFilter { } return true; } + + private boolean filterByImpulseClass(ThrustCurveMotorSet m) { + for( ImpulseClass c : excludedImpulseClass ) { + if (c.isIn(m) ) { + return false; + } + } + return true; + } + } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index bdd644588..3c09a7a15 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -83,23 +83,11 @@ import org.slf4j.LoggerFactory; public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelector { private static final Logger log = LoggerFactory.getLogger(ThrustCurveMotorSelectionPanel.class); + private static final Translator trans = Application.getTranslator(); private static final double MOTOR_SIMILARITY_THRESHOLD = 0.95; - private static final int SHOW_ALL = 0; - private static final int SHOW_SMALLER = 1; - private static final int SHOW_EXACT = 2; - private static final String[] SHOW_DESCRIPTIONS = { - //// Show all motors - trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc1"), - //// Show motors with diameter less than that of the motor mount - trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2"), - //// Show motors with diameter equal to that of the motor mount - trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3") - }; - private static final int SHOW_MAX = 2; - private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50; private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12; @@ -194,11 +182,8 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } database = db; - List unselectedManusFromPreferences = ((SwingPreferences) Application.getPreferences()).getExcludedMotorManufacturers(); - model = new ThrustCurveMotorDatabaseModel(database); final MotorRowFilter rowFilter = new MotorRowFilter(mount, model); - rowFilter.setExcludedManufacturers(unselectedManusFromPreferences); //// GUI @@ -215,86 +200,57 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Selrocketmotor"), Style.BOLD); panel.add(label, "spanx, wrap para"); - // Diameter selection - JComboBox filterComboBox = new JComboBox(SHOW_DESCRIPTIONS); - filterComboBox.addActionListener(new ActionListener() { + // Search field + //// Search: + label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Search")); + panel.add(label, ""); + + searchField = new JTextField(); + searchField.getDocument().addDocumentListener(new DocumentListener() { @Override - public void actionPerformed(ActionEvent e) { - JComboBox cb = (JComboBox) e.getSource(); - int sel = cb.getSelectedIndex(); - if ((sel < 0) || (sel > SHOW_MAX)) - sel = SHOW_ALL; - switch (sel) { - case SHOW_ALL: - rowFilter.setDiameterControl(MotorRowFilter.DiameterFilterControl.ALL); - break; - - case SHOW_SMALLER: - rowFilter.setDiameterControl(MotorRowFilter.DiameterFilterControl.SMALLER); - break; - - case SHOW_EXACT: - rowFilter.setDiameterControl(MotorRowFilter.DiameterFilterControl.EXACT); - break; - - default: - throw new BugException("Invalid selection mode sel=" + sel); - } + 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+"); + rowFilter.setSearchTerms(Arrays.asList(split)); sorter.sort(); - Application.getPreferences().putChoice("MotorDiameterMatch", sel); scrollSelectionVisible(); } }); - panel.add(filterComboBox, "spanx, growx, wrap rel"); - - //// Hide very similar thrust curves - hideSimilarBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideSimilar")); - GUIUtil.changeFontSize(hideSimilarBox, -1); - hideSimilarBox.setSelected(Application.getPreferences().getBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, true)); - hideSimilarBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Application.getPreferences().putBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected()); - updateData(); - } - }); - panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para"); - - { - final JCheckBox hideUsedBox = new JCheckBox("Hide motors already used in the mount"); - GUIUtil.changeFontSize(hideUsedBox, -1); - hideUsedBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - rowFilter.setHideUsedMotors(hideUsedBox.isSelected()); - sorter.sort(); - scrollSelectionVisible(); - } - }); - panel.add(hideUsedBox, "gapleft para, spanx, growx, wrap para"); - } + panel.add(searchField, "growx"); { // Find all the manufacturers: - Set manus = new HashSet(); + Set allManufacturers = new HashSet(); for (ThrustCurveMotorSet s : database) { - manus.add(s.getManufacturer()); + allManufacturers.add(s.getManufacturer()); } - final ManufacturerPopupSelector popup = new ManufacturerPopupSelector(manus, unselectedManusFromPreferences) { + + final MotorFilterPopupMenu popup = new MotorFilterPopupMenu(allManufacturers, rowFilter) { @Override - public void onDismissed(List selectedManufacturers, List unselectedManufacturers) { - ((SwingPreferences) Application.getPreferences()).setExcludedMotorManufacturers(unselectedManufacturers); - rowFilter.setExcludedManufacturers(unselectedManufacturers); + public void onSelectionChanged() { sorter.sort(); scrollSelectionVisible(); - System.out.println("Here I am"); } }; - JButton manuFilter = new JButton("Manufacturer Filter"); + JButton manuFilter = new JButton("Motor Filter"); manuFilter.addMouseListener(new MouseListener() { @Override @@ -319,7 +275,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } }); - panel.add(manuFilter, "gapleft para, spanx, growx, wrap para"); + panel.add(manuFilter, "gapleft para, wrap para"); } @@ -382,8 +338,32 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec scrollpane.setViewportView(table); panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para"); + { + final JCheckBox hideUsedBox = new JCheckBox("Hide motors already used in the mount"); + GUIUtil.changeFontSize(hideUsedBox, -1); + hideUsedBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + rowFilter.setHideUsedMotors(hideUsedBox.isSelected()); + sorter.sort(); + scrollSelectionVisible(); + } + }); + panel.add(hideUsedBox, "gapleft para, spanx, growx, wrap para"); + } - + //// Hide very similar thrust curves + hideSimilarBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideSimilar")); + GUIUtil.changeFontSize(hideSimilarBox, -1); + hideSimilarBox.setSelected(Application.getPreferences().getBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, true)); + hideSimilarBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Application.getPreferences().putBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected()); + updateData(); + } + }); + panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para"); // Motor mount diameter label //// Motor mount diameter: @@ -391,42 +371,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter)); panel.add(label, "gapright 30lp, spanx, split"); - - - // Search field - //// Search: - label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Search")); - panel.add(label, ""); - - 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+"); - rowFilter.setSearchTerms(Arrays.asList(split)); - sorter.sort(); - scrollSelectionVisible(); - } - }); - panel.add(searchField, "growx, wrap"); - - - // Vertical split this.add(panel, "grow"); this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para"); @@ -615,17 +559,8 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec panel.add(layer, "width 300:300:, height 180:180:, grow, spanx"); - - this.add(panel, "grow"); - - - // Sets the filter: - int showMode = Application.getPreferences().getChoice(net.sf.openrocket.startup.Preferences.MOTOR_DIAMETER_FILTER, SHOW_MAX, SHOW_EXACT); - filterComboBox.setSelectedIndex(showMode); - - // Update the panel data updateData(); setDelays(false); diff --git a/swing/src/net/sf/openrocket/gui/util/CheckList.java b/swing/src/net/sf/openrocket/gui/util/CheckList.java index 6eb233092..fa7c17948 100644 --- a/swing/src/net/sf/openrocket/gui/util/CheckList.java +++ b/swing/src/net/sf/openrocket/gui/util/CheckList.java @@ -159,6 +159,14 @@ public class CheckList { return unchecked; } + public void checkAll() { + getModel().checkAll(); + } + + public void clearAll() { + getModel().clearAll(); + } + /** * Resets checked elements * @param elements @@ -167,6 +175,10 @@ public class CheckList { getModel().setCheckedItems(elements); } + public void setUncheckedItems( Collection elements ) { + getModel().setUncheckedItems(elements); + } + public void toggleIndex(int index) { if (index >= 0 && index < list.getModel().getSize()) { DefaultCheckListModel model = getModel(); diff --git a/swing/src/net/sf/openrocket/gui/util/CheckListEditor.java b/swing/src/net/sf/openrocket/gui/util/CheckListEditor.java index 0dafdc111..68680670f 100644 --- a/swing/src/net/sf/openrocket/gui/util/CheckListEditor.java +++ b/swing/src/net/sf/openrocket/gui/util/CheckListEditor.java @@ -73,7 +73,8 @@ final class CheckListEditor extends MouseAdapter { if (e.getClickCount() > 1) { // clear all and check selected for more then 1 clicks - model.setCheckedItems(Arrays.asList(model.getElementAt(index))); + // Original implementation had this implementation. I didn't like that behavior. +// model.setCheckedItems(Arrays.asList(model.getElementAt(index))); } else { // simple toggle for 1 click model.setCheckedIndex(index, !model.isCheckedIndex(index)); diff --git a/swing/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java b/swing/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java index 647019400..cbb4e5919 100644 --- a/swing/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java +++ b/swing/src/net/sf/openrocket/gui/util/DefaultCheckListModel.java @@ -107,6 +107,16 @@ public class DefaultCheckListModel extends AbstractListModel { return Collections.unmodifiableList(items); } + public void clearAll() { + checks.clear(); + fireContentsChanged(this, 0, checks.size() - 1); + } + + public void checkAll() { + checks.addAll(data); + fireContentsChanged(this, 0, checks.size() - 1); + } + public void setCheckedItems(Collection items) { // if ( CollectionUtils.isEmpty(items)) return; @@ -121,4 +131,14 @@ public class DefaultCheckListModel extends AbstractListModel { } + public void setUncheckedItems( Collection items ) { + + List correctedItems = new ArrayList(data); + correctedItems.removeAll(items); + + checks.clear(); + checks.addAll(correctedItems); + fireContentsChanged(this, 0, checks.size() - 1); + + } } From 572b14de13c118e68bf074952ba931c78b8f955b Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Sat, 28 Sep 2013 22:16:31 -0500 Subject: [PATCH 09/47] Localize motor popup and new fields in ThrustCurveMotorSelectionPanel. --- core/resources/l10n/messages.properties | 6 ++++++ .../motor/thrustcurve/MotorFilterPopupMenu.java | 16 ++++++++-------- .../ThrustCurveMotorSelectionPanel.java | 6 +++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 7d169196e..267f1e13d 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1087,6 +1087,8 @@ StorageOptChooser.lbl.Saveopt = Save options ! ThrustCurveMotorSelectionPanel TCMotorSelPan.lbl.Selrocketmotor = Select rocket motor: TCMotorSelPan.checkbox.hideSimilar = Hide very similar thrust curves +TCMotorSelPan.checkbox.hideUsed = Hide motors already used in the mount +TCMotorSelPan.btn.filter = Filter Motors TCMotorSelPan.SHOW_DESCRIPTIONS.desc1 = Show all motors TCMotorSelPan.SHOW_DESCRIPTIONS.desc2 = Show motors with diameter less than that of the motor mount TCMotorSelPan.SHOW_DESCRIPTIONS.desc3 = Show motors with diameter equal to that of the motor mount @@ -1108,6 +1110,10 @@ TCMotorSelPan.title.Thrustcurve = Thrust curve: TCMotorSelPan.title.Thrust = Thrust TCMotorSelPan.delayBox.None = None TCMotorSelPan.noDescription = No description available. +TCMotorSelPan.btn.checkAll = Select All +TCMotorSelPan.btn.checkNone = Clear All +TCMotorSelPan.btn.close = Close + ! PlotDialog diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java index e49733413..b124c6036 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java @@ -57,7 +57,7 @@ public abstract class MotorFilterPopupMenu extends JPopupMenu { // Manufacturer selection JPanel sub = new JPanel(new MigLayout("fill")); - TitledBorder border = BorderFactory.createTitledBorder("Manufacturer"); + TitledBorder border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.MANUFACTURER")); GUIUtil.changeFontStyle(border, Font.BOLD); sub.setBorder(border); @@ -99,7 +99,7 @@ public abstract class MotorFilterPopupMenu extends JPopupMenu { sub.add(new JScrollPane(manufacturerCheckList.getList()), "grow,wrap"); - JButton clearMotors = new JButton("clear"); + JButton clearMotors = new JButton(trans.get("TCMotorSelPan.btn.checkNone")); clearMotors.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -110,7 +110,7 @@ public abstract class MotorFilterPopupMenu extends JPopupMenu { sub.add(clearMotors,"split 2"); - JButton selectMotors = new JButton("all"); + JButton selectMotors = new JButton(trans.get("TCMotorSelPan.btn.checkAll")); selectMotors.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -125,7 +125,7 @@ public abstract class MotorFilterPopupMenu extends JPopupMenu { // Impulse selection sub = new JPanel(new MigLayout("fill")); - border = BorderFactory.createTitledBorder("Impulse"); + border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.TOTAL_IMPULSE")); GUIUtil.changeFontStyle(border, Font.BOLD); sub.setBorder(border); @@ -149,7 +149,7 @@ public abstract class MotorFilterPopupMenu extends JPopupMenu { sub.add(new JScrollPane(impulseCheckList.getList()), "grow,wrap"); - JButton clearImpulse = new JButton("clear"); + JButton clearImpulse = new JButton(trans.get("TCMotorSelPan.btn.checkNone")); clearImpulse.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -159,7 +159,7 @@ public abstract class MotorFilterPopupMenu extends JPopupMenu { }); sub.add(clearImpulse,"split 2"); - JButton selectImpulse = new JButton("all"); + JButton selectImpulse = new JButton(trans.get("TCMotorSelPan.btn.checkAll")); selectImpulse.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -174,7 +174,7 @@ public abstract class MotorFilterPopupMenu extends JPopupMenu { // Diameter selection sub = new JPanel(new MigLayout("fill")); - border = BorderFactory.createTitledBorder("Diameter"); + border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.DIAMETER")); GUIUtil.changeFontStyle(border, Font.BOLD); sub.setBorder(border); @@ -222,7 +222,7 @@ public abstract class MotorFilterPopupMenu extends JPopupMenu { // Close button - JButton closeButton = new JButton("close"); + JButton closeButton = new JButton(trans.get("TCMotorSelPan.btn.close")); closeButton.addActionListener(new ActionListener() { @Override diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index 3c09a7a15..bd24a5ca5 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -203,7 +203,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec // Search field //// Search: label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Search")); - panel.add(label, ""); + panel.add(label, "split"); searchField = new JTextField(); searchField.getDocument().addDocumentListener(new DocumentListener() { @@ -250,7 +250,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec }; - JButton manuFilter = new JButton("Motor Filter"); + JButton manuFilter = new JButton(trans.get("TCMotorSelPan.btn.filter")); manuFilter.addMouseListener(new MouseListener() { @Override @@ -339,7 +339,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para"); { - final JCheckBox hideUsedBox = new JCheckBox("Hide motors already used in the mount"); + final JCheckBox hideUsedBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideUsed")); GUIUtil.changeFontSize(hideUsedBox, -1); hideUsedBox.addActionListener(new ActionListener() { @Override From d692288f68b9462fa5bfbce3acb31c846ce7bdc4 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Mon, 30 Sep 2013 11:38:54 -0500 Subject: [PATCH 10/47] Added build.xml driver in root directory. Moved a few files around to support unittest. --- .gitignore | 3 + build.xml | 91 ++++++++++++++++++ core/.classpath | 2 +- core/build.xml | 59 +----------- .../annotation-detector-3.0.2-SNAPSHOT.jar | Bin 15780 -> 0 bytes core/lib/annotation-detector-3.0.2.jar | Bin 0 -> 14818 bytes lib-ant/ant-contrib-1.0b3.jar | Bin 0 -> 224277 bytes .../hamcrest-core-1.3.0RC1.jar | Bin .../hamcrest-library-1.3.0RC1.jar | Bin .../lib-test => lib-test}/jmock-2.6.0-RC2.jar | Bin .../jmock-junit4-2.6.0-RC2.jar | Bin .../lib-test => lib-test}/junit-dep-4.8.2.jar | Bin {core/lib-test => lib-test}/test-plugin.jar | Bin .../uispec4j-2.3-jdk16.jar | Bin swing/.classpath | 2 +- swing/build.xml | 9 +- swing/test/net/sf/openrocket/Estes_A8.rse | 40 ++++++++ .../test/net/sf/openrocket/simplerocket.ork | Bin 18 files changed, 143 insertions(+), 63 deletions(-) create mode 100644 build.xml delete mode 100644 core/lib/annotation-detector-3.0.2-SNAPSHOT.jar create mode 100644 core/lib/annotation-detector-3.0.2.jar create mode 100644 lib-ant/ant-contrib-1.0b3.jar rename {core/lib-test => lib-test}/hamcrest-core-1.3.0RC1.jar (100%) rename {core/lib-test => lib-test}/hamcrest-library-1.3.0RC1.jar (100%) rename {core/lib-test => lib-test}/jmock-2.6.0-RC2.jar (100%) rename {core/lib-test => lib-test}/jmock-junit4-2.6.0-RC2.jar (100%) rename {core/lib-test => lib-test}/junit-dep-4.8.2.jar (100%) rename {core/lib-test => lib-test}/test-plugin.jar (100%) rename {core/lib-test => lib-test}/uispec4j-2.3-jdk16.jar (100%) create mode 100644 swing/test/net/sf/openrocket/Estes_A8.rse rename {core => swing}/test/net/sf/openrocket/simplerocket.ork (100%) diff --git a/.gitignore b/.gitignore index 91aeba86b..ae7b42e47 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ /core/resources-src/pix/sormus.xcf.gz /core/resources-src/pix/splashscreen-sormus.png /core/resources-src/pix/splashscreen-sormus.xcf.gz + +/swing/build +/swing/tmp diff --git a/build.xml b/build.xml new file mode 100644 index 000000000..8a746fad8 --- /dev/null +++ b/build.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Checking project for FIXMEs. + + + + + + + + + + + + + + + + + + + + + + CRITICAL TODOs exist in project: +${criticaltodos} + No critical TODOs in project. + + + + + + + + Checking project for non-ASCII characters. + + + + + + + + + + + + + + + + + + + + + + Non-ASCII characters exist in project: +${nonascii} + No non-ASCII characters in project. + + + + diff --git a/core/.classpath b/core/.classpath index 3345ea11a..19534aee6 100644 --- a/core/.classpath +++ b/core/.classpath @@ -23,7 +23,7 @@ - + diff --git a/core/build.xml b/core/build.xml index 9f41533e9..819548e25 100644 --- a/core/build.xml +++ b/core/build.xml @@ -7,7 +7,7 @@ - + @@ -84,61 +84,6 @@ - - - - - - - - Checking project for FIXMEs. - - - - - - - - - - - - - - - - CRITICAL TODOs exist in project: -${criticaltodos} - No critical TODOs in project. - - - - - - - - Checking project for non-ASCII characters. - - - - - - - - - - - - - - - - Non-ASCII characters exist in project: -${nonascii} - No non-ASCII characters in project. - - - Building unit tests @@ -147,7 +92,7 @@ ${nonascii} Running unit tests - + diff --git a/core/lib/annotation-detector-3.0.2-SNAPSHOT.jar b/core/lib/annotation-detector-3.0.2-SNAPSHOT.jar deleted file mode 100644 index bab8dc3e1a077a6c1dcbdc93641ffe95308131c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15780 zcmbVz1yEht(lrv?U4jL7cXxO9;1b;3-Q6xuaDr=aC%6X<8r4$7Nc`D)^rt|NDS?{gx3?7NC)k6{VB^pBW^O$*Wmg zXSUGd>%ZB7fq+o|XeJ{dDxodRb^(mCS2WaNicaT zJ@7CQym2tzDB{_}MJghPzQ}EL#G|sh>x2QLp)b)37MhZlyhLo(<T zp4e@eW;};q^2+IWDAg-;zecg4p@KhC2Kvf{vD5El z{nY^GzXs;kCN@_34*!QC`kxK;t*vbw^&QP^tp5)m#6SBuIhtGkZVmaLv98^|sgHhz z=Aj1yqW!b=A0i0+9!%KS(b&+@#-7&DQs2R0RMX8K`kS^^DeDb8BmN9wTE*v zX^(e(D*Nt{k)ufp@NKG9u82Y4-uvA-sQsD`x98^=zAyOkX;i}EYD2*YQng~+dA#l6 ze0dkMH4Rz-3r4(PN8=u`g3qde7>NrzbKZBOWCeGL%-PA?&D77ZKB|E6JaQYBM zoZs=I%WP}a6o+=~t!sgeHyVvw;XOC3y+n^SO8JD0K{!3rD#>cmw-Ze*Vc~=eZ+4L< zOd`WWe1rq_Q>Vr>9I}Q7gUA^E))L|mCBMU!#y!|K(qDLgRhH(;1kWnmUF}MMvx>FSeOy0@zZuuhN@moA5S+S)9THz)4-=+eO9u zObXK|K%0!`{ZXI_zZ%HR0itE=C%}RaxHvV__ZPteiN8sh0WmLD&tMv_SZO zvamVXtvImWlZP1ZvDYy*{YDDhz%R)obk*bKl~;II-nCvT-$ciV8L9%-y)Mp;h#=Cf zBDpu3>zDJ{4opea9!+hhK^JEps~9^v1iW++L`eO{Iq1H}@75NzS39*?)50Ir&(=pp zjT;;KrqXt#xa2(m6tlb3q%6~IW!TEpC6o`nYf-s zybwFmDw2O%qTH?EI`;<{+rbtBR166I$>g)bprAZu#&>!VGyBuda$gYjd34VB$4RQJ zf*{vX&g6=+h34DB>0Mn)#K-39%E;LBGembz{Nkr*il|gr_c0*yHf*X$9fZggNlu|1 z%ykIbLD_}cS|+Z83GyP>cYWJ9a3a;pPb~YkN8~Mq@QL3eLJN_vcXo14VC^I{s+q~;T=$*lNRP z#YKM!9;I9f9_@r1-^b`+Lyl7r#IOW)5^+9^pNPeJSPOPrOIrp(U@th zZ5QOI2xv;fST+Wa`+jVELaCQWC?npP73@${AMeOu@?n3n&~Hs|f|4vL!!XV-v+L@N zw}iBh*BL-{SF|IFx~z^5qB}2VzPFcBFjz#%OO(@xxS&jrDs|>m!v6u>m!gQQFv!0@ zCuEnR@H2}2y8!sfP=Sx0sEgv@wJd(~@Wj|T8z+WIh*E`-Ma|yq%sB?sjo6-StA?j- zo+E1yx8Bm_^tTY6(mN2a-m&=p(5otgMiUD6yo}o|W%01h>Bj<}uW1hm1C9#}ZPSQX zom>-ylW2rLZdigH#l;7vBwm6)$d_D(e~IoHA;lzwbC1{9!N-0i@}6H4yB#Rn0}cZN zDQCPTdsGVW>5m~SzM*w5^WPJOd&YfPS>rp!N16X*Nhtj^xW+fEiQd)w5ul?Ry)D)GxY0GT1C1MBe<&I=cx>FLEI2xbvmpYkg6Exw=tDV|Bv zu#@-XTFPxUc*jU3<|Ey6V;AojGJ`RZ2Igbwyt4oR$N1Fe%@y$&EBku@Q~sG0lM29m z3RQ|p2#}`je`<4T@z~sDbLs%ez3DZPgO7OV!ia(GDQMe4(ak+*7z(!uqUb=(i5aS8 zqR`IkDe=9=7OLAT3>uomE6z+JFOU{XaEu&Ea*CehBDu>{pv@8go6SVPo0Bsa!s$Tp zfVYCxi0o2fRnwp9lTXK{RpGJ*F5q;@`|TxrJlI@t(t`T|CT3WG;R!~T6iJ>zf)3;v zGwT2hJ65Hj68;JNeHsZ(TZswS021WRpdLWHNDHjY95+;pN~1*0eh7^Did{U55JcXZ zaAy48rxyo}EP6x=R?b5M%n~ju8c2$KWxFu1%>%wd4yaEDJ=N>)oYZWTn#iVLl5ysU z>Eu*7q4wCP;itb()i}yQsF_Cu`nc$skz*bkmr5rv%F1b|awl)J@nNztkO=VvcNbB; z^}+9codCVDA;UoyH=uy_`t1ajf~HoxbhSu_BxY7uKv|%HOn{JGXeSy>8(lFm{PCr0 zJ}oxfVX{4qj$QIK1R_%QpsHU&X+HsBASO}P9?w2o)RuWKqriiG7=?Idrspxos?t6L z95-BETvwvO>Sr<3*aarUFfbJYc{-R$vJVgU-}aLT&L9L+i6bDLa=m9*bhr?Y3G8<= zEE{bJ+zj(;GS%UzyCvF_;Q4j!%(0~;N{K%5v9ByWYZ0Vb7Sc)Rj$!j{wPGnxFX`vH z>8eD@z8NO-C*T<(L@CgaGeegt zK$t`mzg7d8+U@bZnNVSUMCw%ITC)K1nE`$eb885HyXnif?#l;ts|0^L?OW;4nGO7? z0rAMuImfj&4*Kj5`b@ai2Jec$_7>+f%D3aYuMNnfF~lQU=XP2t+)~OhH$nRfcO`gRh5Pu*r>GfTq0t|o-XTFyBxt9$wRt8sJ(8QJi&AVNex`&U z#wS^%-(0C%%Tky^h$Azcy#%{CECQUL#O;u~RZpj6vk3P4??s4_bJ^(Y&O&Yhjg5;i z3A&~7y*}_}n^~~Dw)?r^59B^{#z|^8A0LI3foyBQWO;>yfFh`?b`fQ|aKQ%5v75U$ znX$elZYFvqj?E{Y;1`tK;3anCPQ-k-K}`&~w&TB1qU8|~xugQf$SHt50agOMxt@{* zFS(i%pZQyMW9aw!dTd`nHa)NQ`b)Fk+xt>zJ34K<&c8e~V zLdzh*ugRZq{2zlJGkde#V3}eJO(;Lh0S8GHz2CnKl;` zVfP?_syC(BQ$6+Tmrj{&0)I$`bE>H-CSA(T^!W-&Q!z#{GHG21?c*$}AKh*hm^{Nj zEX8(LD`u`CQ-PnX+?En4>0=EdW%ya!?V3T8S8!)uO{GYeMTwe3KaZtKbfFf6l%m05 zO?d&24d3;kV5moVI285yToP7|5yxy#U$ek(+(B%&H9ty6f`qtvW>02*UV^fKLDQe? zM3PfvbYx}>TjvYb%rGZhbx4H~oz5`tJ*?@ZOoq(+P3`8Si(T0);Si2&yze0HiDa8+ z8sa`1C6{Wng=%h`QR{GfTN>OcJGK;=y%838*`pVqqlOb{mchB)QCClPZ*n$OIp4B+ z@fj1zQwJKq7vX?xdAld4&a$hbnSYHn6TyFKs@@tCj41dVo|6T(|32txRSxe%hzyUA z4-s8b_e54-hU{I=&dDWCQ+Bt~uAAOieo4*^Smd&SDb#A`F8+K7>S)=Xh&$AE>hMmt z`}<&p@Dx=2{?>K0Wxa|g7>f-$i$Qj#UL!=O|#9_Tb;Qqzi<2=#SBiw5+>oc7SCxUDvqU?M}10qw73u4Zo z4AZ>v-XT^3>&2OCL@Pl^M;grBZF{W9sSjaE+qQ7mb%zB7>N+02luKaCV$HtWL&^>X zIsL}n9|lkSiIzPPT|CU;X$mh?Rw-X<{8dZZ(Etg<8Gg2ZR+wbZisE*7*k3c^u0E zXsyxcLJv|+K_6~CHG@8rj@0M9kMX8*SvqrqGOh;ai@R+eP3~oNwj(88FC0A#hDe)q zQ|ED_-}h_zQ9QX{$1fD>A&*%5{!0aQzm2IlTE-2?sys%qd5SX_`@Y zbJj$)?@sAqqxq(Ztax=2lp#FG?*r>vk~~}C#*lLcTQeV*g_xRBOo-G{?D5Ojydf6q zSr_Uv0_Y9nbp#eEgdt?KVeH6y4(}4x3t4t*wiQzovOiOA?^7$dQ(g8r=9(z(U#8Q$ zxqcA{n=93ds|&(a#wd`G$1sTL4vu@*Qt1^tv*hWtJ2g{Lm=F}qDjPEu9-%q4dznimezzjwv+$ENpRFR~pRo4aFvT-uz7ownM#VC7}xiZD!Q$ z9yvEL8cj8>OTlQjWO~qtkj)C>IZUI_997A*FumaTcO;7i)bsC7;1G8byCYqwf>jEv@y<-vV($)1-8|{PmQcPYaIFltEx_z8)w(TdgA-0a& z;Ty#p%zPsS=#!h^ctoAOu-Wq*dEe}!G|!$F1;kf`D4!>6mCoQJtz%QAlw{G)WFkc|)rU*I$Ax4KC!8!yz zZdV<7IwaqJpF53p!$wF$X|OMxh;pr3MpzgvigCb75t6BQ#8NBX2}F?F6-%6W$D<`9 zX32tEJ(CY=<5%<03%b z6)83HC9AhZ?Rmm$$+M}*GY&AvIWzHwk;}IEmT+?3Gzlg-!GI{cwC(yfHUx0MT6On! zb@#@kj6;$q^Y<<+^cWM>nUy^5Y97doNYr}_5(8vfHHI**k#j@iY7K9oTn*>m89sKF zbw1LLH`HwoG{7C-ZdR|0Sys6+USekXUdCJsI*4-PKphqGIyv}t1{YX{HtE@2GH4y* zIX$<4M(w`}+u4P-n9V;LOAh53kt?p`7$;P0P1ORjpG#y4e9ZeU3t2N)rc7$IsPrqH zNq0Wfa=cF;_O`;yE*agq+oh^!)Xi71ZBb3D0^`miOKa%#)-*AHyI+exiW??AW}zHp zBzjJGo|SL9uyf(2EX*fBE+ODIO+Ho=-xo%yDcRF^lyoyW*0w)aOOrHUwgait zaEp0VD&T#tTI#o&RCiQzo;sK8e`1{YF;gaJ(SH6Cx^!wxrS((yJ@?RBA0^db554|e z#F4hLzKREjD=cl77H2@|AxgIjt+`=wtCOy?;?#r%ymQ6Y2i~PetoY;-fG8RhMU1Ic z!be8~-3srn#7T}zpsy?yaZj!OE0s0nFkCLv56ilM$Y;>qi^&&+`{7BSiKD0Yzg6M? zTiJf2X3 zMK@$aC=?+fuP5jzCaw>S^8@Yrel{{JMFYY`gSJZr9Ju+IP~&@#iNzdR?;9287v~oS z{03746Dt)<-PcovV6+cNad@zEv>)pCe#L*8vNs|&zkX$C@0BU8 zf5Viro2~Jm*@}vrlzeURh@8wc?^UQ+AUy)dg|FqUO@|YN%Okx}^NcapR{WZ8RO{dI zHN@K&pQI25V1RZ{Z*)w*GX{Hin=uBIkw;hcu8O2eX1<=A;VVzO+^wR;X1upW}tf7g#MEM0SJ9kFy7| z$TB{0lQ%gFa;l@nKn5nqu)OHZkk(Znt9)01=@u&*G_bsHTy-Sf6|Fvzn~2!>rKO_# z@v8B;3d@6Lk@`;+Ec2L;Tpb4{q;93kx>yDU97^;Z)rVC6? z*T9RG!h&$&6aHKP{+d`ALk`7a4<4QyQ)`-pE-x>}2~acx@;u@^>OA40x;M*L88kIH z49&M>nT!Bi&i1rnwlp=xlsjXR1NRzX)G?1d;-;kdbgRYlhG^2w0M0Xnn zd&l43!S)b8f&6(cXOW7lKE6_k@=7VszoAsd*wM`9S5D)8aEciGQsOkNV^Pkt3-cdR z70i$%MG#T{Ln`iEB1@d**N}@f&|7%}?Mc)leFPyBZC@Tr~+OJvLpXVu~=H4%9^ozM-x zWd}4f39yBG`S^RI`usxcDH%JhBp477>1(@}<{xPl`qAkVHMcaDcwH3rfA9T8sp=@9 zn4)^4Bcp%qneJFyO{sby*3ogyxoKt7 zx(9YNvo=%GICfcdiC?!l&uhxoi9QwGW3=kE>M-Uo<~GJb_wX?N9v^r<=!BU&X8Ig+ zwhGNuxwYtB;3R2QM$$m4%de0|IlkZlANN#YoRq1A0h&_4X2yec`$w=T;gx7>iL0^7KVw zR!YvK8Nn`4UZ_7W=>c}j_GeKm->A8d&S}B~=N5ai1olIZW7(z#dU2=n_()q7`#Y^O%e< z#;UgtU~I#2rC{6P4v#71y7jU!G6{)JMyT}})5)0N){G8U_+Les^hz)F)ksNAryikz zA+Tw}uq0m1YqcyI63J_ya!m$k7AEFEqljAcq+wP#RDj^rt9v1n6n(=&Ak7C@-G=a2~bU?IuPJ8j>?}ECfbs)+)uNO+c zN3#kwHJAmc^LuT)6EsbLfm{{qVLa3f20tv?0Ithf4Y0n6DxElQNS96WMG+}Xkh5bP zQW0FpxCPrctg|&Q&Z+h<^e{`Vg|37CI^sbcIt(uNl*x~tURI%daa$ZwpwcQUfb&qw ztEhsnzp=+h3wA=32}=-ECLSJHJ?=l%l|v#ifN&a!uXmw$&d@#NS>sr)rM0L$m5G9w>3-JnY4 zx(i^|=g10ZR$LkG`>6K>R}jtdG2Z+hH3#pS$4Q%=Y4k3>WO8n;topo(KOzRQ1+y;` zV;Fd1X?Vmjo@;y4AWs(zXX)pX<|_*_#om2E&bnSaXO8Ur#z_uIiJ{m){4PSn%sz&W zB@^E;a%;(+0Euu{*7K?9HhRm(b-dwX?7DYlwIO>K+X||q^AwC|SE#Ai zKK`HIb$em=p-ZptIjh%&nd%=GWleM2zgd!%wB%3(P@fBRn(K6|x4h4SF&6Wvl@)Tq zP{|kbzX-vF^`;cSSIr-@F6jh3qdWoMhllac^xhu&oK}*XnrP)OlA17?j*Vp8wO?d1 zJ#5aU*Z?Vh5t)e6qAVnYZ{jY%?lkDm9ctMP7@MY7}KOygq!bD!<7leDf?MVPe1Dy^OI$A5F+^GeZGyBVBRSPIjKRrJI=atB zQsd`~nR)af1Gg5lL{_)~)Pu#sjpMX@Gs1D6Z*|UxH43wE!!{YVVn|b$mg?^zI#(WJP&zGcA8;=t5kHDr}V9d2IKWIUm8fH^1=mR zDxUH82vJ+8cTx86JBVP{5E{Y7Fmt0*x5*%zHa)+uH`z^YlnSmdv#~t6AXK5m#Gt{m zE;iqUIka)-KhYmAu2_N}Pj*dPQByInVh|RA`H-$$!+Fl33K9jFRhfRbicWKsf1G83 z5`bqjyP@i3MU{L=Il%ms_wAAl0trQgjE+2Z2$Z?_o)ILEtqzamgah^5xB5Gj z`Z*QIIH?>Ww|~5}x4(U415qHaWFSLWN$l*PEXoq>yL?+eXK+tg?Oqao_Y}LJrh!2< zv*Ua_w7DIZ@;@Jxr&u%Na?&0DO7Se9=u&n)MrIm>QC1ocl=U;6`!cCy~^3 zEk~t%DSsxLHTZB@RIPHH$+^7pP9>`hiC2@w872mI~ zYha6WQ87l2MX?N&&_;!W=@^D521nNo_{16+FixC{|h6ckRtS4Me?ykg)A>u$0On)w; z-9n5l#q10V#!b6IC#s;h-a7#<#GRs|9jzbY>IyR4T|)?<%DW3T%R(JhV&IN4?cGgh z@PrvJZNlzrG@K9cgdJb!qRdh|^AE>BV;)P|*@?>YpZHwoFFx36KrYV+J>zes8J%j* zZJ=3r5$bQ7X9KVhJ;a%_#ATz|RCY5TBbVKCaMNsvTGHU*E60#IqmDu{ousCiXR}Ux zkg-_NosxQLHeQH6PHmXwi=UG!U|_Pz*TEL@d>4nL+W?(in1w$H6>dj<=% zKSKR;g?tx1EmE6ainP8HQ4z6BMTLIozm(4Dkk11Lm^b%H?5v_W#_VPBH4DF(b*i^h> zJ3Gas82VM71?D?0{V(d#8Q6{GU@rT;qVjH9m90FY9D&9&v@H)})4Tmz%eG)#$X;j^ z7tQq(a=0=Uew_WHQG%0S_M_vJY{&O%wo;|~>ogd{7!Qxwz;;jDbi7Q(?YWsmK#&*| zs|w6Q9Y;p886z`%t!g64rWkb$6P!P%vIy_TDiK5-W6(0^(-Q!mpxsM(neJMvb9O=^ zV75ql>|4v#s+cfUn=Q8;x-~oqur|du5OAU`Zt--HM~AW7V`{r+sjzt?ys+bUr(^lE z!_`>0gYnH$(U==7jZlH%@uCxMMHTU~F)vch*ujq%ym-e{$JDdPxlk_5TP(LER}|4I z&0TT%W=^X@ucW>}x`?r4ppKg3sGJ1{vz-yaC8R2zB?mX28R894vL=*hlheCJE(>>& zH!pohA^BW=5!T+M5x&!k8*!jYJLVPTQCZehxDHZ5bvyFnG@rJnf{o&U5k;+3WhE4+ z@NM$j$5Oo(7e_MB5$r3$uBudY2;_7Wdxz+uQKLMk3&<5?&i#2bl$$oZ^SW&L`eauU z0wt8W?~;QI>hR-W1N+w;&)tgFY^z8fddMV;dyXl}gWXNw&OXdoOf@&t@cM@Y3rHss z@8E9-kmH|-7;A%rt)j~0TU3#BM5eI~8WVJ~h4}<)Pf?to9CQ4R6bx{LP=W`20_Q(g zj7LLP>^Sh@8Nov1Z8U)m;nKpFV50V-PCas`zzFiaGfI8RHM%7-%QKfC@d+_`))e-U zg0(N3KexSUsT(;h@koyN;$v}h#pP2Gf1uyl$$loQ7%Ntd@jkbyNWwZ?d2C{{PKsIg zYzK*_w}UQrTKBM5UlIOuqDr8_i$JCSJ6>USuRy+JD2WHRl1;t2D@`aZ3z9?ROP}a% zr#d_LwZA@lW6a6S==50u_mzfdDS>l zYY2O^-QJdSEY>S5%q}MYnQ!*93j$7yHVNeo7OdnNBya1KZz|Gv5wSX1SSXLTt$|Y} zhbo(8KiSSzPOK&vouNiwXvxycsB(Kq4-hLcU^}_!L`FNHI?o!bHk5LG0Kw|PFednT zoK_GlA@Iq<8I7yO`i-!A@{>xoIABN9VfvFQ@d^*}14A=_(d}s-SNzQf_Ae z8#h!#lf)AU;Eb^0Lb3eVw({~JwncS8ck}v9JN36h`ZSFFhR%k*KvdS)|RAOvrt0>}j zKe04ZH!tK&yI_vDm<`Bz$$EUqY?x!`34+?Y%u%k08~XO(ep>a^4KYs+{KOtpBRSjU zMiY8bCfCJr6jnw2r3N37u-Hjt*HOgGN+&A`F!B{`%PagGSmdsJ{HF7p@VZ(&@mV-A z1BrN@=s^v;vc8>sjrbQ{+6UPM2o2tw1EA5Eh02S>rxh`W(qt1cxKXB7C`x9C{B4B~ zD^4iiI-rW5LpbM3@b}7Y0U@a8vFb(69r*nB(?tZMYt#b}+Cyl21_$#@wKVO)ZH=Jn zK4OSJkz8|k|KG!Y=(yjwc=C;F93Y}DS!xELR8DG#xG;bKcKWX@f>UjjCLYHnX5 z`kq&Hw~p3c9-67IN)|tfPqFXV4?ZLOIT;{C=3YyCJ!g#z4g^H_=Y;t`2cG|$CwnOm z+rJe-_s!(S7weg_UZM#z%9=sXbLKN5+PfUEK- z9FCiAsm`v#PMS|y^)cLL9fmyI)?HN5sDKj2>E>wZCTn`t49tIUxveo<@Xz@he#~J&AtiD4?m6Jp;|(YgXC~)=K>}{F-)_ zlC1oc@1ol*Fw)yg(RRK!%5YG`sw?Cir(G08asHucGx_(n7Kh6B9A^?6)q=L z3@`~Q-1)+zP{~f0f2lGqO~2rPv(`tw0O=XuDxzGf(NxW{(Z?L1$q@P4=^rF_S`wso z1_+uv3+|l1h$k^E&{zOe4ssstBGy)gW&~L@(ds$9pApm`v(_6F1U99n-emHdOxcsF z_lIX`sn*ce?GOqAV>~L17{;C8XLZjwbdy6JQ@RmUxmr?K@$?#aiN0^DIX)b!IjY{Y zU5cm0S)8hDZ7BOBK2tm@SZqz1;?uQGUXI9~xA)5lC2sUy*T~;+7nS{6a?yRLDRj+m;-6(CJdp2fk!b%E{oS->03EbXn1Dz zNUm=`tJ(z>IazCCyo5cIiO1I5d4y16^vls3ARtCO4}xT zv+*Bg55MYEu>Wdo>_q!liN&veKLilJ1o?yCPdx;U(f?Esf2jO_O=A?)oSb5tc-^}9 zy>8uE{xz1ZjTNn{m1ViIjLi}~Y}<~SEpNS$CD{?Y&~~A1hrD5!(#+nRLUo#O0F7I` zHrb1J-C-S6xIT|GBw~bTJ{~>=*iJfOh?+7$%5EFB%@34ZG0m=;(*bXr?bv0Fm!G_29y zm&4%{46wp#yGD0Y5HnW7&K9kAdtB}a^M)?1E6YY2y`_N zICPtF?Da9bu)q@Qw2ndj(xREm zdE!#H4kYT&FVIV#WT8^nT^JAu#mWWNPr|86U8=!Uk&6;(Le0lM#6r(YzPpRGWS!jZH21KuG{-MM7aJ)Ao1e*Qd~uZ0=2@ zbCWCxQ|VKMq>{jpk{6NBqip)>3~s&+VCJA}(mzu7E*?JkO#7`_aqxXAcFtR}%e3kG z9KA~N>vUVT^!wFT0S6Zv`?|?TxNpp#ceS|C>zhdh|T$r6idW^z?1&z?3^m|yg`T6J_)HlTg=%n8o!BLTvF%I>syJ=G! zcR8+|Se}p%GGG);eQP-&( zGiDJI(Ejp=UgHm)*yN#zu=DHG@Ob_E(H#4C)9uF;v9-6cHMVy&H+Fy6L_apn7wy?*}g_6PagPlUg! z$bQJo{(Joh%=FsG{g3GE=SY9mrTvhd{d7?Nufxw3@}C`FwPJs8^!l^o&n5D8W&CH1 zAF{OnUOz@#{b!8-iPV1c{(G6)Z~pmytAF zFUJOc4f1P`{b!I(tbY;YuO0Va1O3{4{uzkz?Y{{0H@)azBmLTx{TV3;=U+tnyB6)Q zp?+;t{tUH@`%j^Mum1hHZTTzoujTfi&~`-s1pT+g_g{g3tx^62P9ps$;NQpm$BN~z zz`yQyegfycCNQt_{mF??=t_mmB=U82=9Ye@WWU;s19v m|Mj)@GlU%L|4)dYFFAQBu-AR<4^cHLkj?8a{2ynTfc_tmOxy?n diff --git a/core/lib/annotation-detector-3.0.2.jar b/core/lib/annotation-detector-3.0.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..bcf3a621227f97a49964937df278ee0924a3611c GIT binary patch literal 14818 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 literal 0 HcmV?d00001 diff --git a/lib-ant/ant-contrib-1.0b3.jar b/lib-ant/ant-contrib-1.0b3.jar new file mode 100644 index 0000000000000000000000000000000000000000..062537661a514c2ce97d18948f4f25f7226cc1a0 GIT binary patch literal 224277 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 z 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%0DDNlc$iKGo<(eUp zZa+y1VO(*5tm<2-sIL5g2e+`U*s$u{6&%wi@0@|2>o2V2*TiwF*jq(&et4`xAoM|s z6{!>pTNR~Yk&|t(Uc;;zepeuSjWId{oj&q{l&ZTXPS|B?IwSgG-9hI%_!-Zuu1@e0 zY&u8jg=6cS*w42S-nApTd7vYeIdOb6&d~)zJzo@8&=JgI^=KE5jaiJ`F7JbO6UOe(ZB;zl)wx)P}g$hs@ zS|hQG#7^A1|Kitd5>sK%RE{VHiu$r>EolAFyeE(bP?yh}41u7R^qHpbaQLYKNn{-( zX=;>uv)Fg9f?~wd*~)T$C&VT(3kCH7sx?wZFeCIG;-T#C*AjXW=TNS!>J^IzQhURc z_%+>szfv&r(JSD8D!=V#iJ0yG_)7VCVanPX+3WoySL9#M%=j(q1wQzo8B%L4&fw(v zw||!HacZDs=FA<_?RT z*W*do9xvWk$X(QBE9P2DJwPhT%5~&rsa8@AXk$u$sbPeHNT0zSd%8pUnRCOiNcRlr zgiqqD2x5Fm!gVdiDxxm|iaQ;nXCL!1cEjOIsi=Of}X2VS_?kaQ6T^vc29 z^SJUsQ$DPD5ow=VGqpD^xZ+0HBeh(hnGbc@uzy!J8MaolTw{R=YZ$dLpQKqRz<#I0 zN=b3lDz%0SBUuG+e18vse_OnCYuFR-|eMsBOSv-Bm3hQnrE&5oO9 znc@X_E!Ha{TWR>gptnXWG&rM5&>nYge0GO~7;gJSr9KKG8$8g6!TlMeosCUQc$gX*R`7Ux zyn*N=IE|1QRQ#jtx}*{r!ZM8J&)n1M=V}?Il=FI zuEt`?#LwhgsS4#HpxfRSF$0>As~m#Aieo?QuAL%AD!i!<(~&{ETaQNsu9*soehc-} zH&PEJ(8hR?`~)luPC5#Z{K{*u896?@ABgZ8kkcYnEkFj z9a*BMHswh4>gDRWvMb|7cm};N#6f~(n6wKn7#ceZ@wR0F7kg> z-g5RfKj*2Vo1&4UqtU-Inu?TW{;Ac2X)QHTA;C*?@xhB46NTl3_=VsJ3jqkn{WcUp z)&42f=Y06#+-HFpFr}3cZAcZ6V22Y(zvPEmH|~h!z=tjGwlmhAyPvz#Y<^axv#x)+ z(<6wGa0r(2X{8IHYl}%N&_v-1#XP7gJp>~NP=x)Q>+9@3s^uL~Q_mw`@}jyMNq(vz^gmfyII6>8r+&^NYGBWcDjQ(ZXr@;O}ViaXaA zHA!xJ6tzErp^{Yds!KemJ{1`jtFhN+i#`%B<|wMVZA7Pr9btqXL59RM4x{sUvnI-~ zF}^c}mL_vCAm19jhn!(oXPr8_rB}jXE43CE8^BmJ56zVjWHDFTT%->rfN7zx-1~j% z9?-!tOYhGKJ-1M|L?3PpV=%udTorB?9QWPDAH3}oa+Bm}>$X=|Km<`^dpmn@)s3D% zG$?nG5qq~zcTTRLq6La@Z98GReiVEYqBk4yM8^?C9V0LN2}qhG*c`3mb%^mg|H4{J=dHoLM(?&uLBczr9^UAQ zIGq>AwFN|r`YZSol~29X4vQoNAzN_@3NMc*N0~1+u8<^yHSZN%MIXy=K#dOKe2GEL zo>@dtTR;-P0t`SOPRi+Xu(+9} zNMEG$X;n54=JM-RhCsq$l|+Fdb3@hxJ`4DeSRIh0QovuqKoDI;TvHgL50y8iAS6O zRgqhipSKIpyNf2uy1^Cfr@Uq~g|U&PpJc|N&;a|4zQMbXmW;DpIJb96d5^vm<)`u7 z2M)Xyd*~9po5G<8+Oc(m2M=G#PzZFcwQ<03=EiuF86j=<8bNuAc5KE0LFS?wzSM+n zXNBj1gD5l1+Uh6*T?U$i!I4#%_~u-Kc+9IRyo6^z{3|1<@XeI;noruA^nC^w3k zFY~*G+3%ctg8isojqWx1cGcB>OcNWRmlqit#H8dXCp9mv2(2>M8kAmi+SD)2HZg&A z8KEM3=Wo>1;2%ivh-Pe^sJ)qqoI}!kU$H-_yfd4bk((0K)u!>Bu5~=7hF0`uezDoj zd}OU|KR-6RwWHI*E{WFT$UZs*s4$>OkSSW|De8uxcT$bVxce=}G(R?uuv$TG70)aN zRFk&{Q#{IeKqz$pXa=LMg$>Y|J5K!9-Y7Keofxrdoz-x;T+oy*PLZRn?5L?m?4)TO zK7ovwBprw?|EWdDRciMd{E7Sc76HVwpOI&vKgHB{LrEFGpZzd2`&6WW+yWPhSqf2D zOCY-zhI*e-9iqNQ$WoO-l0z~%T6t2AR`|z#=KYUD{NSbxU21 zRirJkgpt37bv!Po=1Hv+miheV8*gQ`(8xhDnLh%7q01C)>06Oc*VouIH=Wu$$`Y~m zx2Zy*QgGL)5ZO?Y*igfuZ2h3L`0!)l1wO*@0Cp;oMj&MjJX787kGpKTpO3rLeQusR zt^wHmhsZ(ga)Jpq>59M(V;9&W$Mm!Y?4(LvMqt(`5NmCIf6}CEd6R* zOxp%Tfklih7be=F)CZo*VKSPNDvdOuRJJCMtt`^-Gxxfww<4&TTTIZwHMHCjy7(3!{o02Fwu+Ip3Rcs|?49qq>vmJ@+TLe_} zCXy?Gg|fHj`F8o>LgKP4RG^00*F_KWXyhq158<}Z3s>ujqE#%ZIh7sOdE0x4Y(k8p z9XBI29WMqJIG+C0M7Lsxr!L$=A1fsu?yLLl>BuCo5piS-rrj1RXU^Yp$w(HWId+oo z>CD@Kay0@jr;LJQUr@v$y6S|4fuOMX8O}Hn*mkmNj$e+aTCUzFBOi8MuGy2N#-ijc zN!7G^Q*;68wD$Y)? z$}HMYyG!g{{We*j!;RDEFa!Jj=dRggSxQu;d#{x=Me~8#L*fx{U=749?mGQ5jy8L1 zCxoZvKD*iDaa~Vr1#9KjJ#CNH8zlIp2%HdqBT9kK8T%yFQV4t#@>P-ia=;4P=EOWx z1z=i(@)K6b9L2B2bB2@c3Lo``K;Q*j-ZSzi2JNAMK7&%$24+eQ^ z#akdz)+;bCc?r`$kyx{t8gZ*GaY`)*;ryPacAqENpICv0dT&DGv#|Kj^^} z$y_Fk>e^t1MQFUBBJ?=XQOitVLTiMotR+utM88rNQu@@!*CUP8dOS$sRt||19lyn2 zwN8=UfjSctT349YDaDTY_w19n8u<>9AXBEFGkgjY?B{VjZSDA9LLpJdsL%$J>cKuyNEN}lEs+YgIkIIhtC{W zee%Y)3c7M?Xv+))QeS`e_m6ej2x;6Nt0bPG^9RG6+&i`N|{db%8 zTNQZ)pA``!5K zeX8dhC>P3Sz7C5W#tnyMl%y6aIXHqNA6+?<0j4>+HNLe_aHKSNf&)5|a?Ia7%7T1& z@6@z6aQd|ZQ;UUy!pR0_yft4Z0ke4VP8mmB%Hky`x~jF9C};)QNqgYjT!7Q&bQPnr zvy8m@Lt&r>xT!0djQ%ZLc;b>GwJ2U0aWGnXjU-0sQoRY2(;$jdwKYH&s`&xsvJYZ{ zg?ob{+a4!=TUo*_xnhsie=6ymTZX}gs-ODhcd`n&hW)tpG~(i;E@Mq9#>Nu^OzhfZ-Jf(*-0h8eo*GSQy3t#QVE63WHx0G1hTflQ>|>u2r)D(h(O8?hwW2Gln`!Z$sTLYbtGCsQvhOw6$v4{E ztSU{X%N0p*O~FcmXMb4plIt2EwYTMWzjG-+dk2wa76CQXjB6|7G9`tIuaNHMhuK=7 zdD5;t3h$&o>V!Z%( zlbcq(0aN5+fo}KPfLvNoe3uWQ$%rQLJW}Rl_vrw=*QN~ZwH9DF^iM{qW6an=k`(2z zD>OsB4n=}%1ZUZscWT`!6x|I9?U|2Dm0hJOoKnn)zmAd2l{I@T^h&y_&o)Zvl|Qz? zZ1!_vZnM~8^)TJwZliHEJe4!vcx*D*V&c`dR8vzLna}p(-Q@Z4mK$EZ$RJJF`fD)U zAY4IaSC8}a+b{WoT7;~0kA+9h#fm9VNjH zyjn&}zMVlVWjnLrmLKC~J?2O_E5A?KcV1Nj&RA7cwfY@J)plWQU~JZ#e)l1n+)O0w zGvE13;V;(A?{_+&5@Ua4Bb6y}a{ku>nwXT*{JqjG;ZK9!eqD1lJ)e732au9NYtN-r zTtr9@g>!i48zOU7-YxT2w;?8pauFOwAH##aRNvC&!RHuuP66?~)!E-*W*MSWH`wO9 zqKDlzaYr4|h_Ti}hriB_@Y~0j0CBK;`J?>>U&KRQ##^A7XNWkYCe?g4df>%p1Uh27 zTcGdNi4Vasi$+E^kxzqCAu0#-xFnVGVz>$3@Qo6c7P$5unQzf=PT*5`EMt*ScBKhi zGj~PpCc${4x09;R#~+F*s}pU)|!n#Auzq1EXp&nF7q(lsIGIy$cJ5poUKf?2Cq8sFx zx+|AgyCZo0n&1Z~|CNui6 z{Vh>9h2sa2{ORJbPfat1P=_x(n0CHo>*L%kaa4L8Uc7(D%P81rGTx!=hp+PlRIQyt z|28p5>NHi69LAl@<-t-WO^`Rb=kc=Hw;6U8af7D)7EQOyUS#I(3C3z2du66z;hmv6 z@tO`u)hdBKL}YH8)W8x==nSv22iYhJOiY}kR9q!eb(57j@cmIsZZHA%I3|ZMnJHN4 zaV_+u*ZNx414*3Z6CSCFaP%}SwoxCfV?F#|(swp-KAKl|)$i~wheRL3Ul%8P!plP8 zGZ>jgstJeWl;c#4xy+k-SAe)o2*(v+=O?XLeYlYZ;Ao{CXeqZ`!D$Wml{;uROhTcs z-LC`#PCDayIu_&wWlsc?KVw^VBYbZbLY!Aq1}C=eu(H_~ra423m%981FAvbohD1!O zsZq3c`BG93{3G1?HUtrpSX zN0()V>9Hkc;dC9MHt`T>Z&%(oEKLf|zJew=<%u zs*EE^kXW7`?-uDlU6`|XG8A459Tz{)wbJ>R&<7;&#e;<44vZ!e{TjALdImfFPnP=c zcf7rlpSNo4kH7i9z4-sz>-ksjD8cI=y`$lrj-;l=aa$lyVPN5ASPJMtG##Z-bi{rs zdZHU$)^@uw)0AexPfBk{X+z@w3cz#NVElt@ki6w^-FCwD?2^;m^ZoGx@&~hma(-6% zf&aQF4?kEOMV7v(zqipJ10jOZSouLfm7Z*0kzsPemGq!35)C@u3ReR{r;2E4Q6*@Fs@q_r%8ie%b7q>AZ>bWQL)r$dX>j;9>(>l` zFFsm47l1WR8uUm66huhz!~so89h3F@xg(w-%f%ra15LxtTr!l4cIeNo4(PKDS>=kq z4kJ&7orRCd3-%Sum}n{wIUT2orc3m`{4lJsbFsoA!~Vexk)vvhwSjt|B4I|I`sioH zx@Jq}{(dxOu2HO*Dng$xK50_KCiX?+e#(y+BHvr)?MeyUUDxhq4zj`B5wu!zaC$?U zyLW>11NY*i8^>Q?C^LpBA1oQt6Wa!i(f$bqj!06CS?3R6k_0(IC# z6mqzN9Z8c25bUA@*>67));#fF@hu~Whoo%l|B#9yG53V*ouU^EyyA@2;M^+3YK0qh z#>ADnvDf;Q19e|%R?E+H@ad6Pp5Ye}5{uN@;VZC;QJ@+!;|iUDuH|KKQSsFyjH2Vt zXV2P?@yaVxgG;XGF6ql^gp{p}W<6=lb#E%bfni&i>C!LH`e-t7!Dk{P};CyRwxk zvJrZBw)U#J@rW>LUcVeOvRW(&5fOugltUaE3_xEaDEc4Uv@K)MKxP(?g|mmJD&5@{ znXCkKH<=Q;$2{IS-m+>FXP2j-rGXHy?W^~;tMl&c_w3L2ogHtm*?@OS*gs-@>?i=x zOSvEekRnP-CaeXNU^XnMeQsKEH_hST!$hs25bRRpdJ^ljV@r)%Q{=6c`M835okS;8 zdRj^M9hBPvxWkp5$1NT8goVrS%Ueg808XD{KCSC0f&v4r^~T zqP~MHMrKW;H(Ovv_g+{@%cD&u z#))Yhw=$fhn?>|tuD6un7hGvDZ{7>L4X$%Go8-@$Eh6d5Zfj8n?H}S0f^@?8eM!jY z$gumUL!KfV3~ji$$`_+BpR9lgDd1<`BCI=Ow@K(AhOQ4wxKEMWmUdfb&^A=g92pfO zDc%h}`3G}VT%rl~QNq(}m#Y$6#h#SVc*~57gF7Nu+wLKnmcm)SmVUNK(f+F=L@l%-D6xO)`l+kDE$e%V)9E`jw?zRDVelPDY z2vep;W0kOF)xLJY7rAC2!$3nLFzy5fYK7DC)w84t|L1;d4v%=%F`MLgGVC2&ErTMd zJF@;v=Q`jHfQ?_ z0h;Jsh%IaY8mK;P|AxTpvA&$Wsb3~ZH+!xpf{Vzr`Oq+(0*yTJ?n{E^lkO+6hZ3ZH zv_lbW=>t-(9=RM}P>DI7t+6g_YC@&!WP>rnK@%)TDKpBQAN4s?e6$&I>ZIi^9DSS# z!s>w^)B@h9BdIp17W5$~Byr`_G^GF^eq!te6~eCacywn6kMb?l+c8Rt-P5rlEf6$t z?DLOk=|xJ~nyV^G{62eKI^)*6WW63TV zC5<}x03_RUS)B+XZVx&E5g84A7Hc@w>K#*m1wPtzoi` zT7w>D(7VaO&rY7cf02zH2W>S+a5k6Zdn<$6ka-s4hC3O1N+z!=cKj9S%*oBw=4+{A zD>b_v1^x2wh|Vo08lwdpPH~pFQ|8srPApeoPeAn7HoN&cPjMvtzUnW>qlV}F9$cz` zLgM@CRqHanCR&!-Xd**cIN?W=v@z2AQi_j)QD$Jg-g_Ij+=-Y~pCm|{)SEU1Mc*P> z<1dE=gRZKCMdG@8g_k}+MuStES5mx6(VuKc6iyBY_ph!NEYDxtP;6X(ac%wmbY zI+)zsRLy{j)$FiJFg?8vRmiJRw;Aq;?Z#H5=56^0s{+({(HhMIGS-E|dAb@AolV<; zqo=$Y=R{S*I!8;TopPYcnF_|pVJls{MEtf8gX4G9tn;MT-Q6NMbY^lLfq+fla}^oj zz?#+yDyCc8MLD;#!MqqW>O!8k%4UK4e2KhS9=zkYV6zxHUZA+o2!w)(G+r40Xzv3Cm7tXsBq zt13}xo0Yb0+y2tF?aWHswr$(CZQJ(Fb>`grJnMhoH>V=)Fg4@y7NY#$KjE z&li}CeLQ64>oY^J?G2Dy=U&P0svB>g#%}2T8*1$p%;9T;`tlq0N*g6n>-rexlZC#9 zE^L zJ9KT0atCY^)C|{9@8TuifGEr5?FW zv{=6Wp+*4KjfO_QvxCxib|Cw|W(NyvV^e(^J}UABXFEV|XL`%{fk*R`EwfAwomSFz{m7COE!!+!q$ z3U!;TB}~CcmL@*h`VT3V+S23-kytwOyJK82D( zeZeq$n1RlkO}YBdbljLVc)!FYGto#ql}H8iW9jHxv=WEpS?cU&Z05jJ3_Z>{ptJiq z7 z7Qj&B;4P<~co(1*bXn^(Snx|=lpOe0eH%)V4{^6>YaKue7rztFp2f<+@8K6k@GgEC z+kG9S?|?`BN%g0bHJ^=h{}rIo$uZ3P+O}4D$gt{p0K#K8>e#fCa@skd2;(Hn)VJc` z5iEz@$6B@bOBk4VtETvzwQ^5C95R2{rBE0=_E~L+p!{sP8yTA|90Dq9__wBIXrIOD zaxV)zPTqGN1$!$ucaMW};f5>7ZLke@S-iG*x3v4f*#w0A^Mo*lO6b(AZoc~bLb(E1 zSo#c{T!zb{CECs>#46yNS!TTiI2R?+s5VUHDe!nJ<}>uHBwCawy@X_5Jdz!ZKcQQ; z(!8^$$&ph%QwV!O1{Gkf_!LFsaFP#W9>o`^!R&d^a8!{zH7nt9G>YEn7FXX01*pbbo!0JyLyalg6b7y{R6SDuqO;oE;WfS(qCiFuyQ)G)R1*+8#(KgEE+y?izQ4$D0jz8tAhoK$}5v3bx`ERbw| zv;tZ$<4!8LLOK@&V!`$XF!A@)EwXF|=|`5A+9twtjDz5gp1mbe6_>OH*cokVX1Z$z z@p^;Q@1c8GQtd1Yf=-UF&>JlwPep}kXl@}fkE)?l_j`2=G+!Ti(9Vg^KdfX|)$iGo ziOh~fd1SJ!_ApRv5S@c(+W>bnu7|Q*{<$rT4lVMY#Jh&;s^v$oTihAeSHQB zgb>Lp=$dY+pSH8((_7hBOYnV=x?(*R@-?f&^|EbZ2OFnL*28xyn zh$?7YEa0lkl(|UtC}!{##F#dg11fMraORlBy!o@}#_g0z)wSpJZJ0riS>MNm76!}Y z5^SE=oUa`30=LW9XqbrAk^Pn@SDvT!qf70t_Zy6E!}1igeibgX?jI*RmBU_Ph}HV6o_rV?ViR2j&{cAC7_DNQ0P9Z{fOfRPJ?)9iopsk!%Q}H9NiNI<3xh>s}Dklw&HM{iq64 z3aQayQG2&UA;`BkvXs|~d0i*2GZeLW;B~S!wA5}xW-YMtUd3?Vw8M0qNL`DP_I1;X zfseil!8t5q96%HVHibf=t7yMp>ChA(=bwXnwvW;&23-J0c7kh^{$k_%H|H^4U52sr z4^in^!2Ov91KnH@44{l8bIYIt!>X+NO;reE?So6@f>;Z!AG8zx7;o_)-rlEY*BgB4 zTCl%8p&F;fY;(@wz7fVvzy+GnZN_$%)fk^7K&lz*`7`Pgq zp?dPBguZ|T&b>r;ueEW<0y9Dcjy-%XX?>L#j@m0)5EL*LzEWM|=$HiNGdTEjm^q8jGvN>36 zEXsX42X0t64S|M+1~FN05c({&yB<}kwn%8UK34+W!gPEnEZm_%G)Mwiz^;iy?I&VNGZXBu5W(E)E~!UvT?e z;^t275q#P;4M{F>-?K=>kiM2GNyCrqB)&B=@xkf-rrt zh6+)dJ`;vYQwG_p_vL}L-k1Vg0^}sieLnIsy>Dq$r14jorI9!f7^(Vk8``<2f#$;hI6E$ax_DLFKmPV4PGEG;e6P$#S) zGHguqrLfTAxJ&L3;pwTB+wz1XlQtxq-g?iBW2&=u76Q5;O(ajyVwd1VO{9=@gb7IE zs)1ue9LjPB{i(+JqlvKuI%0wLj!P^A0b>PwkuR~FWZ>#*>CXtp*Hnwu^>HasOH<1> zHuA@X@=X{O>#NnF(nH7^hR2-24!w>q3JI3%dd=B;^qUK|@JWmYj>*F( zZ~*;g2Me^3Sf$~D(ZXfLNHY7x#Q+MuMAW(r09Ac*6Pcv>1VW4Mfw#YIvoA#=C;C`u zGO4`+O6DMzdU`cQlp}OvB(yAZ|5rsII|Qj&wIl1Y0ouou{He&+j%qP` zwSIIRy2BQG6gQaYGL?E1r6{hR8qND#U; zeyF-*xLbtWkmLF_x-~*>FYEDC2;PlC1j)YoX4M?2?A&pOXNPL|JxuIHP(MuX)XY1A z6a{vSrl7Y<;C5K4P?cILhnmy$YiQ>+qv@8w?>-8LNtnwWtUh~<0z@Y6zWk{3+n9|M zJo-SV>%s16n9X;1mHCpV>Aq<6)joBoscAM|>yFUVcHj4pBOi=8lcF0S69=G+XE3hh zKU_t}TJi=Ng`Qku$ zZZ^O5?}dx1$QgWx&`7j|zaaN-1VVjdRFEh7|52o%DGCZq`}yNX&$qboe;0B7bqx`= zwiD9THzB0^S2hcfTa(75gX7xtx2UCr7UXSiIEek7s*I);U7CCQmzO`3mps_nm@Y)O zrc9IO7v3`oAvFT*#}94@$M%T7NeB|$ndnhd>F7LMJfA`*v;9+k;NFZ3 z8ROP*AEJ#K<68k@GHE57BxiSan}SSR#qRJ;!jC zio}SXngfvih+xsingBnGUpQhiv&(rYx+tp5M&J^EqnZ6dee3Fbz?IYy^nh@7Sj#OX zA;G28#SvoTnR#b1VtGwX8h1aBCemgADKXebvVf}rt(DsffB{2en&oC|EWh^-wl#_qsZg{hSltqcFQ35gxc;KbOL` zwwtWpE0G{?+!O9#-8R2muRE6_x+YY>b{gA_?qdJzj|!<+3 z=tXLb-3=<$-(S5l`7^2vI%7e^QOH&I69+=o%krhgI$}YIBE(bUDdlF%4N77S#i?a$ zHNHVI)OKS*NH#eCYAFClk{YE?k9%yjn2x-Ne zHUCCU`03h9*j2BEJA$?f6lxL8B7$8&_4~3np<;*l=x7kZriC23WbHl)_9B(Hu_2wn zJ~CQI_A}pjWGd*wq;ZY-w@|tJq^|NTMuy6!2g?sQy3$fj-WxXEaG#Lqp{DftkJaoJ z?%*(_f(iGQDGl;^Wa$`rSvuSjrM$DHt@ScGQKCyFceJod59}CX%ULO3NR#8TfG0R; zO){|r#Q7Of>IgtoC}!B-J{qu=haONk=OW&guY@HTF9o!ubxcpH_elJx~_zI3QFypDjz zIJ7=~JF3+)zwweQ%f8NoD|Z%U}BUnJ+d|um^F0wgamD5cKEyIkdP-dP}QfnD?y}B#m-gJwK z0*oIP=1^^L(43`{og6JkQT*!I$u{X;IaP+jFXh-1l=!HMfpX3~No=;At76N!J5HZX zt+&5{_ML@9q%2lbge2lgcNX;lC$yOSJ}w$k^$zswi#B?EPHiBY_dZUtXGp>uq?Mzp z8poUzDP(If7#pKFDBeG>c+xVAEg19Y>PVtxr|utRBT};&(Wj~^ID#B3@|zr%(Vcht z(`tKYi8?IDH9;aknhqVUjPYv)n$UrKY^;aZQzZrC(kDlx^d0g)d5ar@9Em&0Q0mNJ zDYTLo%O$Prlo{MRIPgaRQ6-~zl^M-Rq?tZvHpAUNd_f<5W5^>wA5SRr4d8Tv;Qd{z zZeG@Tk(Mxm$;8Olyl54s(irVeLr9=hDbCjIYVeXo%ka(&{km0XOAZ3K%D|`q#6MyT z^YGFP>eQ*3_WNdyh7H>@JOo!$(r@G8Uimi#mPq_^YJTN3W1~aw=bH#L7GJB|1}~;G z52DpdQ5RwFt|(q6UId;;OZU9Qk=P4VMrVM+l$Gpe^QE=ni+$ z17mXhmh@f@pX2`QbU?UGI|Y$w!8&zTAw_<|4Hua zW4%KLw55ca!6cW+aLsj)!Xd`2lfqa>6&SVBBpat3_XL8u>ciVkfWR<>f&6_GBZFfO zLVUzuYlFI7!5$uHh-(~2yf0I(xQx7Re4A4hhWz+#nv9x7VBShGQeR+UXnyBdMjpv9 z`9mhZ%<1vgm`U()Bf-_}Vx_{uz#C-^2%%fG0@yL9c_+8rO&h4bb9>Rf86^LpQD6@-)vW{y+-1|Nc;o*-t{ib*Ahns_H_4*Q^#U@cK?9=m zV2fuLN#=$Ui|&&5>>>3izH@bVr;dbUMt3fBa@AYqp8g{2w!hs~Jq)Xz$m3m-S zy@4*ys~mJTl+Y@}U5V(Lk@>oD6rQ`ml|=-+yFr^p1f9D9fpLV`+eb84ZB(PM6Bm~G zpVizrt*MnGxp5L5{SVT7t;jr4FasA{?wY5>u2GVr zHw^QbkK;f%MEK;B-Ji!X*#}_MleeXnzEV1GNW^W4Aa2T3 z_Gx?|s6^U4qB4Ht9m#R(<6WbREV>ofL)W&6eUp}(?o83C_d7Xu@S7?<5pa~sD;zvq z!(fE6NFIw^tl)`0C=Z?)H48AZc3iV^=!_8eaBkVirXb$Yw^~UT?Cdtj7((kA8a2;# zC)2T(=8J5+7T%FiI3og7*9;{OF&R?wW{y~WgoM!8?%W7a zGZ|gNNp5J4&!E;$@y_nRWh)h>pwyCCX&oyVZ$^eDgGa?x`1tc{EM+}-d)eewLiP(WfWC4(kjEp7fnXVPj;FMR>`Z9EYCas^65J3 znMzuMZZlrKohacsKyrx25N9)yy`6yKx>Cbed(7yzevhRVvn!74x#ND@(~GFruKP`n zuZ0}lgs&BV`u_aufV5YiHHwd8JlZuH=Hm!T7Y)q(iWA9O(>Pt;5|-?yPn_i`xjfoR(sZn?k-0jTLJ zcri^+l&{XoYq6kHc0FWUhlMH5NJIU`>(f%!ZKNW{DpS)+3!Rp7HG=mi=)i54a!}=j z8vs%NElT$+dof_>R-bK1KLN2U_s1eP=2^@;U|_GN5TUH~$D$VI*~~kj@&+&$azODH zxwZyptm>`y22dBmRP;l6cJbrp9dLOAfDCCU{-HcNh%pg$fF89VSWXnD!!-#u>8Hev zV@_0~jNCnoBV5xtA{jks)=(p!EFzv#ZxkM69w4%K)SY0kBVPVTw%K~8gwsZtyP3IH zQ~AfG)geKEJLps)N>n7rsW{F;fT^USe#N5X8)?t? zte?W1rM2mWev3Lf8jehA29Y5$Ls%i${cR;<^`@f~jk$_7v9`}H{e`b6z(ZF@hfiH3 zQvJGx<#cXJFEjRvv}PrN`Givg<(hw_aGS`pL*53#`Lv~Z!*-3hQ|+rw^DYvUxmQ49 z8j_RIA&0^2#VbP@w{;A=VIcWXSR-Xk(yG$^iLe*@=*DiPQOM>xFBuz}*86NevHT9T;)gv+lisN4-u-oMN#4T}>r@Of zI(&uH<8lSO6=4=s|9yr3`CYD|`_EVT|A$^8s%tOy-D|AtVEylYP+-Up9|_jJfjyW@X`abm*z3H0LBVRfu$7d9nmd=x%OA>U!odVk zU^Vx>cGi@-oM~5PUI*v91$r+>oy3H{?Q05#^AY?R86Jl>Tvu}8<$L)dr`AJkFC_9h z5 zKP|$E)x`z=5j+kW`3E1#Q9_8-^85AkzN=Nt|ASBPzu*6VKmT3ch#3>_<3$Sm5}V7H z(ywS})S3wh)9OZQ3Qgm$k#)#%;EB-3jsPcA&=wnr@WpSw{^>z_Ll}W91P9KRl*O~f z7U$~j?FuqIm6Sx)Fwbcbqq zwGJHo%XbA#;;Y+8<7rFVi4}UC49g`y`q6!``8SCEQbA6|m|l<66W?tP`aJV0ta|T; z6k&h)@{ONurlimF0JIb0VEV>4#v#Ls7cn<%A`48dNzMA*3+9XzEvWK%)a5q^k;Q;A zCR_P3txhVy(B#XRJcB6Z*w%X=3uY>y3{A~j?$Pxggw*4V{^7Z*@{6wz{ zCH{nvE83Eqzb$Jw9AMxKKA4FnA0B9ja6~83BB>MziVU7u&~5?1{qM?&Fryk%>30CX zz61C_`G=)+ZT=BEdCmXondfp=Z*_340aWjj6nn%Y0JI|plZYb-m2er`WtQN72cg$; z960|J2jjj5dY0?wknpRZldD^Cze;I&T4!)RygeNP`Fms{QoLh@tpEtNg?-ZJ?Cyh$ z5}*tO;43~3{LGozLJY#kW-pj)+TA9~c@#%K&_YbT*m_)a23x57AoNV3zumC${&N?l zm+e}x(v&jIfv8A5YZ*S?riK)-KQYl6Lruk9)Z09ckyOT4 zdQmw#n`wBvdv*(8I!jY#k*s)pP42HrKlZ~uOgSf`!AHNbc$o)=&m>M3=IXBl9s_{a zr0T%j;Vpt;e&qzvsxvS3o)=#IcBi-h;=L9|>H^yZS2TSCpOS>ixZ0CF&cU7U?Kf>x zHoq(`-?^LoGR>%dMx_JnF3ADm?ekm<>?9%DVBs01(Vo)5EQ@3?$Qth<_1LB`^gW`G z|G>@@Cn=s|8RR`<$Ao6|p}#Hf$BlC7f649V&AY?yVI%jqKkS}?u1NxBrvi^Vn}^r^ z%TZvt!~8}pqTu4MYK2(qEg$Cf>%Rxy-ovU*;9K2R{8qP_{s${p-q6V6+gALK;47M2 zBZ?q#>8F)=Wm7 z6YocZ?QyVej!G>q=Ar*)ZE4RQauv&}AGFmbnsdHg9)@c(*%RgDwb>KyryAH?SxK8u zL7}Jg>;s1R8F3m~osbnrX^ zP-|`|Lq^PCCv}8vMWXmYYnnHFMkef3QKb)F7R8CIx&3y}uVEC>UFlL53&a)=ZQh~c z?k#^?R7aB15jMjBH=s&HlvBJg2^{W$_#ie7;s{KV1Ktm&O3o znj)!t`&^=9CBJlN3*HD(B~+C;W~l(@d?3p}>Ar6h`vUL6Gc7`UmNzV9vlXn1pYGKn z6i4&P4vLimvxtBZ3l%#+@0y1m4{8ARq8Wb`^EktLVwcVRDi+$*t)?g#mZ3EUPTV;3 z2c#c`m)G6Fg-D}^wSmlMN5CI7Ds%)DQ_RZuVR;_g1FCD?jdsjT#@4Q0+Dy!WgeIij zX&w-RTg_duHVBmm&(;9rMtJ+HkJrt1)VLPQv&B~KYaa{WIs3$)wts|i7vMX*>C~w^ zGG~NR3m<<>;NbhPJ|sb!>jdl~_CpL%@16+0qdPg!JpxZ!lTvXCon9(5J19sCrYzpk%Ynin!HwE6m668w7v{A_C zMi-Jo1)Mh?8C1pSe(9uttl4w&HQFLrV5@sLZLIqCt zU|@7H9f?5{Rv3Z{Q8mEDa9f?u`J&TWe@_&16tkGE21$?fK4S$UNg|S@{(64EqUBzG zMO?j=e32`wBTnQR=Q=ap%hb4ubHh1COBVNr`w{o2`{vQQb(Z_yiSdu}L<2AZ7rfxA z1cJya^Sg*2pG5wqE_^(nN@=JKFD#awB@<2cFV2=)5Zh}%-$lR}kasy&y;ah#KyExQ zv|)5Xz2LgN18HHajze(G?g%Ws$|q#%U77x71ul1?^y0rKMSiM_@XXCj0X++WJ>bxF z&Ci^zzRSQp`CoTEcm41xpy*nJ+yu)3Mzgz)mGwhV6 z0TZRvCm4!I!Me@eX6aL~gu+y;WXB1!Vse4%ASScQMEYzzl4l&&v#Mw5jtj zRKZ;|Q4?FJPXZ#Wj;~9kzGp;kSOAQSeQ4uUW2Tp!QMzndSgUP#0~m!@Y{F%ZN{wkw zK?`zczaxD4ustY)StDl51Pz)`Kt2e{$+U4v4ar8QPu6 zXnIoZbEAq%V1Ir6;Wi^PbOaC~VzZ#E3g1Z1%ZydyBhtYXRBQ}z=vIR%{= zA`t9>pE((;NJ_Z|HnACr+^gg1`6p)p(e3P=-j=Rq*qR6bB06FE4LMLEZ&VHiu9B&{7E@L5<`;P0p!S#;**CA-BqRDzXLE*9}c1b8fbIu9lysZ7C%Algt* zl-~J!N{jKp4SZ*hZr!k#ilg`ffq6RSF_#H>g*a|bO6bXj8*WHHkk7jP7D1uc_H?}u z9BW%Do0K8_sG7M6%_CSb8v%;b?Z+5l1SGP9){N*Z+op=&NECNy8^Hs5*}DOH*@F$b z8Q>1xj@f0t?H_qLcj&b)ks{TDI_%YXVj=VNWPYqRFRTicd_(KWXc_Q!VZch_>JPm_ zsa~&po{1dEwlkld5h>Ewq3mMr(tx)#+%P&RR;h(V?XUv`>Ln7wUCEgrzgEvY#LJ@P z5%R-$n0ZAS&Xnnz_S0W+n*bE2N5n);;Z7N-D`1P+AP7G_wh6?bsVCiWgJ?=79}cK1 z$Qh$nWEJ`yo*OP2Mu5FQv$|wRIw)wCjD*t!r=iyfKoLS~SVUdsIU3>Tsk~g%mx-=Q zuEN39J={)Z&z^vT6N!8DvXjqcL6ot#Ei2EF$o{h-*a3uFhu&xmpAU?OB1TDmJ2i zz^~OUtbHR|{Qx@<3qhg!wF1;iz|D3xnp<~0U7OLVY-7VBn5Yo%!)$-3!IfX^CsE3k zG^3U7fzE!QvjFJB_#Fb|u&zKPsUlXCJbx0Y!Kr=e{9jpHd-?&f<*}CffC$~gjp~i5 zN@8=o5}U4(vb?gwZ=^^~dF5-lPhBi+|6mfU9>KR4H}B4OxS;x$+1w|X0y^$qOTi8(EHn?D`Gr!_S z8;)A};f15wTP)_CwWe=Ew*<U&P-)KPEv$P|hv+w>a1)WA1hoflW4m?m}P=fq7|7~QeubdWCj zmt=FW#RmbonkR*WL8hLRf>&6JF?QPq_h3>{3jPh#D+cT2F!tFP8Ceo)wDQi9;9rmk zk{uzTkwR8Y_dgHV%H)PH#iA!jt_iJCr=8a1Ei3{u;$i*Bs9hL9HS?1>x<}1r*+5#Z zTO(M4>tfcSSc8?RrNwrkhDkWwx*X^E2ZMl$fd=ZwvsX4~3W&reGw3xp1t(6foAgwU zkg#;Sme*a?+CB~rS7>Oe9Vexi0=1akOWww0M+t?lV~3%$&#cQpqF{Y6US#olwB2IC zGrT6A(-^Ptn03;ySKNE&P1_r zCDOI6qa`5H1F+?3Rv6QAYIGq}{S(qrj42(pDb~U${Om10%TY{2q@2N8ImP3m9I1mi z>U-R3%>anTHMu}Aev=+ajS!G#h_D)*hvmP>%fno0H7M$=bD>W*O*@*APrIJe=dWJx zIWo)2xrxx-;zd96n|L4PP}PwU*!tA2VrSeYqO!wUeWW>%uyTk{Gc$UMW4wjA$wMGG+P@>Hqf?pn+ncL7};a7M~O#pEtGAL-XWb56E8j^ju8{9y@yP* zIhcvuPn=WW+%7GhwyHV2=ugBc9SOdI$aH1KynqXT$h5{5YIvR`9TQ9~Z4HsxH}ows0A%turf1Gq&V(;m zB6RKRjGNW#t1$bN*+-d1rv<=b(#%DYlZR1*)@s9<rW}rj>4F#Szru3 z!pqC3jY{Ls9+O}qsB3PF2Hsy>&q|%3Ii2>1a{<_VO(%@A910lXUW|mPf#iLn(UQsP zvNEdgVvW&pQ&#r* zSkl~GH;`{D=UmdFr&bx3rEb?&(>Qaqi^ld1qcGms%DMa=aj^Xj-o1C=_SG0Un^^5vIT8p;$h?V$~J&|23dn&z3f>>(VDZS-Xnu(7SYjR`cLiE%uP zL&7zlD~~!@AlXx8@9by|`aD#FiE6iWISPj3V z1GRSYwHpc31cVhlycKGtTJ=QnwEE$1i16ceS%zhh!|fHy3I(ialDWMztvY3nyC+Ou zeVNs`9aRNzn`wl;j_C+~!_Taut0bGL{$sRUEzWET&=i);!h_C1w;4^Mg*#hNZy$Np zZt>eYtc2o>*Tr{)j2lRo!H8Ak62vL&r{@H%2(v4wahzAZE7SXg?ZS44Kfg~zoA&el zQp3}}0g0Y7zdz+;G;k+^!thCzV>G^0p3PA&H9)Q8E1Pa>0*kK%V74S$@o`+7gdS>`ktaGI`%`9?Ex>?YQsCebA9&w z{+^%YO4CRsB4hs9>jr?^yxZ-U$_0CXN*a=n0;5Qhv z?Hcz%`_^}Et?wP}Y1jG*r41|0yb|KJ5^%fqk0Ffc{bD8s zhyOlz22E!088KXhhL}5-_;kdNXB5E2aVsi&Lp!ImpsDSt^fB@N{Bi>MS9d&lc*g

i_&&(8}HnlyDE*1(3?kIxduP*;DsTNFh#orx}#4(JFUFx$)MMroKh7F?XZ3*#FVD856uj<5JVWB;Br{tPmXo;zI)JM74_&&5lSV^i0lD z2sq4RAWu|c#JRbtA4ug*jJ85qlYE)=C_t~XS7$;W3Ee2s2gsi!FQvk7! zymnVssXwK{xRmLJQ$y?0(k_Zk3x)DHsfB11Y7*>tIe@*M`?rtw6&4C#!71TP!u_#H zh;Y0GT4a3&#+Ic&&<0;fDizWTiFG%%jAL>!1-9bG(JBX|0tIIKkHAwUh%x>Wg>^1{ zIMth6CrSW0ji(zhIuj3^Ys0AFW)eP6L6t50mH5F?5wO%yh2$B19LnV0ufG1SAIuzU zA$-m?2_<#US<-6F!DK*5l#+Y&eZ$_cv%R+Iv^E3$ENal-p6rb{2(-bkwnZq|6X{h* zB2VnbA;Sf&KOU+P1u^#TqNNjky5-TXfrq8b@E4T#OF!`s+#b`z&G5HFh*zCA{$PvN ze+UPYtpijMBXjqAwGzf{-me&X!8||NUKMEb>$iu+%X*+OcGtjVtsnRmD-fLDFfck;Oi>=7-m^FPDIrBS3ywl=pmOOlPNFU7EAbu z=D*i+OK<;(8h*DwbbW&q|5N+JzZTQKSCNvs^}pze#upQTjCiOTtJ|}Q>@XL^{wTu( z(ZETd>CF3!h4EwGC@d^S81251`6B`Fer*7LlKOR zIA9y_JSAw0A1Y_-(TFpJN1jWifAd#9HYO;?gFRlP<_tuH>vD4;oL_hRD1 zhuvVQg;C?MbU-aIo_6#Yf7vO1q_{{nVfyO2k$jYT#F|)k(pco`kah&3lD1!!&~lN| zx-ca><>}{RX79w;m2GY~RbP*8i3I8fQUR%lyRH^rxL3YmFj>ZNlzQQlRV=@6UsW7uBqy z9p(#=(umiNY;YP>kW+E@AH+!{!^j;QyCYRZ-tjUsNCm+enZvMkQg>f`+-1^%G-nIG zOroy!Fb7&RwIyFUpP&jo@)(bJ8VwdZ1XYE2K8X3$!VGwgB!P96Ri(@}Fd$MG(`X-gNt z0ZC|Z8A;N~jYou_2Ynq0e$ZkhxpPf@h1Uh;yCS1JptYY-5&P9nm@)qDZ^N#+;NqAIv_{XhNo*RDk%mTR$qGv?? z*VJm)I5_x`Y7Kt{L}w?bLlkv9gBbY%qsb`>q_3xaA#Ky1bxqID%H^uOmDrW zP04wJ5cy?a^iloSi5|5zB=9VFl6fdGXI<1W=E&LYmCnF}KPydUilcXf$xHAhYw zSkzf6x|J#Vl_}c0KtF_+9Qc+9UHb(#I`fBrV8|tye*~N;_C`lmDQ^Ndge-;!Qcnwp z%_TT%1jX_|b!ef*5=1IGbzVWLpS@n$_NI9<7;Gq|%5*FYZk$>{hl;Js6tV+^QJEs& z8TsmQ7YzbQ%Hx=9KA&yt6;+7l+Z(eOvl5VUzc32RuZ(PQAZ>+`&YWy(*#Z@^;49Md ztC=9&;!&PmV5`~RpD2UZh=n%{cF!QxUIS;((AL|`n}nNd&)8Snuo^vCUb(aJzfdE* zatK%jn2rbX;^*mJIYRaQxg(%(2($qC!`uceQS*v-~0Z55{qRV9b}9IbsY=|1*|RpOLY2%&B|#oDd)12>A(!GrU$W>0@<2oZLQ(*eERz{LXz8UfU4 zxEcpt6OeNOD3(_qfr$-&6}bY<>*pJUyWev$wUadLDK{yFicm?JJU0(CYhp1f7CIE9 zx9Cqvyc)YJ85hwj4F=bbgJ42&J-m)BU#TnCF%c3bOIaO1_Mx6aPYqHV`@BpmtoM7r6TyDYBpf4KP)v!P;Up{j8;|`GYBuq18%KB!; z5Q|Et6x~`LrkZL7UT~NqE`4K>?@A-2OOo42m+J6~43BBx#5HGOD2=~|r^9?Uc?NpM zO*J>eouuq9qA9U5G-c5k^kncK?z5k$qbjSNcHT`fP5X#U0Tt&&FabMkEv;ANin(}# zG=W){Z%g&6Q?TYowan$TN^Wgn?@F6 zvn)M&Wz&}gE*Qu|nvdk;EXIiuSP*Jg0zaz!S{v(zz!^UiSkIYpJl!c-JNSnN;xyt( z3;NxJ-wyqsmC672HxV?nFmy0f)U`7<{6AxtR3@A;7m&Xu>b~K``Xn`oewO?O2)v0I zq+8$+Xb9?fzgA!x=^TI2>A#3&8gS-~hs(E_@{9F}ti?CR(*%KFG3)jO+6QL{NSm@1 zr9V21@N|b-w4M8=H$FugjN#_mb~InkKI5OB9eiTpCHiDm4-*ua(P-c_}+Go`NIk%s((pmjV|}k{GOyJ2xe^|)tiLE-YhorAl0hrI6GS(2AxLS zQo;nB0>Ns>Y%I5BE6B;5+SGERgF9v49U;}z`r1>ZA(%sGHOWUF*0St7(p~8EYm1!= zriq(}sXr4Nl|50`^ZbCSN?L5DG19MD;|NIMS^9H+yWsa#5MP)Bsl!d#Pb2n`z4}7W zf&BTGNjlX-`kZX8v*v(&IYzrU`V)@GgB$qRPf0nqk+?t}G38QA$s4>h>7`rbOt5Ms zi%=TpCB6sz{d(VJgu3txi6tuJ5mz^MlCaK`Wl2gIRmblZW4^~!D`-mgI({UiT&!Bj zC_N-5J2r^k$c?rVtH&z57Yc;#|3%n423Zxp2{c-+hp3I$VueF~9<1E`^5&6m4$;0CWx~f}aVD7j=iE;g_Wv2iO zJjt^&)1UM$g_~7J(P4T=)gfC?-6330<)Ma$-f+EZ%pQj-wT!|L<2v9I-QoNVFIs4! z)KVX7-K5k<|6vxIb6H$={PrFK&*3nhd)Mfmes_5>ynW)`Guwvkgs0J&-Z~Fe||qG@(girG6TE+{O3uskB<~^1oXV zw{-gjA%$PGsvs${@Mc)_NG@uO?;6>a4cVxu6@qdhXPFNn87{>hoM&5Tl3O7WY$=$$ z5%4ie&MW>0FhojPat9g<-F0s|D&syX!?`HFr`AGw8>L=RL?>uTZ=;6jF-B3UA6<^# z2u`gj7gI{UXAODq(UN?R-3yfKtNO?_!kl+_zJb}{F* zd!7@#tFhkOdReV-_hPpa?`F5d6Cb8pLB<*mw=U#Fc@H?GeG-+i#Lu=j47zqGb^_qb z#b%#2gra3xNS5hu|iB9&24)_?Rws77!nHS zB~^yWyA&Izv~;+J4_I06kuOHEs#=-2AEyCi&OrT`MC?e-K*prN|3agzMqNfyXu|S# zq(?tfGi;u&={s$1FW+&9UHykdV6ghdf6Iq*Ugzd)T~oD+=}ol0lXR+U9BbDmURJ47 z_pq+-kLre}pKKajm_uN>)jcJh<%|!nJ2?uSCz?tBGb7;s*y!9dwis>EmauRnkb?o7 zl2`6ztk};LYH3=csCcMuNJ2wEh$4lLdgdV0uO>*istlny0j-a)noE6?I6?U%rB}M? z7j}7~xsRZIs_2d)9K;d|1h2p|s9#XXekbUTG0qCFSnm)DP_r*N6g!johC=}J)g^XE zd-g=12J=?EVf-6A$cowvEvR*tBMbflzEF8otdf9vBzIl3URPzp{*~t4GEga)H+CrW zs1%_HFH!J`@!y|`JrKcO5Pp&CRYMdNFGpG6OQh;15Og08G$2bR-bsOhhfl zeHgl`>WMiME8hQ+K*8@{UpGWUQ0Fh#8CIp?ns|5qU6z#>*Fi>Pf=Lsq7Nl%$wQAU1 zid?JiF~&bpa*IgJ>63-XYZ)!5%*_WfMscdf8W=B_7nkM+Ofh5JDm**4c4|J!*D1^> zjZ&sv6foBZ)LQM~U8b0u{J+yY>ngn8xTaMb^QM|q0ZphDQ&A5C&r-7HFgxg#=Vzi< zZr3-1VJ#Pjhh|9^r@mCMF5hX=DydWU9iHPl^AK(-9=R8eHLZ82E}#Z-?UE_cXN};@ zX?j|a1R_M(cRoIb5nl$py*CD6Tgkeq#)^-`h>tCc+;$m(Ja|W|6BJse*{F6(dCTsc z+C5;=8wz(g&|JVxB&x7}Vc-H{1M^I>o#%dB!ZE;n4zP!y#i_M)1d7|T9v6WF|UQ7!SegOsuigmD`RpC${U}#||Yri>2|Fo76U^(1F&qDn7 zMIW3Z|KvdW^=s!RO#DCPiT`WS|6{FlHQ{_xo_)Xn-O#(IPbY_jkUEMI_E4k`2zjsx zQr-f40s#m9vW`EJa(3>7ai0$*RC64LTffNRhFLMX1k|*;Xp#j`YHKuJUu|+<0^C-) zFWc1ha_Q_hrTY6%J#RWOZU|BN|Mq|P_osi~UU%O5zpQW(XHB*rFT=lQLd<3}d&y+w zd!KoE>4<;dl)0CDhqB&woeA(BF}Z!IWPK-{q%VC+XT6qq8e}1q{B^MMyDmd0{U)&D zPdeeCf3FSi0iOZ0;(u9?e>cGRm)~$789ugHE_~|V^!iQzJ|mKRrh=jOw17&E(VT6E zpEc%&z{5E|E01fQp1s34{+Is6V5*9up?{RJROmuKuQJYJ}(e0 z`nZ`1xp;OC{n(H7<5-J&t#UF3(9~cKtp_5c3Mk{aWi-+w3JSs7zkY17-H$ zu7v>VC=6m(Goe;kTyzc8tViF#QpSJ==@NX$8a@8MTt_wyVV2BTIV5N?H)E;75R)+0%2k>=KG*0n8EyRDJ>+Y2_14?OvE!lI!Sy-88BDHXa`D6rahZa7 z=Um*FOCn3PO|3#Obcxat%6oCN6Zr9wIzDP7N&`yUG5QL*PT&qd*-z#)!+FRsx~fey zom%25Rg3#pT5Cix?%3{y3MNL@3wZ0Q^8_@;0n%HW8P^2&%3x~?-Qn|3$sxqdF^zHo z@poBDY0V8(CQZYt$~o*_n~NMU}Z*~uz#GB?nGp?Q;Ji`kq>R#!|We6gLPXU1(Ugvnp}nB#S4L)6WL5dP9Ow zs6oR`*)r_ZEbt3QU}cJ_hT5aq!x!v}>jxn+e(F}-oqAA(?3qC`oT5Ry!tCE`DB>l5 zQ-ky;XhyE2qj_lYVqR!-MG!Ak@UFRv9;o@=XQ-pa?J0~FFd(Z{x9kR}qd`@xMZeDq z{?SB>RHuv5YV2sF##|#-i{5)qyrFkoq(lI;M{7{^WYq^N=qxn)wX$sVwo?auOyX{7 zmSX2Ii3qjA=*`W#3CWbX1cMaAO7-@ntrrGJ#xg>4EUqK9G=+nOW_OD1tLr0#uN~jS zjSTAvZU(q|W#uV(khFJtBoAeuUqZJ(qKCFShi9U71u0=TsCe+)N@D6m!-dwL;-K~= z3fvw91l_GvI${hve+BjxFTeow_wC0j_2H=}1iDe*bqbLV_%VhJ^NSs5a03fn1a8$i zrcbbL56xtKdDixyo0nI)KssbM7yi*n4xd?g+gDQvxhhykwahaF$~X8 z?m6W4b>7rNUmt;dxO5l$GJ3fBT@^lAFn%c+4=qq z&kbMXrJT3uCh00ua2gM0lR1Y4G39P$)u1J+;EnJHL>JbV@|YwdI(H+5PgOOSnUueo zU_PtpwJq_xo4eVlzqn`j!8Yx-aFg_kQ_pt+se6MonuFG+qDu>D;;q!AOId9DU^Bu) zJp((48VhJ~>$`NeL-;9NaLle4LDB!gQfg&jE?}<=q^HHy&h`8&O+^#4;bzcJlvf$t zMYAcS&7{q@-JF01#L-Cpk|wrRlNREPy~bjA@_J=#zNq;C7iFwGb0c-Zg>#K-knA`U zOMN8^=YC?y-Q7$Mosy|sFoQLx;rY&yZpitZ?`9ZUyll&bHBYFc)#h4)A-RbcNxb>{ zUN56YQ|0%}kO%vvx^Q5|8tyr?8^rM;xR4{AcJEkz7M-%$a7FjfAZ&b_2**RIV`bC& zS%3MJ@~be-TmtqD(zH7m8GpOm>6?;a?6Y1T`o=$vucG;iu45SyzeXli-6W$3wrkcv zw}s~s07z=G+=UGBQ^nj<-};r3`2`=fQm=$(rC*o-YR&uqTp^tO@PeMwHa zb;%{oth_(Wu zD~@f++HB~CDxPf#-MkmYQ(R%MaOXj72!{WCQ?eCUgBi4(zmQ*K!5z|AhpG(?Z+rIW z+bY6}4osT)aE8-80jCEe8S1nEnz+dS&N9I0o7iVMWET>5tU9WNy}plAbYEUkHTg

0zGh%HPCY&11) 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 - @@ -21,5 +20,6 @@ + diff --git a/swing/build.xml b/swing/build.xml index 380ece52e..760ab7b86 100644 --- a/swing/build.xml +++ b/swing/build.xml @@ -7,8 +7,8 @@ - - + + @@ -45,6 +45,7 @@ + @@ -56,7 +57,7 @@ - + @@ -103,7 +104,7 @@ - + diff --git a/swing/test/net/sf/openrocket/Estes_A8.rse b/swing/test/net/sf/openrocket/Estes_A8.rse new file mode 100644 index 000000000..1098ffe10 --- /dev/null +++ b/swing/test/net/sf/openrocket/Estes_A8.rse @@ -0,0 +1,40 @@ + + + +Estes A8 RASP.ENG file made from NAR published data +File produced October 3, 2000 +The total impulse, peak thrust, average thrust and burn time are +the same as the averaged static test data on the NAR web site in +the certification file. The curve drawn with these data points is as +close to the certification curve as can be with such a limited +number of points (32) allowed with wRASP up to v1.6. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/test/net/sf/openrocket/simplerocket.ork b/swing/test/net/sf/openrocket/simplerocket.ork similarity index 100% rename from core/test/net/sf/openrocket/simplerocket.ork rename to swing/test/net/sf/openrocket/simplerocket.ork From fc5faf2285a98a715cb6a08a7d57fe41d156a245 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Mon, 30 Sep 2013 11:57:34 -0500 Subject: [PATCH 11/47] Update travis to use base build. --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index bb0b5b3ad..5d030ed65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,4 @@ language: java jdk: - oraclejdk7 script: - - "cd core" - - "ant -buildfile build.xml clean checkascii build jar" - - "ant -buildfile build.xml unittest-no-junit-report" + - "ant -buildfile build.xml clean check jar unittest" From 393850a367673f21cf4dbd03f3697073e540dc22 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Mon, 30 Sep 2013 12:03:15 -0500 Subject: [PATCH 12/47] Change case in core jar name. --- swing/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swing/build.xml b/swing/build.xml index 760ab7b86..748b52946 100644 --- a/swing/build.xml +++ b/swing/build.xml @@ -92,7 +92,7 @@ - + From 43187b2b2c5179334ae87813573facf4daa40bb3 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Fri, 13 Sep 2013 19:24:36 -0500 Subject: [PATCH 13/47] Implement Configuration tab in BasicFrame next to Design and Simulation tabs. Provides functionality to configure motors, recovery and stages. Todo - verify functionality, add listeners to the table contents change when the design is updated, add listener so the configuration is highlighed to reflect selection in RocketFigure. --- core/resources/l10n/messages.properties | 1 + .../DeploymentSelectionDialog.java | 3 +- .../IgnitionSelectionDialog.java | 3 +- .../RenameConfigDialog.java | 3 +- .../SeparationSelectionDialog.java | 3 +- .../sf/openrocket/gui/main/BasicFrame.java | 6 +- .../FlightConfigurablePanel.java | 45 +++ .../FlightConfigurationPanel.java | 193 ++++++++++++ .../MotorConfigurationPanel.java | 295 ++++++++++++++++++ .../MotorConfigurationTableModel.java | 92 ++++++ .../RecoveryConfigurationPanel.java | 209 +++++++++++++ .../flightconfigpanel/RecoveryTableModel.java | 92 ++++++ .../SeparationConfigurationPanel.java | 205 ++++++++++++ .../SeparationTableModel.java | 97 ++++++ 14 files changed, 1241 insertions(+), 6 deletions(-) create mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java create mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java create mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java create mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java create mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java create mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java create mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java create mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 7d169196e..d257d8814 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -58,6 +58,7 @@ RocketPanel.lbl.ViewType = View Type: ! BasicFrame BasicFrame.tab.Rocketdesign = Rocket design +BasicFrame.tab.Flightconfig = Configurations BasicFrame.tab.Flightsim = Flight simulations BasicFrame.title.Addnewcomp = Add new component BasicFrame.dlg.lbl1 = Design ' diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/DeploymentSelectionDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/DeploymentSelectionDialog.java index 87bfe2a76..6744f07eb 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/DeploymentSelectionDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/DeploymentSelectionDialog.java @@ -1,6 +1,7 @@ package net.sf.openrocket.gui.dialogs.flightconfiguration; import java.awt.Dialog; +import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -43,7 +44,7 @@ public class DeploymentSelectionDialog extends JDialog { private final UnitSelector altUnit; private final JSlider altSlider; - DeploymentSelectionDialog(JDialog parent, final Rocket rocket, final RecoveryDevice component) { + public DeploymentSelectionDialog(Window parent, final Rocket rocket, final RecoveryDevice component) { super(parent, trans.get("edtmotorconfdlg.title.Selectdeploymentconf"), Dialog.ModalityType.APPLICATION_MODAL); final String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/IgnitionSelectionDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/IgnitionSelectionDialog.java index f4e8cd4f6..8093deb34 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/IgnitionSelectionDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/IgnitionSelectionDialog.java @@ -1,6 +1,7 @@ package net.sf.openrocket.gui.dialogs.flightconfiguration; import java.awt.Dialog; +import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -36,7 +37,7 @@ public class IgnitionSelectionDialog extends JDialog { private IgnitionConfiguration newConfiguration; - public IgnitionSelectionDialog(JDialog parent, final Rocket rocket, final MotorMount component) { + public IgnitionSelectionDialog(Window parent, final Rocket rocket, final MotorMount component) { super(parent, trans.get("edtmotorconfdlg.title.Selectignitionconf"), Dialog.ModalityType.APPLICATION_MODAL); final String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java index bd5d5afeb..1b26bc5d8 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java @@ -1,6 +1,7 @@ package net.sf.openrocket.gui.dialogs.flightconfiguration; import java.awt.Dialog; +import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -20,7 +21,7 @@ public class RenameConfigDialog extends JDialog { private static final Translator trans = Application.getTranslator(); - RenameConfigDialog(final FlightConfigurationDialog parent, final Rocket rocket) { + public RenameConfigDialog(final Window parent, final Rocket rocket) { super(parent, trans.get("RenameConfigDialog.title"), Dialog.ModalityType.APPLICATION_MODAL); final String configId = rocket.getDefaultConfiguration().getFlightConfigurationID(); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java index 58a76f19f..2456d1694 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationSelectionDialog.java @@ -1,6 +1,7 @@ package net.sf.openrocket.gui.dialogs.flightconfiguration; import java.awt.Dialog; +import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -35,7 +36,7 @@ public class SeparationSelectionDialog extends JDialog { private StageSeparationConfiguration newConfiguration; - SeparationSelectionDialog(JDialog parent, final Rocket rocket, final Stage component) { + public SeparationSelectionDialog(Window parent, final Rocket rocket, final Stage component) { super(parent, trans.get("edtmotorconfdlg.title.Selectseparationconf"), Dialog.ModalityType.APPLICATION_MODAL); final String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index e44fa0c24..0b754ef7e 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -83,6 +83,7 @@ import net.sf.openrocket.gui.dialogs.optimization.GeneralOptimizationDialog; import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog; import net.sf.openrocket.gui.help.tours.GuidedTourSelectionDialog; import net.sf.openrocket.gui.main.componenttree.ComponentTree; +import net.sf.openrocket.gui.main.flightconfigpanel.FlightConfigurationPanel; import net.sf.openrocket.gui.scalefigure.RocketPanel; import net.sf.openrocket.gui.util.FileHelper; import net.sf.openrocket.gui.util.GUIUtil; @@ -100,10 +101,10 @@ import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.MemoryManagement; import net.sf.openrocket.util.MemoryManagement.MemoryData; -import net.sf.openrocket.utils.ComponentPresetEditor; import net.sf.openrocket.util.Reflection; import net.sf.openrocket.util.StateChangeListener; import net.sf.openrocket.util.TestRockets; +import net.sf.openrocket.utils.ComponentPresetEditor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -195,6 +196,8 @@ public class BasicFrame extends JFrame { tabbedPane = new JTabbedPane(); //// Rocket design tabbedPane.addTab(trans.get("BasicFrame.tab.Rocketdesign"), null, designTab()); + //// Flight configurations + tabbedPane.addTab(trans.get("BasicFrame.tab.Flightconfig"), null, new FlightConfigurationPanel(document)); //// Flight simulations tabbedPane.addTab(trans.get("BasicFrame.tab.Flightsim"), null, simulationPanel); @@ -251,7 +254,6 @@ public class BasicFrame extends JFrame { log.debug("BasicFrame instantiation complete"); } - /** * Construct the "Rocket design" tab. This contains a horizontal split pane * with the left component the design tree and the right component buttons diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java new file mode 100644 index 000000000..54ec0b4a4 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java @@ -0,0 +1,45 @@ +package net.sf.openrocket.gui.main.flightconfigpanel; + +import javax.swing.JPanel; +import javax.swing.JTable; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.formatting.RocketDescriptor; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.Pair; + +public abstract class FlightConfigurablePanel extends JPanel { + + protected static final Translator trans = Application.getTranslator(); + protected RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); + + protected final FlightConfigurationPanel flightConfigurationPanel; + protected final Rocket rocket; + + public FlightConfigurablePanel(final FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { + super(new MigLayout("fill")); + this.flightConfigurationPanel = flightConfigurationPanel; + this.rocket = rocket; + } + + protected abstract JTable getTable(); + + protected T getSelectedComponent() { + + int col = getTable().getSelectedColumn(); + int row = getTable().getSelectedRow(); + if ( row < 0 || col < 0 ) { + return null; + } + Object tableValue = getTable().getModel().getValueAt(row, col); + if ( tableValue instanceof Pair ) { + Pair selectedComponent = (Pair) tableValue; + return selectedComponent.getV(); + } + return null; + } + +} \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java new file mode 100644 index 000000000..66b8ed08a --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java @@ -0,0 +1,193 @@ +package net.sf.openrocket.gui.main.flightconfigpanel; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.EventObject; + +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.SwingUtilities; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.Simulation; +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.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.StateChangeListener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FlightConfigurationPanel extends JPanel implements StateChangeListener { + + private static final Logger log = LoggerFactory.getLogger(FlightConfigurationPanel.class); + private static final Translator trans = Application.getTranslator(); + + private final OpenRocketDocument document; + private final Rocket rocket; + + private final JButton newConfButton, renameConfButton, removeConfButton, copyConfButton; + + private final JTabbedPane tabs; + private final MotorConfigurationPanel motorConfigurationPanel; + private final RecoveryConfigurationPanel recoveryConfigurationPanel; + private final SeparationConfigurationPanel separationConfigurationPanel; + + @Override + public void stateChanged(EventObject e) { + updateButtonState(); + } + + public FlightConfigurationPanel(OpenRocketDocument doc) { + super(new MigLayout("fill")); + + this.document = doc; + this.rocket = doc.getRocket(); + + JPanel panel = new JPanel(new MigLayout("fill")); + + //// Tabs for advanced view. + tabs = new JTabbedPane(); + this.add(tabs, "grow, spanx, wrap"); + + //// Motor tabs + motorConfigurationPanel = new MotorConfigurationPanel(this, rocket); + tabs.add(trans.get("edtmotorconfdlg.lbl.Motortab"), motorConfigurationPanel); + //// Recovery tab + recoveryConfigurationPanel = new RecoveryConfigurationPanel(this, rocket); + tabs.add(trans.get("edtmotorconfdlg.lbl.Recoverytab"), recoveryConfigurationPanel); + + //// Stage tab + separationConfigurationPanel = new SeparationConfigurationPanel(this, rocket); + tabs.add(trans.get("edtmotorconfdlg.lbl.Stagetab"), separationConfigurationPanel); + + newConfButton = new JButton(trans.get("edtmotorconfdlg.but.Newconfiguration")); + newConfButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + addConfiguration(); + configurationChanged(); + } + + }); + + panel.add(newConfButton); + + renameConfButton = new JButton(trans.get("edtmotorconfdlg.but.Renameconfiguration")); + renameConfButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + renameConfiguration(); + configurationChanged(); + } + }); + panel.add(renameConfButton); + + removeConfButton = new JButton(trans.get("edtmotorconfdlg.but.Removeconfiguration")); + removeConfButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + removeConfiguration(); + configurationChanged(); + } + }); + panel.add(removeConfButton); + + copyConfButton = new JButton(trans.get("edtmotorconfdlg.but.Copyconfiguration")); + copyConfButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + copyConfiguration(); + configurationChanged(); + } + }); + panel.add(copyConfButton, "wrap para"); + + this.add(panel, "growx"); + updateButtonState(); + + this.rocket.getDefaultConfiguration().addChangeListener(this); + } + + private void addConfiguration() { + String newId = rocket.newFlightConfigurationID(); + rocket.getDefaultConfiguration().setFlightConfigurationID(newId); + + // Create a new simulation for this configuration. + createSimulationForNewConfiguration(); + + configurationChanged(); + } + + private void copyConfiguration() { + String currentId = rocket.getDefaultConfiguration().getFlightConfigurationID(); + + // currentID is the currently selected configuration. + String newConfigId = rocket.newFlightConfigurationID(); + String oldName = rocket.getFlightConfigurationName(currentId); + + for (RocketComponent c : rocket) { + if (c instanceof FlightConfigurableComponent) { + ((FlightConfigurableComponent) c).cloneFlightConfiguration(currentId, newConfigId); + } + } + rocket.setFlightConfigurationName(currentId, oldName); + rocket.getDefaultConfiguration().setFlightConfigurationID(newConfigId); + + // Create a new simulation for this configuration. + createSimulationForNewConfiguration(); + + configurationChanged(); + } + + private void renameConfiguration() { + new RenameConfigDialog(SwingUtilities.getWindowAncestor(this), rocket).setVisible(true); + } + + private void removeConfiguration() { + String currentId = rocket.getDefaultConfiguration().getFlightConfigurationID(); + if (currentId == null) + return; + rocket.removeFlightConfigurationID(currentId); + rocket.getDefaultConfiguration().setFlightConfigurationID(null); + configurationChanged(); + } + + /** + * prereq - assumes that the new configuration has been set as the default configuration. + */ + private void createSimulationForNewConfiguration() { + Simulation newSim = new Simulation(rocket); + OpenRocketDocument doc = BasicFrame.findDocument(rocket); + newSim.setName(doc.getNextSimulationName()); + doc.addSimulation(newSim); + } + + private void configurationChanged() { + motorConfigurationPanel.fireTableDataChanged(); + recoveryConfigurationPanel.fireTableDataChanged(); + separationConfigurationPanel.fireTableDataChanged(); + updateButtonState(); + } + + private void updateButtonState() { + String currentId = rocket.getDefaultConfiguration().getFlightConfigurationID(); + removeConfButton.setEnabled(currentId != null); + renameConfButton.setEnabled(currentId != null); + copyConfButton.setEnabled(currentId != null); + + } + + public void setCurrentConfiguration(String id) { + rocket.getDefaultConfiguration().setFlightConfigurationID(id); + updateButtonState(); + } + +} diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java new file mode 100644 index 000000000..2914ba557 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java @@ -0,0 +1,295 @@ +package net.sf.openrocket.gui.main.flightconfigpanel; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableCellRenderer; + +import net.sf.openrocket.gui.dialogs.flightconfiguration.IgnitionSelectionDialog; +import net.sf.openrocket.gui.dialogs.motor.MotorChooserDialog; +import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.rocketcomponent.IgnitionConfiguration; +import net.sf.openrocket.rocketcomponent.MotorConfiguration; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Chars; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.Pair; + +public class MotorConfigurationPanel extends FlightConfigurablePanel { + + private static final String NONE = trans.get("edtmotorconfdlg.tbl.None"); + + private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton; + + protected final JTable configurationTable; + protected final MotorConfigurationTableModel configurationTableModel; + + MotorConfigurationPanel(final FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { + super(flightConfigurationPanel,rocket); + + //// Motor selection table. + configurationTableModel = new MotorConfigurationTableModel(rocket); + configurationTable = new JTable(configurationTableModel); + configurationTable.setCellSelectionEnabled(true); + configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + configurationTable.setDefaultRenderer(Object.class, new MotorTableCellRenderer()); + + configurationTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + updateButtonState(); + int selectedColumn = configurationTable.getSelectedColumn(); + if (e.getClickCount() == 2) { + if (selectedColumn > 0) { + selectMotor(); + } + } + } + }); + + configurationTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + + @Override + public void valueChanged(ListSelectionEvent e) { + if ( e.getValueIsAdjusting() ) { + return; + } + int firstrow = e.getFirstIndex(); + int lastrow = e.getLastIndex(); + ListSelectionModel model = (ListSelectionModel) e.getSource(); + for( int row = firstrow; row <= lastrow; row ++) { + if ( model.isSelectedIndex(row) ) { + String id = (String) configurationTableModel.getValueAt(row, 0); + flightConfigurationPanel.setCurrentConfiguration(id); + return; + } + } + } + + }); + + JScrollPane scroll = new JScrollPane(configurationTable); + this.add(scroll, "grow, wrap"); + + //// Select motor + selectMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectMotor")); + selectMotorButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selectMotor(); + } + }); + this.add(selectMotorButton, "split, sizegroup button"); + + //// Remove motor button + removeMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.removeMotor")); + removeMotorButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + removeMotor(); + } + }); + this.add(removeMotorButton, "sizegroup button"); + + //// Select Ignition button + selectIgnitionButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectIgnition")); + selectIgnitionButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selectIgnition(); + } + }); + this.add(selectIgnitionButton, "sizegroup button"); + + //// Reset Ignition button + resetIgnitionButton = new JButton(trans.get("MotorConfigurationPanel.btn.resetIgnition")); + resetIgnitionButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + resetIgnition(); + } + }); + this.add(resetIgnitionButton, "sizegroup button, wrap"); + + updateButtonState(); + + } + + @Override + protected JTable getTable() { + return configurationTable; + } + + public void fireTableDataChanged() { + int selected = configurationTable.getSelectedRow(); + configurationTableModel.fireTableDataChanged(); + if (selected >= 0) { + selected = Math.min(selected, configurationTable.getRowCount() - 1); + configurationTable.getSelectionModel().setSelectionInterval(selected, selected); + } + updateButtonState(); + } + + 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); + } + + + private void selectMotor() { + String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); + MotorMount mount = getSelectedComponent(); + if (id == null || mount == null) + return; + + MotorConfiguration config = mount.getMotorConfiguration().get(id); + + MotorChooserDialog dialog = new MotorChooserDialog( + config.getMotor(), + config.getEjectionDelay(), + mount.getMotorMountDiameter(), + SwingUtilities.getWindowAncestor(flightConfigurationPanel)); + dialog.setVisible(true); + Motor m = dialog.getSelectedMotor(); + double d = dialog.getSelectedDelay(); + + if (m != null) { + config = new MotorConfiguration(); + config.setMotor(m); + config.setEjectionDelay(d); + mount.getMotorConfiguration().set(id, config); + } + + fireTableDataChanged(); + } + + private void removeMotor() { + String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); + MotorMount mount = getSelectedComponent(); + if (id == null || mount == null) + return; + + mount.getMotorConfiguration().resetDefault(id); + + fireTableDataChanged(); + } + + private void selectIgnition() { + String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); + MotorMount currentMount = getSelectedComponent(); + if (currentID == null || currentMount == null) + return; + + IgnitionSelectionDialog dialog = new IgnitionSelectionDialog( + SwingUtilities.getWindowAncestor(this.flightConfigurationPanel), + rocket, + currentMount); + dialog.setVisible(true); + + fireTableDataChanged(); + } + + + private void resetIgnition() { + String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); + MotorMount currentMount = getSelectedComponent(); + if (currentID == null || currentMount == null) + return; + + currentMount.getIgnitionConfiguration().resetDefault(currentID); + + fireTableDataChanged(); + } + + + private class MotorTableCellRenderer extends DefaultTableCellRenderer { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (!(c instanceof JLabel)) { + return c; + } + JLabel label = (JLabel) c; + + switch (column) { + case 0: { + label.setText(descriptor.format(rocket, (String) value)); + return label; + } + default: { + Pair v = (Pair) value; + String id = v.getU(); + MotorMount mount = v.getV(); + MotorConfiguration motorConfig = mount.getMotorConfiguration().get(id); + String motorString = getMotorSpecification(mount, motorConfig); + String ignitionString = getIgnitionEventString(id, mount); + label.setText(motorString + " " + ignitionString); + return label; + } + } + } + + private String getMotorSpecification(MotorMount mount, MotorConfiguration motorConfig) { + Motor motor = motorConfig.getMotor(); + + if (motor == null) + return NONE; + + String str = motor.getDesignation(motorConfig.getEjectionDelay()); + int count = getMountMultiplicity(mount); + if (count > 1) { + str = "" + count + Chars.TIMES + " " + str; + } + return str; + } + + private int getMountMultiplicity(MotorMount mount) { + RocketComponent c = (RocketComponent) mount; + return c.toAbsolute(Coordinate.NUL).length; + } + + + + private String getIgnitionEventString(String id, MotorMount mount) { + IgnitionConfiguration ignitionConfig = mount.getIgnitionConfiguration().get(id); + IgnitionConfiguration.IgnitionEvent ignitionEvent = ignitionConfig.getIgnitionEvent(); + + Double ignitionDelay = ignitionConfig.getIgnitionDelay(); + boolean isDefault = mount.getIgnitionConfiguration().isDefault(id); + + String str = trans.get("MotorMount.IgnitionEvent.short." + ignitionEvent.name()); + if (ignitionDelay > 0.001) { + str = str + " + " + UnitGroup.UNITS_SHORT_TIME.toStringUnit(ignitionDelay); + } + if (isDefault) { + String def = trans.get("MotorConfigurationTableModel.table.ignition.default"); + str = def.replace("{0}", str); + } + return str; + } + + } + +} diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java new file mode 100644 index 000000000..90896afbb --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java @@ -0,0 +1,92 @@ +package net.sf.openrocket.gui.main.flightconfigpanel; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.Pair; + +/** + * The table model for selecting and editing the motor configurations. + */ +class MotorConfigurationTableModel extends AbstractTableModel { + + private static final Translator trans = Application.getTranslator(); + + private static final String CONFIGURATION = "Configuration"; + + private final Rocket rocket; + + private List motorMounts = new ArrayList(); + + + public MotorConfigurationTableModel(Rocket rocket) { + this.rocket = rocket; + + initializeMotorMounts(); + + } + + private void initializeMotorMounts() { + motorMounts.clear(); + for (RocketComponent c : rocket) { + if (c instanceof MotorMount) { + if (((MotorMount) c).isMotorMount()) { + motorMounts.add((MotorMount) c); + } + } + } + + } + + @Override + public int getColumnCount() { + return motorMounts.size() + 1; + } + + @Override + public int getRowCount() { + return rocket.getFlightConfigurationIDs().length - 1; + } + + @Override + public Object getValueAt(int row, int column) { + String id = getConfiguration(row); + switch (column) { + case 0: { + return id; + } + default: { + int mountIndex = column - 1; + MotorMount mount = motorMounts.get(mountIndex); + return new Pair(id, mount); + } + } + } + + @Override + public String getColumnName(int column) { + switch (column) { + case 0: { + return CONFIGURATION; + } + default: { + int mountIndex = column - 1; + MotorMount mount = motorMounts.get(mountIndex); + return mount.toString(); + } + } + } + + private String getConfiguration(int row) { + String id = rocket.getFlightConfigurationIDs()[row + 1]; + return id; + } + +} \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java new file mode 100644 index 000000000..49e7c76c3 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java @@ -0,0 +1,209 @@ +package net.sf.openrocket.gui.main.flightconfigpanel; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +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.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.table.DefaultTableCellRenderer; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.formatting.RocketDescriptor; +import net.sf.openrocket.gui.dialogs.flightconfiguration.DeploymentSelectionDialog; +import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; +import net.sf.openrocket.rocketcomponent.DeploymentConfiguration.DeployEvent; +import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Pair; + +public class RecoveryConfigurationPanel extends JPanel { + + Translator trans = Application.getTranslator(); + private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); + + private final FlightConfigurationPanel flightConfigurationPanel; + final Rocket rocket; + + private final RecoveryTableModel recoveryTableModel; + private final JTable recoveryTable; + private final JButton selectDeploymentButton; + private final JButton resetDeploymentButton; + + + RecoveryConfigurationPanel(FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { + super(new MigLayout("fill")); + this.flightConfigurationPanel = flightConfigurationPanel; + this.rocket = rocket; + + //// Recovery selection + recoveryTableModel = new RecoveryTableModel(rocket); + recoveryTable = new JTable(recoveryTableModel); + recoveryTable.setCellSelectionEnabled(true); + recoveryTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + recoveryTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + updateButtonState(); + + if (e.getClickCount() == 2) { + // Double-click edits + selectDeployment(); + } + } + }); + recoveryTable.setDefaultRenderer(Object.class, new RecoveryTableCellRenderer()); + + JScrollPane scroll = new JScrollPane(recoveryTable); + this.add(scroll, "span, grow, wrap"); + + //// Select deployment + selectDeploymentButton = new JButton(trans.get("edtmotorconfdlg.but.Selectdeployment")); + selectDeploymentButton.setEnabled(false); + selectDeploymentButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selectDeployment(); + } + }); + this.add(selectDeploymentButton, "split, sizegroup button"); + + //// Reset deployment + resetDeploymentButton = new JButton(trans.get("edtmotorconfdlg.but.Resetdeployment")); + resetDeploymentButton.setEnabled(false); + resetDeploymentButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + resetDeployment(); + } + }); + this.add(resetDeploymentButton, "sizegroup button, wrap"); + } + + public void fireTableDataChanged() { + int selected = recoveryTable.getSelectedRow(); + recoveryTableModel.fireTableDataChanged(); + if (selected >= 0) { + selected = Math.min(selected, recoveryTable.getRowCount() - 1); + recoveryTable.getSelectionModel().setSelectionInterval(selected, selected); + } + updateButtonState(); + } + + private void selectDeployment() { + RecoveryDevice c = getSelectedComponent(); + if (c == null) { + return; + } + JDialog d = new DeploymentSelectionDialog(SwingUtilities.getWindowAncestor(this), rocket, c); + d.setVisible(true); + fireTableDataChanged(); + } + + private void resetDeployment() { + RecoveryDevice c = getSelectedComponent(); + if (c == null) { + return; + } + String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); + c.getDeploymentConfiguration().resetDefault(id); + fireTableDataChanged(); + } + + public void updateButtonState() { + boolean componentSelected = getSelectedComponent() != null; + selectDeploymentButton.setEnabled(componentSelected); + resetDeploymentButton.setEnabled(componentSelected); + } + + + private RecoveryDevice getSelectedComponent() { + int col = recoveryTable.getSelectedColumn(); + int row = recoveryTable.getSelectedRow(); + if ( row < 0 || col < 0 ) { + return null; + } + Object tableValue = recoveryTable.getModel().getValueAt(row, col); + if ( tableValue instanceof Pair ) { + Pair selected = (Pair) tableValue; + return selected.getV(); + } + return null; + } + + class RecoveryTableCellRenderer extends DefaultTableCellRenderer { + + RecoveryTableCellRenderer() {} + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (!(c instanceof JLabel)) { + return c; + } + JLabel label = (JLabel) c; + + switch (column) { + case 0: { + label.setText(descriptor.format(rocket, (String) value)); + regular(label); + return label; + } + default: { + Pair v = (Pair) value; + String id = v.getU(); + RecoveryDevice recovery = v.getV(); + DeploymentConfiguration deployConfig = recovery.getDeploymentConfiguration().get(id); + String spec = getDeploymentSpecification(deployConfig); + label.setText(spec); + if (recovery.getDeploymentConfiguration().isDefault(id)) { + shaded(label); + } else { + regular(label); + } + break; + } + } + return label; + } + + private void shaded(JLabel label) { + GUIUtil.changeFontStyle(label, Font.ITALIC); + label.setForeground(Color.GRAY); + } + + private void regular(JLabel label) { + GUIUtil.changeFontStyle(label, Font.PLAIN); + label.setForeground(Color.BLACK); + } + + private String getDeploymentSpecification( DeploymentConfiguration config ) { + String str; + + str = trans.get("RecoveryDevice.DeployEvent.short." + config.getDeployEvent().name()); + if (config.getDeployEvent() == DeployEvent.ALTITUDE) { + str += " " + UnitGroup.UNITS_DISTANCE.toStringUnit(config.getDeployAltitude()); + } + if (config.getDeployDelay() > 0.001) { + str += " + " + UnitGroup.UNITS_SHORT_TIME.toStringUnit(config.getDeployDelay()); + } + + return str; + } + } + +} diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java new file mode 100644 index 000000000..acf76281f --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java @@ -0,0 +1,92 @@ +package net.sf.openrocket.gui.main.flightconfigpanel; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.Pair; + +class RecoveryTableModel extends AbstractTableModel { + + private static final Translator trans = Application.getTranslator(); + + private final Rocket rocket; + + private final List recoveryDevices = new ArrayList(); + + private static final String CONFIGURATION = "Configuration"; + + + /** + * @param recoveryConfigurationPanel + */ + RecoveryTableModel(Rocket rocket) { + this.rocket = rocket; + + initialize(); + } + + private void initialize() { + recoveryDevices.clear(); + Iterator it = rocket.iterator(); + while (it.hasNext()) { + RocketComponent c = it.next(); + if (c instanceof RecoveryDevice) { + recoveryDevices.add( (RecoveryDevice) c); + } + } + } + + @Override + public int getRowCount() { + return rocket.getFlightConfigurationIDs().length - 1; + } + + @Override + public int getColumnCount() { + return recoveryDevices.size() + 1; + } + + @Override + public Object getValueAt(int row, int column) { + String id = getConfiguration(row); + switch (column) { + case 0: { + return id; + } + default: { + int index = column - 1; + RecoveryDevice d = recoveryDevices.get(index); + return new Pair(id, d); + } + } + } + + @Override + public String getColumnName(int column) { + switch (column) { + case 0: { + return CONFIGURATION; + } + default: { + int index = column - 1; + RecoveryDevice d = recoveryDevices.get(index); + return d.toString(); + + } + } + } + + private String getConfiguration(int row) { + String id = rocket.getFlightConfigurationIDs()[row + 1]; + return id; + } + +} \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java new file mode 100644 index 000000000..e6233f0ad --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java @@ -0,0 +1,205 @@ +package net.sf.openrocket.gui.main.flightconfigpanel; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +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.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.table.DefaultTableCellRenderer; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.formatting.RocketDescriptor; +import net.sf.openrocket.gui.dialogs.flightconfiguration.SeparationSelectionDialog; +import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.Stage; +import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Pair; + +public class SeparationConfigurationPanel extends JPanel { + + static final Translator trans = Application.getTranslator(); + private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); + + private final FlightConfigurationPanel flightConfigurationPanel; + final Rocket rocket; + + private final JTable separationTable; + private final SeparationTableModel separationTableModel; + private final JButton selectSeparationButton; + private final JButton resetDeploymentButton; + + + SeparationConfigurationPanel(FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { + super(new MigLayout("fill")); + this.flightConfigurationPanel = flightConfigurationPanel; + this.rocket = rocket; + + + //// Recovery selection + separationTableModel = new SeparationTableModel(rocket); + separationTable = new JTable(separationTableModel); + separationTable.setCellSelectionEnabled(true); + separationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + separationTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + updateButtonState(); + if (e.getClickCount() == 2) { + // Double-click edits + selectDeployment(); + } + } + }); + separationTable.setDefaultRenderer(Object.class, new SeparationTableCellRenderer()); + + JScrollPane scroll = new JScrollPane(separationTable); + this.add(scroll, "span, grow, wrap"); + + //// Select deployment + selectSeparationButton = new JButton(trans.get("edtmotorconfdlg.but.Selectseparation")); + selectSeparationButton.setEnabled(false); + selectSeparationButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selectDeployment(); + } + }); + this.add(selectSeparationButton, "split, sizegroup button"); + + //// Reset deployment + resetDeploymentButton = new JButton(trans.get("edtmotorconfdlg.but.Resetseparation")); + resetDeploymentButton.setEnabled(false); + resetDeploymentButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + resetDeployment(); + } + }); + this.add(resetDeploymentButton, "sizegroup button, wrap"); + + } + + public void fireTableDataChanged() { + int selected = separationTable.getSelectedRow(); + separationTableModel.fireTableDataChanged(); + if (selected >= 0) { + selected = Math.min(selected, separationTable.getRowCount() - 1); + separationTable.getSelectionModel().setSelectionInterval(selected, selected); + } + updateButtonState(); + } + + private Stage getSelectedComponent() { + int col = separationTable.getSelectedColumn(); + int row = separationTable.getSelectedRow(); + if ( row < 0 || col < 0 ) { + return null; + } + Object tableValue = separationTable.getModel().getValueAt(row, col); + if ( tableValue instanceof Pair ) { + Pair selected = (Pair) tableValue; + return selected.getV(); + } + return null; + } + + private void selectDeployment() { + Stage stage = getSelectedComponent(); + if (stage == null) { + return; + } + JDialog d = new SeparationSelectionDialog(SwingUtilities.getWindowAncestor(this), rocket, stage); + d.setVisible(true); + fireTableDataChanged(); + } + + private void resetDeployment() { + Stage stage = getSelectedComponent(); + if (stage == null) { + return; + } + String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); + stage.getStageSeparationConfiguration().resetDefault(id); + fireTableDataChanged(); + } + public void updateButtonState() { + boolean componentSelected = getSelectedComponent() != null; + selectSeparationButton.setEnabled(componentSelected); + resetDeploymentButton.setEnabled(componentSelected); + } + + private class SeparationTableCellRenderer extends DefaultTableCellRenderer { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (!(c instanceof JLabel)) { + return c; + } + JLabel label = (JLabel) c; + + switch (column) { + case 0: { + label.setText(descriptor.format(rocket, (String) value)); + regular(label); + return label; + } + default: { + Pair v = (Pair) value; + String id = v.getU(); + Stage stage = v.getV(); + StageSeparationConfiguration sepConfig = stage.getStageSeparationConfiguration().get(id); + String spec = getSeparationSpecification(sepConfig); + label.setText(spec); + if (stage.getStageSeparationConfiguration().isDefault(id)) { + shaded(label); + } else { + regular(label); + } + break; + } + } + return label; + + } + + private void shaded(JLabel label) { + GUIUtil.changeFontStyle(label, Font.ITALIC); + label.setForeground(Color.GRAY); + } + + private void regular(JLabel label) { + GUIUtil.changeFontStyle(label, Font.PLAIN); + label.setForeground(Color.BLACK); + } + + private String getSeparationSpecification( StageSeparationConfiguration sepConfig ) { + String str; + + str = sepConfig.getSeparationEvent().toString(); + if (sepConfig.getSeparationDelay() > 0.001) { + str += " + " + UnitGroup.UNITS_SHORT_TIME.toStringUnit(sepConfig.getSeparationDelay()); + } + + return str; + + } + } + + +} diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java new file mode 100644 index 000000000..07333d0b5 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java @@ -0,0 +1,97 @@ +package net.sf.openrocket.gui.main.flightconfigpanel; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.Stage; +import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.Pair; + +class SeparationTableModel extends AbstractTableModel { + + private static final Translator trans = Application.getTranslator(); + + private final Rocket rocket; + + private final List stages = new ArrayList(); + + private static final String CONFIGURATION = "Configuration"; + + SeparationTableModel(Rocket rocket ) { + this.rocket = rocket; + + initialize(); + } + + private void initialize() { + stages.clear(); + Iterator it = rocket.iterator(); + { + int stageIndex = -1; + while (it.hasNext()) { + RocketComponent c = it.next(); + if (c instanceof Stage) { + if (stageIndex >= 0) { + stages.add( (Stage) c); + } + stageIndex++; + } + } + } + } + + + @Override + public int getRowCount() { + return rocket.getFlightConfigurationIDs().length - 1; + } + + @Override + public int getColumnCount() { + return stages.size() + 1; + } + @Override + public Object getValueAt(int row, int column) { + String id = getConfiguration(row); + switch (column) { + case 0: { + return id; + } + default: { + int index = column - 1; + Stage d = stages.get(index); + return new Pair(id, d); + } + } + } + + @Override + public String getColumnName(int column) { + switch (column) { + case 0: { + return CONFIGURATION; + } + default: { + int index = column - 1; + Stage d = stages.get(index); + return d.toString(); + + } + } + } + + private String getConfiguration(int row) { + String id = rocket.getFlightConfigurationIDs()[row + 1]; + return id; + } + +} \ No newline at end of file From 9e01ff4e762eece6e890e1eb4f3b6efc54011c35 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Mon, 30 Sep 2013 21:59:37 -0500 Subject: [PATCH 14/47] Refactor Recovery and Separation panels to derive from FlightConfigurablePanel. Added DefaultConfiguration listener to the FlightConfigurablePanel to keep the RocketFigure in sync. --- .../FlightConfigurablePanel.java | 52 +++++++++++++++++++ .../RecoveryConfigurationPanel.java | 25 +++------ .../SeparationConfigurationPanel.java | 24 +++------ 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java index 54ec0b4a4..dae63b254 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java @@ -1,5 +1,7 @@ package net.sf.openrocket.gui.main.flightconfigpanel; +import java.util.EventObject; + import javax.swing.JPanel; import javax.swing.JTable; @@ -10,6 +12,7 @@ import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Pair; +import net.sf.openrocket.util.StateChangeListener; public abstract class FlightConfigurablePanel extends JPanel { @@ -23,6 +26,39 @@ public abstract class FlightConfigurablePanel selectedComponent = (Pair) tableValue; + return selectedComponent.getU(); + } else if ( tableValue instanceof String ){ + return (String) tableValue; + } + return null; + } } \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java index 49e7c76c3..ff3660b3d 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java @@ -11,7 +11,6 @@ import java.awt.event.MouseEvent; 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.ListSelectionModel; @@ -31,7 +30,7 @@ import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.Pair; -public class RecoveryConfigurationPanel extends JPanel { +public class RecoveryConfigurationPanel extends FlightConfigurablePanel { Translator trans = Application.getTranslator(); private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); @@ -46,7 +45,7 @@ public class RecoveryConfigurationPanel extends JPanel { RecoveryConfigurationPanel(FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { - super(new MigLayout("fill")); + super(flightConfigurationPanel,rocket); this.flightConfigurationPanel = flightConfigurationPanel; this.rocket = rocket; @@ -94,6 +93,11 @@ public class RecoveryConfigurationPanel extends JPanel { this.add(resetDeploymentButton, "sizegroup button, wrap"); } + @Override + protected JTable getTable() { + return recoveryTable; + } + public void fireTableDataChanged() { int selected = recoveryTable.getSelectedRow(); recoveryTableModel.fireTableDataChanged(); @@ -130,21 +134,6 @@ public class RecoveryConfigurationPanel extends JPanel { resetDeploymentButton.setEnabled(componentSelected); } - - private RecoveryDevice getSelectedComponent() { - int col = recoveryTable.getSelectedColumn(); - int row = recoveryTable.getSelectedRow(); - if ( row < 0 || col < 0 ) { - return null; - } - Object tableValue = recoveryTable.getModel().getValueAt(row, col); - if ( tableValue instanceof Pair ) { - Pair selected = (Pair) tableValue; - return selected.getV(); - } - return null; - } - class RecoveryTableCellRenderer extends DefaultTableCellRenderer { RecoveryTableCellRenderer() {} diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java index e6233f0ad..718a90a2a 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java @@ -11,14 +11,12 @@ import java.awt.event.MouseEvent; 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.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableCellRenderer; -import net.miginfocom.swing.MigLayout; import net.sf.openrocket.formatting.RocketDescriptor; import net.sf.openrocket.gui.dialogs.flightconfiguration.SeparationSelectionDialog; import net.sf.openrocket.gui.util.GUIUtil; @@ -30,7 +28,7 @@ import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.Pair; -public class SeparationConfigurationPanel extends JPanel { +public class SeparationConfigurationPanel extends FlightConfigurablePanel { static final Translator trans = Application.getTranslator(); private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); @@ -45,7 +43,7 @@ public class SeparationConfigurationPanel extends JPanel { SeparationConfigurationPanel(FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { - super(new MigLayout("fill")); + super(flightConfigurationPanel,rocket); this.flightConfigurationPanel = flightConfigurationPanel; this.rocket = rocket; @@ -94,6 +92,11 @@ public class SeparationConfigurationPanel extends JPanel { } + @Override + protected JTable getTable() { + return separationTable; + } + public void fireTableDataChanged() { int selected = separationTable.getSelectedRow(); separationTableModel.fireTableDataChanged(); @@ -104,19 +107,6 @@ public class SeparationConfigurationPanel extends JPanel { updateButtonState(); } - private Stage getSelectedComponent() { - int col = separationTable.getSelectedColumn(); - int row = separationTable.getSelectedRow(); - if ( row < 0 || col < 0 ) { - return null; - } - Object tableValue = separationTable.getModel().getValueAt(row, col); - if ( tableValue instanceof Pair ) { - Pair selected = (Pair) tableValue; - return selected.getV(); - } - return null; - } private void selectDeployment() { Stage stage = getSelectedComponent(); From e36a90adfc25137f92d2ec54bad24bd8ee87cd05 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Tue, 1 Oct 2013 10:20:44 -0500 Subject: [PATCH 15/47] Improved common base of configuration panels. --- .../FlightConfigurablePanel.java | 100 +++++++++++++----- .../FlightConfigurationPanel.java | 5 - .../MotorConfigurationPanel.java | 74 +++++-------- .../RecoveryConfigurationPanel.java | 51 +++++---- .../SeparationConfigurationPanel.java | 49 +++++---- 5 files changed, 146 insertions(+), 133 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java index dae63b254..bc0540962 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java @@ -4,6 +4,9 @@ import java.util.EventObject; import javax.swing.JPanel; import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.formatting.RocketDescriptor; @@ -26,45 +29,84 @@ public abstract class FlightConfigurablePanel private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton; - protected final JTable configurationTable; - protected final MotorConfigurationTableModel configurationTableModel; + protected JTable configurationTable; + protected MotorConfigurationTableModel configurationTableModel; MotorConfigurationPanel(final FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { super(flightConfigurationPanel,rocket); - //// Motor selection table. - configurationTableModel = new MotorConfigurationTableModel(rocket); - configurationTable = new JTable(configurationTableModel); - configurationTable.setCellSelectionEnabled(true); - configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - configurationTable.setDefaultRenderer(Object.class, new MotorTableCellRenderer()); - - configurationTable.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - updateButtonState(); - int selectedColumn = configurationTable.getSelectedColumn(); - if (e.getClickCount() == 2) { - if (selectedColumn > 0) { - selectMotor(); - } - } - } - }); - - configurationTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - - @Override - public void valueChanged(ListSelectionEvent e) { - if ( e.getValueIsAdjusting() ) { - return; - } - int firstrow = e.getFirstIndex(); - int lastrow = e.getLastIndex(); - ListSelectionModel model = (ListSelectionModel) e.getSource(); - for( int row = firstrow; row <= lastrow; row ++) { - if ( model.isSelectedIndex(row) ) { - String id = (String) configurationTableModel.getValueAt(row, 0); - flightConfigurationPanel.setCurrentConfiguration(id); - return; - } - } - } - - }); - JScrollPane scroll = new JScrollPane(configurationTable); this.add(scroll, "grow, wrap"); @@ -132,6 +86,30 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } + @Override + protected JTable initializeTable() { + //// Motor selection table. + configurationTableModel = new MotorConfigurationTableModel(rocket); + configurationTable = new JTable(configurationTableModel); + configurationTable.setCellSelectionEnabled(true); + configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + configurationTable.setDefaultRenderer(Object.class, new MotorTableCellRenderer()); + + configurationTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + updateButtonState(); + int selectedColumn = configurationTable.getSelectedColumn(); + if (e.getClickCount() == 2) { + if (selectedColumn > 0) { + selectMotor(); + } + } + } + }); + return configurationTable; + } + @Override protected JTable getTable() { return configurationTable; diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java index ff3660b3d..448d5c092 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java @@ -17,7 +17,6 @@ import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableCellRenderer; -import net.miginfocom.swing.MigLayout; import net.sf.openrocket.formatting.RocketDescriptor; import net.sf.openrocket.gui.dialogs.flightconfiguration.DeploymentSelectionDialog; import net.sf.openrocket.gui.util.GUIUtil; @@ -35,37 +34,14 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel static final Translator trans = Application.getTranslator(); private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); - private final FlightConfigurationPanel flightConfigurationPanel; - final Rocket rocket; - - private final JTable separationTable; - private final SeparationTableModel separationTableModel; + private JTable separationTable; + private SeparationTableModel separationTableModel; private final JButton selectSeparationButton; private final JButton resetDeploymentButton; SeparationConfigurationPanel(FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { super(flightConfigurationPanel,rocket); - this.flightConfigurationPanel = flightConfigurationPanel; - this.rocket = rocket; - - - //// Recovery selection - separationTableModel = new SeparationTableModel(rocket); - separationTable = new JTable(separationTableModel); - separationTable.setCellSelectionEnabled(true); - separationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - separationTable.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - updateButtonState(); - if (e.getClickCount() == 2) { - // Double-click edits - selectDeployment(); - } - } - }); - separationTable.setDefaultRenderer(Object.class, new SeparationTableCellRenderer()); JScrollPane scroll = new JScrollPane(separationTable); this.add(scroll, "span, grow, wrap"); @@ -92,6 +69,28 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel } + @Override + protected JTable initializeTable() { + //// Separation selection + separationTableModel = new SeparationTableModel(rocket); + separationTable = new JTable(separationTableModel); + separationTable.setCellSelectionEnabled(true); + separationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + separationTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + updateButtonState(); + if (e.getClickCount() == 2) { + // Double-click edits + selectDeployment(); + } + } + }); + separationTable.setDefaultRenderer(Object.class, new SeparationTableCellRenderer()); + + return separationTable; + } + @Override protected JTable getTable() { return separationTable; From fa7916f7b0e66006636fdb941f1ddffa2b912ea0 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Tue, 1 Oct 2013 11:34:57 -0500 Subject: [PATCH 16/47] Localized column heading and added rocket listener to update columns. --- core/resources/l10n/messages.properties | 1 + .../MotorConfigurationTableModel.java | 16 +++++++++++++--- .../flightconfigpanel/RecoveryTableModel.java | 17 ++++++++++++++--- .../SeparationTableModel.java | 19 +++++++++++++------ 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index d257d8814..0241fc733 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -175,6 +175,7 @@ debuglogdlg.lbl.Stacktrace = Stack trace: MotorChooserDialog.title = Select a rocket motor ! Edit Motor configuration dialog +edtmotorconfdlg.col.configuration = Configuration edtmotorconfdlg.but.Removeconfiguration = Remove edtmotorconfdlg.but.Renameconfiguration = Rename edtmotorconfdlg.but.Newconfiguration = New diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java index 90896afbb..08a079f1c 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java @@ -6,6 +6,8 @@ import java.util.List; import javax.swing.table.AbstractTableModel; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -15,11 +17,11 @@ import net.sf.openrocket.util.Pair; /** * The table model for selecting and editing the motor configurations. */ -class MotorConfigurationTableModel extends AbstractTableModel { +class MotorConfigurationTableModel extends AbstractTableModel implements ComponentChangeListener { private static final Translator trans = Application.getTranslator(); - private static final String CONFIGURATION = "Configuration"; + private static final String CONFIGURATION = trans.get("edtmotorconfdlg.col.configuration"); private final Rocket rocket; @@ -28,11 +30,19 @@ class MotorConfigurationTableModel extends AbstractTableModel { public MotorConfigurationTableModel(Rocket rocket) { this.rocket = rocket; - + this.rocket.addComponentChangeListener(this); initializeMotorMounts(); } + @Override + public void componentChanged(ComponentChangeEvent e) { + if ( e.isMotorChange() || e.isTreeChange() ) { + initializeMotorMounts(); + fireTableStructureChanged(); + } + } + private void initializeMotorMounts() { motorMounts.clear(); for (RocketComponent c : rocket) { diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java index acf76281f..cecac53e7 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java @@ -7,13 +7,15 @@ import java.util.List; import javax.swing.table.AbstractTableModel; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Pair; -class RecoveryTableModel extends AbstractTableModel { +class RecoveryTableModel extends AbstractTableModel implements ComponentChangeListener { private static final Translator trans = Application.getTranslator(); @@ -21,7 +23,7 @@ class RecoveryTableModel extends AbstractTableModel { private final List recoveryDevices = new ArrayList(); - private static final String CONFIGURATION = "Configuration"; + private static final String CONFIGURATION = trans.get("edtmotorconfdlg.col.configuration"); /** @@ -29,10 +31,19 @@ class RecoveryTableModel extends AbstractTableModel { */ RecoveryTableModel(Rocket rocket) { this.rocket = rocket; - + this.rocket.addComponentChangeListener(this); initialize(); } + @Override + public void componentChanged(ComponentChangeEvent e) { + if ( e.isTreeChange() ) { + initialize(); + fireTableStructureChanged(); + } + } + + private void initialize() { recoveryDevices.clear(); Iterator it = rocket.iterator(); diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java index 07333d0b5..10115a18f 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java @@ -7,16 +7,15 @@ import java.util.List; import javax.swing.table.AbstractTableModel; import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.rocketcomponent.RecoveryDevice; +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.rocketcomponent.StageSeparationConfiguration; import net.sf.openrocket.startup.Application; -import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.Pair; -class SeparationTableModel extends AbstractTableModel { +class SeparationTableModel extends AbstractTableModel implements ComponentChangeListener { private static final Translator trans = Application.getTranslator(); @@ -24,14 +23,22 @@ class SeparationTableModel extends AbstractTableModel { private final List stages = new ArrayList(); - private static final String CONFIGURATION = "Configuration"; + private static final String CONFIGURATION = trans.get("edtmotorconfdlg.col.configuration"); SeparationTableModel(Rocket rocket ) { this.rocket = rocket; - + this.rocket.addComponentChangeListener(this); initialize(); } + @Override + public void componentChanged(ComponentChangeEvent e) { + if ( e.isTreeChange() ) { + initialize(); + fireTableStructureChanged(); + } + } + private void initialize() { stages.clear(); Iterator it = rocket.iterator(); From c8f71fcc946213911909bfb89c37e0282388dffd Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Tue, 1 Oct 2013 12:12:30 -0500 Subject: [PATCH 17/47] Refactored the table models into a common base class FlightConfigurableTableModel. --- ...java => FlightConfigurableTableModel.java} | 40 +++---- .../MotorConfigurationPanel.java | 4 +- .../MotorConfigurationTableModel.java | 102 ----------------- .../RecoveryConfigurationPanel.java | 4 +- .../SeparationConfigurationPanel.java | 4 +- .../SeparationTableModel.java | 104 ------------------ 6 files changed, 24 insertions(+), 234 deletions(-) rename swing/src/net/sf/openrocket/gui/main/flightconfigpanel/{RecoveryTableModel.java => FlightConfigurableTableModel.java} (66%) delete mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java delete mode 100644 swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurableTableModel.java similarity index 66% rename from swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java rename to swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurableTableModel.java index cecac53e7..853e959c8 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryTableModel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurableTableModel.java @@ -9,48 +9,44 @@ import javax.swing.table.AbstractTableModel; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.ComponentChangeListener; -import net.sf.openrocket.rocketcomponent.RecoveryDevice; +import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Pair; -class RecoveryTableModel extends AbstractTableModel implements ComponentChangeListener { +public class FlightConfigurableTableModel extends AbstractTableModel implements ComponentChangeListener{ private static final Translator trans = Application.getTranslator(); - - private final Rocket rocket; - - private final List recoveryDevices = new ArrayList(); - private static final String CONFIGURATION = trans.get("edtmotorconfdlg.col.configuration"); + protected final Rocket rocket; + protected final Class clazz; + private final List components = new ArrayList(); - /** - * @param recoveryConfigurationPanel - */ - RecoveryTableModel(Rocket rocket) { + public FlightConfigurableTableModel(Class clazz, Rocket rocket) { + super(); this.rocket = rocket; + this.clazz = clazz; this.rocket.addComponentChangeListener(this); initialize(); } @Override public void componentChanged(ComponentChangeEvent e) { - if ( e.isTreeChange() ) { + if ( e.isMotorChange() || e.isTreeChange() ) { initialize(); fireTableStructureChanged(); } } - - private void initialize() { - recoveryDevices.clear(); + protected void initialize() { + components.clear(); Iterator it = rocket.iterator(); while (it.hasNext()) { RocketComponent c = it.next(); - if (c instanceof RecoveryDevice) { - recoveryDevices.add( (RecoveryDevice) c); + if (clazz.isAssignableFrom(c.getClass())) { + components.add( (T) c); } } } @@ -62,7 +58,7 @@ class RecoveryTableModel extends AbstractTableModel implements ComponentChangeLi @Override public int getColumnCount() { - return recoveryDevices.size() + 1; + return components.size() + 1; } @Override @@ -74,8 +70,8 @@ class RecoveryTableModel extends AbstractTableModel implements ComponentChangeLi } default: { int index = column - 1; - RecoveryDevice d = recoveryDevices.get(index); - return new Pair(id, d); + T d = components.get(index); + return new Pair(id, d); } } } @@ -88,9 +84,9 @@ class RecoveryTableModel extends AbstractTableModel implements ComponentChangeLi } default: { int index = column - 1; - RecoveryDevice d = recoveryDevices.get(index); + T d = components.get(index); return d.toString(); - + } } } 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 de94fc5f5..ae86657a1 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java @@ -34,7 +34,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton; protected JTable configurationTable; - protected MotorConfigurationTableModel configurationTableModel; + protected FlightConfigurableTableModel configurationTableModel; MotorConfigurationPanel(final FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { super(flightConfigurationPanel,rocket); @@ -89,7 +89,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel @Override protected JTable initializeTable() { //// Motor selection table. - configurationTableModel = new MotorConfigurationTableModel(rocket); + configurationTableModel = new FlightConfigurableTableModel(MotorMount.class,rocket); configurationTable = new JTable(configurationTableModel); configurationTable.setCellSelectionEnabled(true); configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java deleted file mode 100644 index 08a079f1c..000000000 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationTableModel.java +++ /dev/null @@ -1,102 +0,0 @@ -package net.sf.openrocket.gui.main.flightconfigpanel; - -import java.util.ArrayList; -import java.util.List; - -import javax.swing.table.AbstractTableModel; - -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; -import net.sf.openrocket.rocketcomponent.ComponentChangeListener; -import net.sf.openrocket.rocketcomponent.MotorMount; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.Pair; - -/** - * The table model for selecting and editing the motor configurations. - */ -class MotorConfigurationTableModel extends AbstractTableModel implements ComponentChangeListener { - - private static final Translator trans = Application.getTranslator(); - - private static final String CONFIGURATION = trans.get("edtmotorconfdlg.col.configuration"); - - private final Rocket rocket; - - private List motorMounts = new ArrayList(); - - - public MotorConfigurationTableModel(Rocket rocket) { - this.rocket = rocket; - this.rocket.addComponentChangeListener(this); - initializeMotorMounts(); - - } - - @Override - public void componentChanged(ComponentChangeEvent e) { - if ( e.isMotorChange() || e.isTreeChange() ) { - initializeMotorMounts(); - fireTableStructureChanged(); - } - } - - private void initializeMotorMounts() { - motorMounts.clear(); - for (RocketComponent c : rocket) { - if (c instanceof MotorMount) { - if (((MotorMount) c).isMotorMount()) { - motorMounts.add((MotorMount) c); - } - } - } - - } - - @Override - public int getColumnCount() { - return motorMounts.size() + 1; - } - - @Override - public int getRowCount() { - return rocket.getFlightConfigurationIDs().length - 1; - } - - @Override - public Object getValueAt(int row, int column) { - String id = getConfiguration(row); - switch (column) { - case 0: { - return id; - } - default: { - int mountIndex = column - 1; - MotorMount mount = motorMounts.get(mountIndex); - return new Pair(id, mount); - } - } - } - - @Override - public String getColumnName(int column) { - switch (column) { - case 0: { - return CONFIGURATION; - } - default: { - int mountIndex = column - 1; - MotorMount mount = motorMounts.get(mountIndex); - return mount.toString(); - } - } - } - - private String getConfiguration(int row) { - String id = rocket.getFlightConfigurationIDs()[row + 1]; - return id; - } - -} \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java index 448d5c092..03055a7fa 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java @@ -34,7 +34,7 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel recoveryTableModel; private JTable recoveryTable; private final JButton selectDeploymentButton; private final JButton resetDeploymentButton; @@ -72,7 +72,7 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel(RecoveryDevice.class, rocket); recoveryTable = new JTable(recoveryTableModel); recoveryTable.setCellSelectionEnabled(true); recoveryTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java index 80d858c44..f4f0b748f 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java @@ -34,7 +34,7 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); private JTable separationTable; - private SeparationTableModel separationTableModel; + private FlightConfigurableTableModel separationTableModel; private final JButton selectSeparationButton; private final JButton resetDeploymentButton; @@ -72,7 +72,7 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel @Override protected JTable initializeTable() { //// Separation selection - separationTableModel = new SeparationTableModel(rocket); + separationTableModel = new FlightConfigurableTableModel(Stage.class, rocket); separationTable = new JTable(separationTableModel); separationTable.setCellSelectionEnabled(true); separationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java deleted file mode 100644 index 10115a18f..000000000 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationTableModel.java +++ /dev/null @@ -1,104 +0,0 @@ -package net.sf.openrocket.gui.main.flightconfigpanel; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import javax.swing.table.AbstractTableModel; - -import net.sf.openrocket.l10n.Translator; -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.startup.Application; -import net.sf.openrocket.util.Pair; - -class SeparationTableModel extends AbstractTableModel implements ComponentChangeListener { - - private static final Translator trans = Application.getTranslator(); - - private final Rocket rocket; - - private final List stages = new ArrayList(); - - private static final String CONFIGURATION = trans.get("edtmotorconfdlg.col.configuration"); - - SeparationTableModel(Rocket rocket ) { - this.rocket = rocket; - this.rocket.addComponentChangeListener(this); - initialize(); - } - - @Override - public void componentChanged(ComponentChangeEvent e) { - if ( e.isTreeChange() ) { - initialize(); - fireTableStructureChanged(); - } - } - - private void initialize() { - stages.clear(); - Iterator it = rocket.iterator(); - { - int stageIndex = -1; - while (it.hasNext()) { - RocketComponent c = it.next(); - if (c instanceof Stage) { - if (stageIndex >= 0) { - stages.add( (Stage) c); - } - stageIndex++; - } - } - } - } - - - @Override - public int getRowCount() { - return rocket.getFlightConfigurationIDs().length - 1; - } - - @Override - public int getColumnCount() { - return stages.size() + 1; - } - @Override - public Object getValueAt(int row, int column) { - String id = getConfiguration(row); - switch (column) { - case 0: { - return id; - } - default: { - int index = column - 1; - Stage d = stages.get(index); - return new Pair(id, d); - } - } - } - - @Override - public String getColumnName(int column) { - switch (column) { - case 0: { - return CONFIGURATION; - } - default: { - int index = column - 1; - Stage d = stages.get(index); - return d.toString(); - - } - } - } - - private String getConfiguration(int row) { - String id = rocket.getFlightConfigurationIDs()[row + 1]; - return id; - } - -} \ No newline at end of file From f433c640e27a1e17469a99b5f118a2230f859d99 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Tue, 1 Oct 2013 13:26:44 -0500 Subject: [PATCH 18/47] Fixed motor table so it only shows motor mounts. Extracted the table cell renderer code into abstract base class. --- .../FlightConfigurablePanel.java | 50 ++++++++++++++++++- .../FlightConfigurableTableModel.java | 11 +++- .../MotorConfigurationPanel.java | 42 ++++++---------- .../RecoveryConfigurationPanel.java | 49 ++++-------------- .../SeparationConfigurationPanel.java | 50 ++++--------------- 5 files changed, 93 insertions(+), 109 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java index bc0540962..a5241cc23 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java @@ -1,17 +1,25 @@ package net.sf.openrocket.gui.main.flightconfigpanel; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; import java.util.EventObject; +import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableCellRenderer; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.formatting.RocketDescriptor; +import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent; +import net.sf.openrocket.rocketcomponent.MotorConfiguration; +import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Pair; @@ -68,7 +76,7 @@ public abstract class FlightConfigurablePanel v = (Pair) value; + String id = v.getU(); + T component = v.getV(); + format(component, id, label ); + return label; + } + } + } + + protected final void shaded(JLabel label) { + GUIUtil.changeFontStyle(label, Font.ITALIC); + label.setForeground(Color.GRAY); + } + + protected final void regular(JLabel label) { + GUIUtil.changeFontStyle(label, Font.PLAIN); + label.setForeground(Color.BLACK); + } + + protected abstract void format( T component, String configId, JLabel label ); + + } + } \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurableTableModel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurableTableModel.java index 853e959c8..14c31fd67 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurableTableModel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurableTableModel.java @@ -40,12 +40,21 @@ public class FlightConfigurableTableModel } } + /** + * Return true if this component should be included in the table. + * @param component + * @return + */ + protected boolean includeComponent( T component ) { + return true; + } + protected void initialize() { components.clear(); Iterator it = rocket.iterator(); while (it.hasNext()) { RocketComponent c = it.next(); - if (clazz.isAssignableFrom(c.getClass())) { + if (clazz.isAssignableFrom(c.getClass()) && includeComponent( (T) c) ) { components.add( (T) c); } } 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 ae86657a1..bb7969229 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java @@ -89,7 +89,14 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel @Override protected JTable initializeTable() { //// Motor selection table. - configurationTableModel = new FlightConfigurableTableModel(MotorMount.class,rocket); + configurationTableModel = new FlightConfigurableTableModel(MotorMount.class,rocket) { + + @Override + protected boolean includeComponent(MotorMount component) { + return component.isMotorMount(); + } + + }; configurationTable = new JTable(configurationTableModel); configurationTable.setCellSelectionEnabled(true); configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -201,34 +208,17 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } - private class MotorTableCellRenderer extends DefaultTableCellRenderer { + private class MotorTableCellRenderer extends FlightConfigurablePanel.FlightConfigurableCellRenderer { + @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (!(c instanceof JLabel)) { - return c; - } - JLabel label = (JLabel) c; - - switch (column) { - case 0: { - label.setText(descriptor.format(rocket, (String) value)); - return label; - } - default: { - Pair v = (Pair) value; - String id = v.getU(); - MotorMount mount = v.getV(); - MotorConfiguration motorConfig = mount.getMotorConfiguration().get(id); - String motorString = getMotorSpecification(mount, motorConfig); - String ignitionString = getIgnitionEventString(id, mount); - label.setText(motorString + " " + ignitionString); - return label; - } - } + protected void format(MotorMount mount, String configId, JLabel label) { + MotorConfiguration motorConfig = mount.getMotorConfiguration().get(configId); + String motorString = getMotorSpecification(mount, motorConfig); + String ignitionString = getIgnitionEventString(configId, mount); + label.setText(motorString + " " + ignitionString); } - + private String getMotorSpecification(MotorMount mount, MotorConfiguration motorConfig) { Motor motor = motorConfig.getMotor(); diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java index 03055a7fa..7e3e460d1 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java @@ -23,6 +23,7 @@ import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration.DeployEvent; +import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.startup.Application; @@ -133,50 +134,18 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel.FlightConfigurableCellRenderer { @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (!(c instanceof JLabel)) { - return c; - } - JLabel label = (JLabel) c; - - switch (column) { - case 0: { - label.setText(descriptor.format(rocket, (String) value)); + protected void format(RecoveryDevice recovery, String configId, JLabel label) { + DeploymentConfiguration deployConfig = recovery.getDeploymentConfiguration().get(configId); + String spec = getDeploymentSpecification(deployConfig); + label.setText(spec); + if (recovery.getDeploymentConfiguration().isDefault(configId)) { + shaded(label); + } else { regular(label); - return label; } - default: { - Pair v = (Pair) value; - String id = v.getU(); - RecoveryDevice recovery = v.getV(); - DeploymentConfiguration deployConfig = recovery.getDeploymentConfiguration().get(id); - String spec = getDeploymentSpecification(deployConfig); - label.setText(spec); - if (recovery.getDeploymentConfiguration().isDefault(id)) { - shaded(label); - } else { - regular(label); - } - break; - } - } - return label; - } - - private void shaded(JLabel label) { - GUIUtil.changeFontStyle(label, Font.ITALIC); - label.setForeground(Color.GRAY); - } - - private void regular(JLabel label) { - GUIUtil.changeFontStyle(label, Font.PLAIN); - label.setForeground(Color.BLACK); } private String getDeploymentSpecification( DeploymentConfiguration config ) { diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java index f4f0b748f..e9fe51c83 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java @@ -21,6 +21,7 @@ import net.sf.openrocket.formatting.RocketDescriptor; import net.sf.openrocket.gui.dialogs.flightconfiguration.SeparationSelectionDialog; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.Stage; import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; @@ -132,51 +133,20 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel resetDeploymentButton.setEnabled(componentSelected); } - private class SeparationTableCellRenderer extends DefaultTableCellRenderer { + private class SeparationTableCellRenderer extends FlightConfigurablePanel.FlightConfigurableCellRenderer { @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (!(c instanceof JLabel)) { - return c; - } - JLabel label = (JLabel) c; - - switch (column) { - case 0: { - label.setText(descriptor.format(rocket, (String) value)); + protected void format(Stage stage, String configId, JLabel label) { + StageSeparationConfiguration sepConfig = stage.getStageSeparationConfiguration().get(configId); + String spec = getSeparationSpecification(sepConfig); + label.setText(spec); + if (stage.getStageSeparationConfiguration().isDefault(configId)) { + shaded(label); + } else { regular(label); - return label; } - default: { - Pair v = (Pair) value; - String id = v.getU(); - Stage stage = v.getV(); - StageSeparationConfiguration sepConfig = stage.getStageSeparationConfiguration().get(id); - String spec = getSeparationSpecification(sepConfig); - label.setText(spec); - if (stage.getStageSeparationConfiguration().isDefault(id)) { - shaded(label); - } else { - regular(label); - } - break; - } - } - return label; - } - - private void shaded(JLabel label) { - GUIUtil.changeFontStyle(label, Font.ITALIC); - label.setForeground(Color.GRAY); - } - - private void regular(JLabel label) { - GUIUtil.changeFontStyle(label, Font.PLAIN); - label.setForeground(Color.BLACK); - } - + private String getSeparationSpecification( StageSeparationConfiguration sepConfig ) { String str; From 60a7014ca32c4045be1e0eb6fa2407d44db0511c Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Tue, 1 Oct 2013 15:37:17 -0500 Subject: [PATCH 19/47] Fix button layout. --- .../flightconfigpanel/FlightConfigurationPanel.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 ec3bdaca5..d3d47e192 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java @@ -1,6 +1,5 @@ package net.sf.openrocket.gui.main.flightconfigpanel; -import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.EventObject; @@ -51,11 +50,11 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe this.document = doc; this.rocket = doc.getRocket(); - JPanel panel = new JPanel(new MigLayout("fill")); + JPanel panel = new JPanel(new MigLayout("fill","[grow][][][][][grow]")); //// Tabs for advanced view. tabs = new JTabbedPane(); - this.add(tabs, "grow, spanx, wrap"); + this.add(tabs, "spanx, grow, wrap"); //// Motor tabs motorConfigurationPanel = new MotorConfigurationPanel(this, rocket); @@ -78,7 +77,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe }); - panel.add(newConfButton); + panel.add(newConfButton,"skip 1,gapright para"); renameConfButton = new JButton(trans.get("edtmotorconfdlg.but.Renameconfiguration")); renameConfButton.addActionListener(new ActionListener() { @@ -88,7 +87,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe configurationChanged(); } }); - panel.add(renameConfButton); + panel.add(renameConfButton,"gapright para"); removeConfButton = new JButton(trans.get("edtmotorconfdlg.but.Removeconfiguration")); removeConfButton.addActionListener(new ActionListener() { @@ -98,7 +97,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe configurationChanged(); } }); - panel.add(removeConfButton); + panel.add(removeConfButton,"gapright para"); copyConfButton = new JButton(trans.get("edtmotorconfdlg.but.Copyconfiguration")); copyConfButton.addActionListener(new ActionListener() { From dca6c31241a0ada09a3ab968382a843a53d101a3 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Tue, 1 Oct 2013 15:47:28 -0500 Subject: [PATCH 20/47] Move ownership of the table to the base class. --- .../FlightConfigurablePanel.java | 33 +++++++------------ .../MotorConfigurationPanel.java | 18 ++++------ .../RecoveryConfigurationPanel.java | 16 +++------ .../SeparationConfigurationPanel.java | 16 +++------ 4 files changed, 28 insertions(+), 55 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java index a5241cc23..4b65690fc 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java @@ -32,12 +32,13 @@ public abstract class FlightConfigurablePanel 1) ? 1 : 0; } for( int row = 0; row < table.getRowCount(); row++ ) { String rowId = rocket.getFlightConfigurationIDs()[row + 1]; @@ -78,7 +75,7 @@ public abstract class FlightConfigurablePanel selectedComponent = (Pair) tableValue; return selectedComponent.getV(); @@ -129,12 +120,12 @@ public abstract class FlightConfigurablePanel selectedComponent = (Pair) tableValue; return selectedComponent.getU(); 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 bb7969229..d799661ba 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java @@ -33,13 +33,12 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton; - protected JTable configurationTable; protected FlightConfigurableTableModel configurationTableModel; MotorConfigurationPanel(final FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { super(flightConfigurationPanel,rocket); - JScrollPane scroll = new JScrollPane(configurationTable); + JScrollPane scroll = new JScrollPane(table); this.add(scroll, "grow, wrap"); //// Select motor @@ -97,7 +96,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } }; - configurationTable = new JTable(configurationTableModel); + JTable configurationTable = new JTable(configurationTableModel); configurationTable.setCellSelectionEnabled(true); configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); configurationTable.setDefaultRenderer(Object.class, new MotorTableCellRenderer()); @@ -106,7 +105,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel @Override public void mouseClicked(MouseEvent e) { updateButtonState(); - int selectedColumn = configurationTable.getSelectedColumn(); + int selectedColumn = table.getSelectedColumn(); if (e.getClickCount() == 2) { if (selectedColumn > 0) { selectMotor(); @@ -117,17 +116,12 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel return configurationTable; } - @Override - protected JTable getTable() { - return configurationTable; - } - public void fireTableDataChanged() { - int selected = configurationTable.getSelectedRow(); + int selected = table.getSelectedRow(); configurationTableModel.fireTableDataChanged(); if (selected >= 0) { - selected = Math.min(selected, configurationTable.getRowCount() - 1); - configurationTable.getSelectionModel().setSelectionInterval(selected, selected); + selected = Math.min(selected, table.getRowCount() - 1); + table.getSelectionModel().setSelectionInterval(selected, selected); } updateButtonState(); } diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java index 7e3e460d1..135f74eb6 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java @@ -36,7 +36,6 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel recoveryTableModel; - private JTable recoveryTable; private final JButton selectDeploymentButton; private final JButton resetDeploymentButton; @@ -44,7 +43,7 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel(RecoveryDevice.class, rocket); - recoveryTable = new JTable(recoveryTableModel); + JTable recoveryTable = new JTable(recoveryTableModel); recoveryTable.setCellSelectionEnabled(true); recoveryTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); recoveryTable.addMouseListener(new MouseAdapter() { @@ -93,17 +92,12 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel= 0) { - selected = Math.min(selected, recoveryTable.getRowCount() - 1); - recoveryTable.getSelectionModel().setSelectionInterval(selected, selected); + selected = Math.min(selected, table.getRowCount() - 1); + table.getSelectionModel().setSelectionInterval(selected, selected); } updateButtonState(); } diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java index e9fe51c83..b3a67f6b3 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java @@ -34,7 +34,6 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel static final Translator trans = Application.getTranslator(); private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); - private JTable separationTable; private FlightConfigurableTableModel separationTableModel; private final JButton selectSeparationButton; private final JButton resetDeploymentButton; @@ -43,7 +42,7 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel SeparationConfigurationPanel(FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { super(flightConfigurationPanel,rocket); - JScrollPane scroll = new JScrollPane(separationTable); + JScrollPane scroll = new JScrollPane(table); this.add(scroll, "span, grow, wrap"); //// Select deployment @@ -74,7 +73,7 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel protected JTable initializeTable() { //// Separation selection separationTableModel = new FlightConfigurableTableModel(Stage.class, rocket); - separationTable = new JTable(separationTableModel); + JTable separationTable = new JTable(separationTableModel); separationTable.setCellSelectionEnabled(true); separationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); separationTable.addMouseListener(new MouseAdapter() { @@ -92,17 +91,12 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel return separationTable; } - @Override - protected JTable getTable() { - return separationTable; - } - public void fireTableDataChanged() { - int selected = separationTable.getSelectedRow(); + int selected = table.getSelectedRow(); separationTableModel.fireTableDataChanged(); if (selected >= 0) { - selected = Math.min(selected, separationTable.getRowCount() - 1); - separationTable.getSelectionModel().setSelectionInterval(selected, selected); + selected = Math.min(selected, table.getRowCount() - 1); + table.getSelectionModel().setSelectionInterval(selected, selected); } updateButtonState(); } From 6abcda7abd80645ea75e2404d961268c4bde4827 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Tue, 1 Oct 2013 16:12:05 -0500 Subject: [PATCH 21/47] Don't show the sustainer stage in the stage configuration panel. --- .../flightconfigpanel/SeparationConfigurationPanel.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java index b3a67f6b3..64b6ed7b8 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/SeparationConfigurationPanel.java @@ -21,6 +21,7 @@ import net.sf.openrocket.formatting.RocketDescriptor; import net.sf.openrocket.gui.dialogs.flightconfiguration.SeparationSelectionDialog; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.Stage; @@ -72,7 +73,13 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel @Override protected JTable initializeTable() { //// Separation selection - separationTableModel = new FlightConfigurableTableModel(Stage.class, rocket); + separationTableModel = new FlightConfigurableTableModel(Stage.class, rocket) { + @Override + protected boolean includeComponent(Stage component) { + return component.getStageNumber() > 0; + } + + }; JTable separationTable = new JTable(separationTableModel); separationTable.setCellSelectionEnabled(true); separationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); From ed0444a909aac74787abf89a126c1453991d4139 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 2 Oct 2013 08:55:55 -0500 Subject: [PATCH 22/47] Move buttons to top. --- .../FlightConfigurationPanel.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) 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 d3d47e192..de8001ead 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java @@ -45,16 +45,15 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe } public FlightConfigurationPanel(OpenRocketDocument doc) { - super(new MigLayout("fill")); + super(new MigLayout("fill","[grow][][][][][grow]")); this.document = doc; this.rocket = doc.getRocket(); - JPanel panel = new JPanel(new MigLayout("fill","[grow][][][][][grow]")); + //JPanel panel = new JPanel(new MigLayout("fill","[grow][][][][][grow]")); //// Tabs for advanced view. tabs = new JTabbedPane(); - this.add(tabs, "spanx, grow, wrap"); //// Motor tabs motorConfigurationPanel = new MotorConfigurationPanel(this, rocket); @@ -77,7 +76,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe }); - panel.add(newConfButton,"skip 1,gapright para"); + this.add(newConfButton,"skip 1,gapright para"); renameConfButton = new JButton(trans.get("edtmotorconfdlg.but.Renameconfiguration")); renameConfButton.addActionListener(new ActionListener() { @@ -87,7 +86,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe configurationChanged(); } }); - panel.add(renameConfButton,"gapright para"); + this.add(renameConfButton,"gapright para"); removeConfButton = new JButton(trans.get("edtmotorconfdlg.but.Removeconfiguration")); removeConfButton.addActionListener(new ActionListener() { @@ -97,7 +96,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe configurationChanged(); } }); - panel.add(removeConfButton,"gapright para"); + this.add(removeConfButton,"gapright para"); copyConfButton = new JButton(trans.get("edtmotorconfdlg.but.Copyconfiguration")); copyConfButton.addActionListener(new ActionListener() { @@ -107,10 +106,12 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe configurationChanged(); } }); - panel.add(copyConfButton, "wrap para"); - - this.add(panel, "growx"); + this.add(copyConfButton, "wrap"); + updateButtonState(); + + this.add(tabs, "spanx, grow, wrap rel"); + this.rocket.getDefaultConfiguration().addChangeListener(this); } From 59e75d5c83ba7a9cff9122e37ec770c399060e9a Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 2 Oct 2013 12:51:59 -0500 Subject: [PATCH 23/47] Rework BodyTubeConfig and InnerTubeConfig removing the motor configuration stuff. The motormount checkbox and default ignition configuration is now on the General tab. Removed ThicknessRingComponentConfig since that was only used by InnerTubeConfig. Moved the radialPanel from RingComponentConfig to InnerTubeConfig for now. --- .../gui/configdialog/BodyTubeConfig.java | 12 +- .../gui/configdialog/InnerTubeConfig.java | 368 ++++++++++++++---- .../gui/configdialog/LaunchLugConfig.java | 2 - .../gui/configdialog/MotorConfig.java | 136 +------ .../gui/configdialog/RingComponentConfig.java | 74 ---- .../ThicknessRingComponentConfig.java | 34 -- 6 files changed, 295 insertions(+), 331 deletions(-) delete mode 100644 swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java diff --git a/swing/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java index c91871a1a..2e5dad565 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java @@ -16,13 +16,13 @@ import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.material.Material; import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; public class BodyTubeConfig extends RocketComponentConfig { - private MotorConfig motorConfigPane = null; private DoubleModel maxLength; private static final Translator trans = Application.getTranslator(); @@ -98,7 +98,10 @@ public class BodyTubeConfig extends RocketComponentConfig { check.setText(trans.get("BodyTubecfg.checkbox.Filled")); panel.add(check, "skip, span 2, wrap"); + MotorConfig motorConfig = new MotorConfig((MotorMount)c); + panel.add(motorConfig,"spanx, growx"); + //// Material panel.add(materialPanel(new JPanel(new MigLayout()), Material.Type.BULK), "cell 4 0, gapleft paragraph, aligny 0%, spany"); @@ -106,10 +109,7 @@ public class BodyTubeConfig extends RocketComponentConfig { //// General and General properties tabbedPane.insertTab(trans.get("BodyTubecfg.tab.General"), null, panel, trans.get("BodyTubecfg.tab.Generalproperties"), 0); - motorConfigPane = new MotorConfig((BodyTube) c); - //// Motor and Motor mount configuration - tabbedPane.insertTab(trans.get("BodyTubecfg.tab.Motor"), null, motorConfigPane, - trans.get("BodyTubecfg.tab.Motormountconf"), 1); + tabbedPane.setSelectedIndex(0); @@ -118,8 +118,6 @@ public class BodyTubeConfig extends RocketComponentConfig { @Override public void updateFields() { super.updateFields(); - if (motorConfigPane != null) - motorConfigPane.updateFields(); } } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java index b3b96ed61..4d981d7b3 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java @@ -1,28 +1,12 @@ package net.sf.openrocket.gui.configdialog; -import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.document.OpenRocketDocument; -import net.sf.openrocket.gui.Resettable; -import net.sf.openrocket.gui.SpinnerEditor; -import net.sf.openrocket.gui.adaptors.DoubleModel; -import net.sf.openrocket.gui.components.BasicSlider; -import net.sf.openrocket.gui.components.UnitSelector; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.rocketcomponent.ClusterConfiguration; -import net.sf.openrocket.rocketcomponent.Clusterable; -import net.sf.openrocket.rocketcomponent.InnerTube; -import net.sf.openrocket.rocketcomponent.MotorMount; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.unit.UnitGroup; -import net.sf.openrocket.util.BugException; -import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.StateChangeListener; - -import javax.swing.*; -import javax.swing.border.BevelBorder; -import java.awt.*; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; @@ -31,40 +15,258 @@ import java.awt.geom.Ellipse2D; import java.util.EventObject; import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SwingUtilities; +import javax.swing.border.BevelBorder; -public class InnerTubeConfig extends ThicknessRingComponentConfig { +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.gui.Resettable; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.adaptors.EnumModel; +import net.sf.openrocket.gui.components.BasicSlider; +import net.sf.openrocket.gui.components.DescriptionArea; +import net.sf.openrocket.gui.components.UnitSelector; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.material.Material; +import net.sf.openrocket.rocketcomponent.ClusterConfiguration; +import net.sf.openrocket.rocketcomponent.Clusterable; +import net.sf.openrocket.rocketcomponent.EngineBlock; +import net.sf.openrocket.rocketcomponent.InnerTube; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.RingComponent; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.BugException; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.StateChangeListener; + + +public class InnerTubeConfig extends RocketComponentConfig { private static final Translator trans = Application.getTranslator(); - - + + public InnerTubeConfig(OpenRocketDocument d, RocketComponent c) { super(d, c); + + //// General and General properties + JPanel panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::][]", "")); + DoubleModel m; + JSpinner spin; + DoubleModel od = null; + + //// Outer diameter + panel.add(new JLabel(trans.get("ThicknessRingCompCfg.tab.Outerdiam"))); + + //// OuterRadius + od = new DoubleModel(component, "OuterRadius", 2, UnitGroup.UNITS_LENGTH, 0); + // Diameter = 2*Radius + + spin = new JSpinner(od.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin, "growx"); + + panel.add(new UnitSelector(od), "growx"); + panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)), "w 100lp, wrap"); + + if (od.isAutomaticAvailable()) { + JCheckBox check = new JCheckBox(od.getAutomaticAction()); + //// Automatic + check.setText(trans.get("ringcompcfg.Automatic")); + panel.add(check, "skip, span 2, wrap"); + } + + //// Inner diameter + panel.add(new JLabel(trans.get("ThicknessRingCompCfg.tab.Innerdiam"))); + + //// InnerRadius + m = new DoubleModel(component, "InnerRadius", 2, UnitGroup.UNITS_LENGTH, 0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin, "growx"); + + panel.add(new UnitSelector(m), "growx"); + if (od == null) + panel.add(new BasicSlider(m.getSliderModel(0, 0.04, 0.2)), "w 100lp, wrap"); + else + panel.add(new BasicSlider(m.getSliderModel(new DoubleModel(0), od)), + "w 100lp, wrap"); + + if (m.isAutomaticAvailable()) { + JCheckBox check = new JCheckBox(m.getAutomaticAction()); + //// Automatic + check.setText(trans.get("ringcompcfg.Automatic")); + panel.add(check, "skip, span 2, wrap"); + } + + + //// Wall thickness + panel.add(new JLabel(trans.get("ThicknessRingCompCfg.tab.Wallthickness"))); + + //// Thickness + m = new DoubleModel(component, "Thickness", UnitGroup.UNITS_LENGTH, 0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin, "growx"); + + panel.add(new UnitSelector(m), "growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.01)), "w 100lp, wrap"); + + + //// Inner tube length + panel.add(new JLabel(trans.get("ThicknessRingCompCfg.tab.Length"))); + + //// Length + m = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin, "growx"); + + panel.add(new UnitSelector(m), "growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 1.0)), "w 100lp, wrap"); + + + //// Position + + //// Position relative to: + panel.add(new JLabel(trans.get("ringcompcfg.Positionrelativeto"))); + + JComboBox combo = new JComboBox( + new EnumModel(component, "RelativePosition", + new RocketComponent.Position[] { + RocketComponent.Position.TOP, + RocketComponent.Position.MIDDLE, + RocketComponent.Position.BOTTOM, + RocketComponent.Position.ABSOLUTE + })); + panel.add(combo, "spanx 3, growx, wrap"); + + //// plus + panel.add(new JLabel(trans.get("ringcompcfg.plus")), "right"); + + //// PositionValue + m = new DoubleModel(component, "PositionValue", UnitGroup.UNITS_LENGTH); + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + panel.add(spin, "growx"); + + panel.add(new UnitSelector(m), "growx"); + panel.add(new BasicSlider(m.getSliderModel( + new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE), + new DoubleModel(component.getParent(), "Length"))), + "w 100lp, wrap"); + + MotorConfig motorConfig = new MotorConfig((MotorMount)c); - JPanel tab; + panel.add(motorConfig,"spanx, growx"); - tab = new MotorConfig((MotorMount) c); - //// Motor and Motor mount configuration - tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Motor"), null, tab, - trans.get("InnerTubeCfg.tab.ttip.Motor"), 1); - - tab = clusterTab(); + //// Material + panel.add(materialPanel(new JPanel(new MigLayout()), Material.Type.BULK), + "cell 4 0, gapleft paragraph, aligny 0%, spany"); + + tabbedPane.insertTab(trans.get("ThicknessRingCompCfg.tab.General"), null, panel, + trans.get("ThicknessRingCompCfg.tab.Generalprop"), 0); + + JPanel tab = clusterTab(); //// Cluster and Cluster configuration tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Cluster"), null, tab, trans.get("InnerTubeCfg.tab.ttip.Cluster"), 2); - + tab = positionTab(); //// Radial position tabbedPane.insertTab(trans.get("InnerTubeCfg.tab.Radialpos"), null, tab, trans.get("InnerTubeCfg.tab.ttip.Radialpos"), 3); - + tabbedPane.setSelectedIndex(0); } - - + + protected JPanel positionTab() { + JPanel panel = new JPanel(new MigLayout("align 20% 20%, gap rel unrel", + "[][65lp::][30lp::]", "")); + + //// Radial position + JLabel l = new JLabel(trans.get("ringcompcfg.Radialdistance")); + //// Distance from the rocket centerline + l.setToolTipText(trans.get("ringcompcfg.Distancefrom")); + panel.add(l); + + DoubleModel m = new DoubleModel(component, "RadialPosition", UnitGroup.UNITS_LENGTH, 0); + + JSpinner spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + //// Distance from the rocket centerline + spin.setToolTipText(trans.get("ringcompcfg.Distancefrom")); + panel.add(spin, "growx"); + + panel.add(new UnitSelector(m), "growx"); + BasicSlider bs = new BasicSlider(m.getSliderModel(0, 0.1, 1.0)); + //// Distance from the rocket centerline + bs.setToolTipText(trans.get("ringcompcfg.Distancefrom")); + panel.add(bs, "w 100lp, wrap"); + + + //// Radial direction + l = new JLabel(trans.get("ringcompcfg.Radialdirection")); + //// The radial direction from the rocket centerline + l.setToolTipText(trans.get("ringcompcfg.radialdirectionfrom")); + panel.add(l); + + m = new DoubleModel(component, "RadialDirection", UnitGroup.UNITS_ANGLE); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + //// The radial direction from the rocket centerline + spin.setToolTipText(trans.get("ringcompcfg.radialdirectionfrom")); + panel.add(spin, "growx"); + + panel.add(new UnitSelector(m), "growx"); + bs = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)); + //// The radial direction from the rocket centerline + bs.setToolTipText(trans.get("ringcompcfg.radialdirectionfrom")); + panel.add(bs, "w 100lp, wrap"); + + + //// Reset button + JButton button = new JButton(trans.get("ringcompcfg.but.Reset")); + //// Reset the component to the rocket centerline + button.setToolTipText(trans.get("ringcompcfg.but.Resetcomponant")); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ((RingComponent) component).setRadialDirection(0.0); + ((RingComponent) component).setRadialPosition(0.0); + } + }); + panel.add(button, "spanx, right, wrap para"); + + + DescriptionArea note = new DescriptionArea(3); + //// Note: An inner tube will not affect the aerodynamics of the rocket even if it is located outside of the body tube. + note.setText(trans.get("ringcompcfg.note.desc")); + panel.add(note, "spanx, growx"); + + + return panel; + } + + private JPanel clusterTab() { JPanel panel = new JPanel(new MigLayout()); - + JPanel subPanel = new JPanel(new MigLayout()); - + // Cluster type selection //// Select cluster configuration: subPanel.add(new JLabel(trans.get("InnerTubeCfg.lbl.Selectclustercfg")), "spanx, wrap"); @@ -72,12 +274,12 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig { // JPanel clusterSelection = new ClusterSelectionPanel((InnerTube)component); // clusterSelection.setBackground(Color.blue); // subPanel.add(clusterSelection); - + panel.add(subPanel); - + subPanel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]")); - + // Tube separation scale //// Tube separation: JLabel l = new JLabel(trans.get("InnerTubeCfg.lbl.TubeSep")); @@ -85,18 +287,18 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig { l.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep")); subPanel.add(l); DoubleModel dm = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0); - + JSpinner spin = new JSpinner(dm.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); //// The separation of the tubes, 1.0 = touching each other spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep")); subPanel.add(spin, "growx"); - + BasicSlider bs = new BasicSlider(dm.getSliderModel(0, 1, 4)); //// The separation of the tubes, 1.0 = touching each other bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep")); subPanel.add(bs, "skip,w 100lp, wrap"); - + // Rotation: l = new JLabel(trans.get("InnerTubeCfg.lbl.Rotation")); //// Rotation angle of the cluster configuration @@ -104,19 +306,19 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig { subPanel.add(l); dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE, -Math.PI, Math.PI); - + spin = new JSpinner(dm.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); //// Rotation angle of the cluster configuration spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation")); subPanel.add(spin, "growx"); - + subPanel.add(new UnitSelector(dm), "growx"); bs = new BasicSlider(dm.getSliderModel(-Math.PI, 0, Math.PI)); //// Rotation angle of the cluster configuration bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation")); subPanel.add(bs, "w 100lp, wrap para"); - + // Split button @@ -139,19 +341,19 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig { throw new BugException("Inconsistent state: component=" + component + " parent=" + parent + " parent.children=" + parent.getChildren()); } - + InnerTube tube = (InnerTube) component; if (tube.getClusterCount() <= 1) return; - + document.addUndoPosition("Split cluster"); - + Coordinate[] coords = { Coordinate.NUL }; coords = component.shiftCoordinates(coords); parent.removeChild(index); for (int i = 0; i < coords.length; i++) { - InnerTube copy = InnerTube.makeIndividualClusterComponent(coords[i], component.getName() + " #" + (i + 1), component); - + InnerTube copy = InnerTube.makeIndividualClusterComponent(coords[i], component.getName() + " #" + (i + 1), component); + parent.addChild(copy, index + i); } } @@ -159,7 +361,7 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig { } }); subPanel.add(split, "spanx, split 2, gapright para, sizegroup buttons, right"); - + // Reset button ///// Reset settings @@ -174,9 +376,9 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig { } }); subPanel.add(reset, "sizegroup buttons, right"); - + panel.add(subPanel, "grow"); - + return panel; } @@ -188,35 +390,35 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig { class ClusterSelectionPanel extends JPanel { private static final int BUTTON_SIZE = 50; private static final int MOTOR_DIAMETER = 10; - + private static final Color SELECTED_COLOR = Color.RED; private static final Color UNSELECTED_COLOR = Color.WHITE; private static final Color MOTOR_FILL_COLOR = Color.GREEN; private static final Color MOTOR_BORDER_COLOR = Color.BLACK; - + public ClusterSelectionPanel(Clusterable component) { super(new MigLayout("gap 0 0", "[" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!]", "[" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!]")); - + for (int i = 0; i < ClusterConfiguration.CONFIGURATIONS.length; i++) { ClusterConfiguration config = ClusterConfiguration.CONFIGURATIONS[i]; - + JComponent button = new ClusterButton(component, config); if (i % 4 == 3) add(button, "wrap"); else add(button); } - + } - - + + private class ClusterButton extends JPanel implements StateChangeListener, MouseListener, - Resettable { + Resettable { private Clusterable component; private ClusterConfiguration config; - + public ClusterButton(Clusterable c, ClusterConfiguration config) { component = c; this.config = config; @@ -228,81 +430,81 @@ class ClusterSelectionPanel extends JPanel { component.addChangeListener(this); addMouseListener(this); } - - + + @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; Rectangle area = g2.getClipBounds(); - + if (component.getClusterConfiguration() == config) g2.setColor(SELECTED_COLOR); else g2.setColor(UNSELECTED_COLOR); - + g2.fillRect(area.x, area.y, area.width, area.height); - + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - + List points = config.getPoints(); Ellipse2D.Float circle = new Ellipse2D.Float(); for (int i = 0; i < points.size() / 2; i++) { double x = points.get(i * 2); double y = points.get(i * 2 + 1); - + double px = BUTTON_SIZE / 2 + x * MOTOR_DIAMETER; double py = BUTTON_SIZE / 2 - y * MOTOR_DIAMETER; circle.setFrameFromCenter(px, py, px + MOTOR_DIAMETER / 2, py + MOTOR_DIAMETER / 2); - + g2.setColor(MOTOR_FILL_COLOR); g2.fill(circle); g2.setColor(MOTOR_BORDER_COLOR); g2.draw(circle); } } - - + + @Override public void stateChanged(EventObject e) { repaint(); } - - + + @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { component.setClusterConfiguration(config); } } - + @Override public void mouseEntered(MouseEvent e) { } - + @Override public void mouseExited(MouseEvent e) { } - + @Override public void mousePressed(MouseEvent e) { } - + @Override public void mouseReleased(MouseEvent e) { } - - + + @Override public void resetModel() { component.removeChangeListener(this); removeMouseListener(this); } } - + } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java index 07248d113..6f6ff0a32 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java @@ -154,8 +154,6 @@ public class LaunchLugConfig extends RocketComponentConfig { @Override public void updateFields() { super.updateFields(); - if (motorConfigPane != null) - motorConfigPane.updateFields(); } } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java index 03882cce5..ca4538e38 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/MotorConfig.java @@ -1,20 +1,16 @@ package net.sf.openrocket.gui.configdialog; +import java.awt.Color; import java.awt.Component; import java.awt.Container; -import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import javax.swing.JButton; +import javax.swing.BorderFactory; import javax.swing.JCheckBox; import javax.swing.JComboBox; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSpinner; -import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -23,40 +19,27 @@ import net.sf.openrocket.gui.SpinnerEditor; import net.sf.openrocket.gui.adaptors.BooleanModel; import net.sf.openrocket.gui.adaptors.DoubleModel; import net.sf.openrocket.gui.adaptors.EnumModel; -import net.sf.openrocket.gui.adaptors.FlightConfigurationModel; import net.sf.openrocket.gui.components.BasicSlider; import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.UnitSelector; -import net.sf.openrocket.gui.dialogs.flightconfiguration.FlightConfigurationDialog; -import net.sf.openrocket.gui.dialogs.motor.MotorChooserDialog; import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.motor.Motor; -import net.sf.openrocket.motor.ThrustCurveMotor; -import net.sf.openrocket.rocketcomponent.Configuration; import net.sf.openrocket.rocketcomponent.IgnitionConfiguration; -import net.sf.openrocket.rocketcomponent.MotorConfiguration; import net.sf.openrocket.rocketcomponent.MotorMount; -import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; public class MotorConfig extends JPanel { - private final Rocket rocket; private final MotorMount mount; - private final Configuration configuration; - private JPanel panel; - private JLabel motorLabel; private static final Translator trans = Application.getTranslator(); public MotorConfig(MotorMount motorMount) { super(new MigLayout("fill")); - this.rocket = ((RocketComponent) motorMount).getRocket(); this.mount = motorMount; - this.configuration = ((RocketComponent) motorMount).getRocket().getDefaultConfiguration(); + this.setBorder( BorderFactory.createLineBorder(Color.BLACK,1) ); BooleanModel model; model = new BooleanModel(motorMount, "MotorMount"); @@ -65,58 +48,10 @@ public class MotorConfig extends JPanel { check.setText(trans.get("MotorCfg.checkbox.compmotormount")); this.add(check, "wrap"); - - panel = new JPanel(new MigLayout("fill")); + final JPanel panel = new JPanel(new MigLayout("fill")); this.add(panel, "grow, wrap"); - // Motor configuration selector - //// Motor configuration: - panel.add(new JLabel(trans.get("MotorCfg.lbl.Flightcfg")), "shrink"); - - JComboBox combo = new JComboBox(new FlightConfigurationModel(configuration)); - panel.add(combo, "growx"); - combo.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - updateFields(); - } - - }); - - //// New button - JButton button = new JButton(trans.get("MotorCfg.but.New")); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String id = rocket.newFlightConfigurationID(); - configuration.setFlightConfigurationID(id); - } - }); - panel.add(button, ""); - - //// Edit button - button = new JButton(trans.get("MotorCfg.but.FlightcfgEdit")); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JDialog configDialog = new FlightConfigurationDialog(rocket, SwingUtilities.windowForComponent(MotorConfig.this)); - configDialog.show(); - } - }); - panel.add(button, "wrap unrel"); - - - // Current motor: - panel.add(new JLabel(trans.get("MotorCfg.lbl.Currentmotor")), "shrink"); - - motorLabel = new JLabel(); - motorLabel.setFont(motorLabel.getFont().deriveFont(Font.BOLD)); - updateFields(); - panel.add(motorLabel, "wrap unrel"); - - - // Overhang //// Motor overhang: panel.add(new JLabel(trans.get("MotorCfg.lbl.Motoroverhang"))); @@ -137,7 +72,7 @@ public class MotorConfig extends JPanel { panel.add(new JLabel(trans.get("MotorCfg.lbl.Ignitionat") + " " + CommonStrings.dagger), ""); IgnitionConfiguration ignitionConfig = mount.getIgnitionConfiguration().getDefault(); - combo = new JComboBox(new EnumModel(ignitionConfig, "IgnitionEvent")); + JComboBox combo = new JComboBox(new EnumModel(ignitionConfig, "IgnitionEvent")); panel.add(combo, "growx, wrap"); // ... and delay @@ -176,51 +111,6 @@ public class MotorConfig extends JPanel { } - // Select etc. buttons - //// Select motor - button = new JButton(trans.get("MotorCfg.but.Selectmotor")); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String id = configuration.getFlightConfigurationID(); - - MotorChooserDialog dialog = new MotorChooserDialog(mount.getMotor(id), - mount.getMotorDelay(id), mount.getMotorMountDiameter(), - SwingUtilities.getWindowAncestor(MotorConfig.this)); - dialog.setVisible(true); - Motor m = dialog.getSelectedMotor(); - double d = dialog.getSelectedDelay(); - - if (m != null) { - if (id == null) { - id = rocket.newFlightConfigurationID(); - configuration.setFlightConfigurationID(id); - } - MotorConfiguration config = new MotorConfiguration(); - config.setMotor(m); - config.setEjectionDelay(d); - mount.getMotorConfiguration().set(id, config); - } - updateFields(); - } - }); - panel.add(button, "span, split, growx"); - - //// Remove motor - button = new JButton(trans.get("MotorCfg.but.Removemotor")); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - mount.getMotorConfiguration().resetDefault(configuration.getFlightConfigurationID()); - updateFields(); - } - }); - panel.add(button, "growx, wrap"); - - - - - // Set enabled status setDeepEnabled(panel, motorMount.isMotorMount()); @@ -233,22 +123,6 @@ public class MotorConfig extends JPanel { } - public void updateFields() { - String id = configuration.getFlightConfigurationID(); - Motor m = mount.getMotor(id); - if (m == null) { - //// None - motorLabel.setText(trans.get("MotorCfg.lbl.motorLabel")); - } else { - String str = ""; - if (m instanceof ThrustCurveMotor) - str = ((ThrustCurveMotor) m).getManufacturer() + " "; - str += m.getDesignation(mount.getMotorDelay(id)); - motorLabel.setText(str); - } - } - - private static void setDeepEnabled(Component component, boolean enabled) { component.setEnabled(enabled); if (component instanceof Container) { diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java index 803633004..10b6e8483 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java @@ -1,10 +1,6 @@ package net.sf.openrocket.gui.configdialog; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; @@ -22,7 +18,6 @@ import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.material.Material; import net.sf.openrocket.rocketcomponent.EngineBlock; -import net.sf.openrocket.rocketcomponent.RingComponent; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; @@ -171,73 +166,4 @@ public class RingComponentConfig extends RocketComponentConfig { } - protected JPanel positionTab() { - JPanel panel = new JPanel(new MigLayout("align 20% 20%, gap rel unrel", - "[][65lp::][30lp::]", "")); - - //// Radial position - JLabel l = new JLabel(trans.get("ringcompcfg.Radialdistance")); - //// Distance from the rocket centerline - l.setToolTipText(trans.get("ringcompcfg.Distancefrom")); - panel.add(l); - - DoubleModel m = new DoubleModel(component, "RadialPosition", UnitGroup.UNITS_LENGTH, 0); - - JSpinner spin = new JSpinner(m.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - //// Distance from the rocket centerline - spin.setToolTipText(trans.get("ringcompcfg.Distancefrom")); - panel.add(spin, "growx"); - - panel.add(new UnitSelector(m), "growx"); - BasicSlider bs = new BasicSlider(m.getSliderModel(0, 0.1, 1.0)); - //// Distance from the rocket centerline - bs.setToolTipText(trans.get("ringcompcfg.Distancefrom")); - panel.add(bs, "w 100lp, wrap"); - - - //// Radial direction - l = new JLabel(trans.get("ringcompcfg.Radialdirection")); - //// The radial direction from the rocket centerline - l.setToolTipText(trans.get("ringcompcfg.radialdirectionfrom")); - panel.add(l); - - m = new DoubleModel(component, "RadialDirection", UnitGroup.UNITS_ANGLE); - - spin = new JSpinner(m.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - //// The radial direction from the rocket centerline - spin.setToolTipText(trans.get("ringcompcfg.radialdirectionfrom")); - panel.add(spin, "growx"); - - panel.add(new UnitSelector(m), "growx"); - bs = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI)); - //// The radial direction from the rocket centerline - bs.setToolTipText(trans.get("ringcompcfg.radialdirectionfrom")); - panel.add(bs, "w 100lp, wrap"); - - - //// Reset button - JButton button = new JButton(trans.get("ringcompcfg.but.Reset")); - //// Reset the component to the rocket centerline - button.setToolTipText(trans.get("ringcompcfg.but.Resetcomponant")); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ((RingComponent) component).setRadialDirection(0.0); - ((RingComponent) component).setRadialPosition(0.0); - } - }); - panel.add(button, "spanx, right, wrap para"); - - - DescriptionArea note = new DescriptionArea(3); - //// Note: An inner tube will not affect the aerodynamics of the rocket even if it is located outside of the body tube. - note.setText(trans.get("ringcompcfg.note.desc")); - panel.add(note, "spanx, growx"); - - - return panel; - } - } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java deleted file mode 100644 index 3013b7c97..000000000 --- a/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.sf.openrocket.gui.configdialog; - - -import javax.swing.JPanel; - -import net.sf.openrocket.document.OpenRocketDocument; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.startup.Application; - - - -public class ThicknessRingComponentConfig extends RingComponentConfig { - private static final Translator trans = Application.getTranslator(); - - public ThicknessRingComponentConfig(OpenRocketDocument d, RocketComponent c) { - super(d, c); - - JPanel tab; - - //// Outer diameter: - //// Inner diameter: - //// Wall thickness: - //// Length: - tab = generalTab(trans.get("ThicknessRingCompCfg.tab.Outerdiam"), - trans.get("ThicknessRingCompCfg.tab.Innerdiam"), - trans.get("ThicknessRingCompCfg.tab.Wallthickness"), trans.get("ThicknessRingCompCfg.tab.Length")); - //// General and General properties - tabbedPane.insertTab(trans.get("ThicknessRingCompCfg.tab.General"), null, tab, - trans.get("ThicknessRingCompCfg.tab.Generalprop"), 0); - tabbedPane.setSelectedIndex(0); - } - -} \ No newline at end of file From 444035442f35c67ca806c08abcdaddef3f3a81f7 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 2 Oct 2013 13:45:54 -0500 Subject: [PATCH 24/47] scroll to selection on opening. --- .../motor/thrustcurve/ThrustCurveMotorSelectionPanel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index bd24a5ca5..555c49a38 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -352,6 +352,8 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec panel.add(hideUsedBox, "gapleft para, spanx, growx, wrap para"); } + scrollSelectionVisible(); + //// Hide very similar thrust curves hideSimilarBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideSimilar")); GUIUtil.changeFontSize(hideSimilarBox, -1); From e44c87029b9ab2787715335c04f917b778c4ac10 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 2 Oct 2013 20:48:13 -0500 Subject: [PATCH 25/47] Fix eclipse classpath after moving the lib-test stuff. --- core/.classpath | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/.classpath b/core/.classpath index 19534aee6..753ef1030 100644 --- a/core/.classpath +++ b/core/.classpath @@ -10,20 +10,20 @@ - - - - - - - + + + + + + + From 32817f2cb1c2f302f092de3c9735ff5403d2d876 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Thu, 3 Oct 2013 21:00:50 -0500 Subject: [PATCH 26/47] Reworked the ThrustCurveMotorSelectionPanel so a single instance can be reused for different searches. This means the MotorRowFilter has an easier time remembering its settings on a per rocket basis. --- .../MotorConfigurationPanel.java | 4 +- .../gui/dialogs/motor/MotorChooserDialog.java | 11 ++- .../motor/thrustcurve/MotorRowFilter.java | 7 +- .../ThrustCurveMotorSelectionPanel.java | 85 +++++++++++-------- 4 files changed, 67 insertions(+), 40 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java index 1cb7178f6..b47d910f7 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java @@ -43,9 +43,11 @@ public class MotorConfigurationPanel extends JPanel { private final MotorConfigurationTableModel configurationTableModel; private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton; + private final MotorChooserDialog dialog; MotorConfigurationPanel(FlightConfigurationDialog flightConfigurationDialog, Rocket rocket) { super(new MigLayout("fill")); + dialog = new MotorChooserDialog(flightConfigurationDialog); this.flightConfigurationDialog = flightConfigurationDialog; this.rocket = rocket; @@ -207,7 +209,7 @@ public class MotorConfigurationPanel extends JPanel { MotorConfiguration config = mount.getMotorConfiguration().get(id); - MotorChooserDialog dialog = new MotorChooserDialog(mount, id, flightConfigurationDialog); + dialog.setMotorMountAndConfig(mount, id); dialog.setVisible(true); Motor m = dialog.getSelectedMotor(); double d = dialog.getSelectedDelay(); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java index 898964af0..a6718116f 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java @@ -26,14 +26,18 @@ public class MotorChooserDialog extends JDialog implements CloseableDialog { private boolean okClicked = false; private static final Translator trans = Application.getTranslator(); - public MotorChooserDialog(MotorMount mount, String currentConfig, Window owner) { + this(owner); + setMotorMountAndConfig(mount, currentConfig); + } + + public MotorChooserDialog(Window owner) { super(owner, trans.get("MotorChooserDialog.title"), Dialog.ModalityType.APPLICATION_MODAL); JPanel panel = new JPanel(new MigLayout("fill")); - selectionPanel = new ThrustCurveMotorSelectionPanel(mount, currentConfig); + selectionPanel = new ThrustCurveMotorSelectionPanel(); panel.add(selectionPanel, "grow, wrap para"); @@ -74,6 +78,9 @@ public class MotorChooserDialog extends JDialog implements CloseableDialog { selectionPanel.setCloseableDialog(this); } + public void setMotorMountAndConfig( MotorMount mount, String currentConfig ) { + selectionPanel.setMotorMountAndConfig(mount, currentConfig); + } /** * Return the motor selected by this chooser dialog, or null if the selection has been aborted. diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java index 82d7483c9..a7961ec1d 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java @@ -30,7 +30,7 @@ class MotorRowFilter extends RowFilter { // configuration data used in the filter process private final ThrustCurveMotorDatabaseModel model; - private final Double diameter; + private Double diameter; private List usedMotors = new ArrayList(); // things which can be changed to modify filter behavior @@ -50,9 +50,12 @@ class MotorRowFilter extends RowFilter { // List of ImpulseClasses to exclude. private List excludedImpulseClass = new ArrayList(); - public MotorRowFilter(MotorMount mount, ThrustCurveMotorDatabaseModel model) { + public MotorRowFilter(ThrustCurveMotorDatabaseModel model) { super(); this.model = model; + } + + public void setMotorMount( MotorMount mount ) { if (mount != null) { this.diameter = mount.getMotorMountDiameter(); for (MotorConfiguration m : mount.getMotorConfiguration()) { diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index 555c49a38..07d2ccc54 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -98,20 +98,21 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec private static final ThrustCurveMotorComparator MOTOR_COMPARATOR = new ThrustCurveMotorComparator(); - private final List database; + private List database; private CloseableDialog dialog = null; final ThrustCurveMotorDatabaseModel model; private final JTable table; private final TableRowSorter sorter; + private final MotorRowFilter rowFilter; private final JCheckBox hideSimilarBox; private final JTextField searchField; String[] searchTerms = new String[0]; - + private final StyledLabel diameterLabel; private final JLabel curveSelectionLabel; private final JComboBox curveSelectionBox; private final DefaultComboBoxModel curveSelectionModel; @@ -140,7 +141,11 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec private ThrustCurveMotorSet selectedMotorSet; private double selectedDelay; - + public ThrustCurveMotorSelectionPanel(MotorMount mount, String currentConfig) { + this(); + setMotorMountAndConfig( mount, currentConfig ); + + } /** * Sole constructor. * @@ -148,42 +153,17 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec * @param delay the currently selected ejection charge delay. * @param diameter the diameter of the motor mount. */ - public ThrustCurveMotorSelectionPanel(MotorMount mount, String currentConfig) { + public ThrustCurveMotorSelectionPanel() { super(new MigLayout("fill", "[grow][]")); - double diameter = 0; - if (currentConfig != null && mount != null) { - MotorConfiguration motorConf = mount.getMotorConfiguration().get(currentConfig); - selectedMotor = (ThrustCurveMotor) motorConf.getMotor(); - selectedDelay = motorConf.getEjectionDelay(); - diameter = mount.getMotorMountDiameter(); - } - // Construct the database (adding the current motor if not in the db already) List db; db = Application.getThrustCurveMotorSetDatabase().getMotorSets(); - // If current motor is not found in db, add a new ThrustCurveMotorSet containing it - if (selectedMotor != null) { - for (ThrustCurveMotorSet motorSet : db) { - if (motorSet.getMotors().contains(selectedMotor)) { - selectedMotorSet = motorSet; - break; - } - } - if (selectedMotorSet == null) { - db = new ArrayList(db); - ThrustCurveMotorSet extra = new ThrustCurveMotorSet(); - extra.addMotor(selectedMotor); - selectedMotorSet = extra; - db.add(extra); - Collections.sort(db); - } - } database = db; model = new ThrustCurveMotorDatabaseModel(database); - final MotorRowFilter rowFilter = new MotorRowFilter(mount, model); + rowFilter = new MotorRowFilter(model); //// GUI @@ -369,8 +349,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec // Motor mount diameter label //// Motor mount diameter: - label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Motormountdia") + " " + - UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter)); + diameterLabel = new StyledLabel(); panel.add(label, "gapright 30lp, spanx, split"); // Vertical split @@ -490,9 +469,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec scrollpane = new JScrollPane(comment); panel.add(scrollpane, "spanx, growx, wrap para"); - - - // Thrust curve plot chart = ChartFactory.createXYLineChart( null, // title @@ -569,6 +545,45 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } + public void setMotorMountAndConfig( MotorMount mount, String currentConfig ) { + double diameter = 0; + + if (currentConfig != null && mount != null) { + MotorConfiguration motorConf = mount.getMotorConfiguration().get(currentConfig); + selectedMotor = (ThrustCurveMotor) motorConf.getMotor(); + selectedDelay = motorConf.getEjectionDelay(); + diameter = mount.getMotorMountDiameter(); + } + + // If current motor is not found in db, add a new ThrustCurveMotorSet containing it + if (selectedMotor != null) { + for (ThrustCurveMotorSet motorSet : database) { + if (motorSet.getMotors().contains(selectedMotor)) { + selectedMotorSet = motorSet; + break; + } + } + if (selectedMotorSet == null) { + database = new ArrayList(database); + ThrustCurveMotorSet extra = new ThrustCurveMotorSet(); + extra.addMotor(selectedMotor); + selectedMotorSet = extra; + database.add(extra); + Collections.sort(database); + } + } + + updateData(); + setDelays(true); + + diameterLabel.setText(trans.get("TCMotorSelPan.lbl.Motormountdia") + " " + + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(0)); + + rowFilter.setMotorMount(mount); + scrollSelectionVisible(); + + } + @Override public Motor getSelectedMotor() { return selectedMotor; From 71e3e76493038baed8e85a49f9a305c5939b91d1 Mon Sep 17 00:00:00 2001 From: soupwizard Date: Thu, 3 Oct 2013 21:25:55 -0700 Subject: [PATCH 27/47] Swing build depends on core jar already being built, added that dependency. --- build.xml | 172 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 101 insertions(+), 71 deletions(-) diff --git a/build.xml b/build.xml index 8a746fad8..b568e33ea 100644 --- a/build.xml +++ b/build.xml @@ -1,91 +1,121 @@ - + + + + + + + + + - + + + + + + + - - + + + + + + + + + + + + + + - - - + - - + + + - - - - - Checking project for FIXMEs. - - - - - - - - - - - - - - - - - - - - - - CRITICAL TODOs exist in project: + + + + + + + + + + + + Checking project for FIXMEs. + + + + + + + + + + + + + + + + + + + + + + CRITICAL TODOs exist in project: ${criticaltodos} - No critical TODOs in project. - - - - - - - - Checking project for non-ASCII characters. - - - - - - - - - - - - - - - - - - - - - - Non-ASCII characters exist in project: + No critical TODOs in project. + + + + + + + Checking project for non-ASCII characters. + + + + + + + + + + + + + + + + + + + + + + Non-ASCII characters exist in project: ${nonascii} - No non-ASCII characters in project. - - - + No non-ASCII characters in project. + + From e6755e0e8475b5a00b16d4cb11994ea1de809a8c Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Fri, 4 Oct 2013 20:36:30 -0500 Subject: [PATCH 28/47] Rework the thrustcurve motor filter and information. They now share tabs on the MotorSelectionPanel. Added Minimum Motor diameter to the motor filter. --- core/resources/l10n/messages.properties | 1 + ...erPopupMenu.java => MotorFilterPanel.java} | 538 ++++++------ .../thrustcurve/MotorInformationPanel.java | 315 +++++++ .../motor/thrustcurve/MotorRowFilter.java | 20 +- .../ThrustCurveMotorSelectionPanel.java | 824 ++++++------------ 5 files changed, 888 insertions(+), 810 deletions(-) rename swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/{MotorFilterPopupMenu.java => MotorFilterPanel.java} (61%) create mode 100644 swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 267f1e13d..55f9ece1a 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1088,6 +1088,7 @@ StorageOptChooser.lbl.Saveopt = Save options TCMotorSelPan.lbl.Selrocketmotor = Select rocket motor: TCMotorSelPan.checkbox.hideSimilar = Hide very similar thrust curves TCMotorSelPan.checkbox.hideUsed = Hide motors already used in the mount +TCMotorSelPan.btn.details = Show Details TCMotorSelPan.btn.filter = Filter Motors TCMotorSelPan.SHOW_DESCRIPTIONS.desc1 = Show all motors TCMotorSelPan.SHOW_DESCRIPTIONS.desc2 = Show motors with diameter less than that of the motor mount diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java similarity index 61% rename from swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java rename to swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java index b124c6036..3c706c430 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPopupMenu.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java @@ -1,251 +1,287 @@ -package net.sf.openrocket.gui.dialogs.motor.thrustcurve; - -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.JButton; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.border.TitledBorder; -import javax.swing.event.ListDataEvent; -import javax.swing.event.ListDataListener; - -import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.gui.util.CheckList; -import net.sf.openrocket.gui.util.GUIUtil; -import net.sf.openrocket.gui.util.SwingPreferences; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.motor.Manufacturer; -import net.sf.openrocket.startup.Application; - -import com.itextpdf.text.Font; - -public abstract class MotorFilterPopupMenu extends JPopupMenu { - - private static final Translator trans = Application.getTranslator(); - - private final CheckList manufacturerCheckList; - - private final CheckList impulseCheckList; - - private final MotorRowFilter filter; - - private int showMode = SHOW_ALL; - - private static final int SHOW_ALL = 0; - private static final int SHOW_SMALLER = 1; - private static final int SHOW_EXACT = 2; - private static final int SHOW_MAX = 2; - - - public MotorFilterPopupMenu(Collection allManufacturers, MotorRowFilter filter ) { - - this.filter = filter; - - showMode = Application.getPreferences().getChoice(net.sf.openrocket.startup.Preferences.MOTOR_DIAMETER_FILTER, MotorFilterPopupMenu.SHOW_MAX, MotorFilterPopupMenu.SHOW_EXACT); - List unselectedManusFromPreferences = ((SwingPreferences) Application.getPreferences()).getExcludedMotorManufacturers(); - - // Manufacturer selection - JPanel sub = new JPanel(new MigLayout("fill")); - TitledBorder border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.MANUFACTURER")); - GUIUtil.changeFontStyle(border, Font.BOLD); - sub.setBorder(border); - - JPanel root = new JPanel(new MigLayout("fill", "[grow]")); - root.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); - - List manufacturers = new ArrayList(); - for (Manufacturer m : allManufacturers) { - manufacturers.add(m); - } - - Collections.sort(manufacturers, new Comparator() { - @Override - public int compare(Manufacturer o1, Manufacturer o2) { - return o1.getSimpleName().compareTo( o2.getSimpleName()); - } - - }); - - manufacturerCheckList = new CheckList.Builder().build(); - manufacturerCheckList.setData(manufacturers); - - manufacturerCheckList.setUncheckedItems(unselectedManusFromPreferences); - filter.setExcludedManufacturers(unselectedManusFromPreferences); - manufacturerCheckList.getModel().addListDataListener( new ListDataListener() { - @Override - public void intervalAdded(ListDataEvent e) { - } - @Override - public void intervalRemoved(ListDataEvent e) { - } - - @Override - public void contentsChanged(ListDataEvent e) { - MotorFilterPopupMenu.this.filter.setExcludedManufacturers( manufacturerCheckList.getUncheckedItems() ); - onSelectionChanged(); - } - }); - - sub.add(new JScrollPane(manufacturerCheckList.getList()), "grow,wrap"); - - JButton clearMotors = new JButton(trans.get("TCMotorSelPan.btn.checkNone")); - clearMotors.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - MotorFilterPopupMenu.this.manufacturerCheckList.clearAll(); - - } - }); - - sub.add(clearMotors,"split 2"); - - JButton selectMotors = new JButton(trans.get("TCMotorSelPan.btn.checkAll")); - selectMotors.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - MotorFilterPopupMenu.this.manufacturerCheckList.checkAll(); - - } - }); - - sub.add(selectMotors,"wrap"); - - root.add(sub,"grow, wrap"); - - // Impulse selection - sub = new JPanel(new MigLayout("fill")); - border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.TOTAL_IMPULSE")); - GUIUtil.changeFontStyle(border, Font.BOLD); - sub.setBorder(border); - - impulseCheckList = new CheckList.Builder().build(); - impulseCheckList.setData(Arrays.asList(ImpulseClass.values())); - impulseCheckList.checkAll(); - impulseCheckList.getModel().addListDataListener( new ListDataListener() { - @Override - public void intervalAdded(ListDataEvent e) { - } - @Override - public void intervalRemoved(ListDataEvent e) { - } - @Override - public void contentsChanged(ListDataEvent e) { - MotorFilterPopupMenu.this.filter.setExcludedImpulseClasses( impulseCheckList.getUncheckedItems() ); - onSelectionChanged(); - } - - }); - - sub.add(new JScrollPane(impulseCheckList.getList()), "grow,wrap"); - - JButton clearImpulse = new JButton(trans.get("TCMotorSelPan.btn.checkNone")); - clearImpulse.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - MotorFilterPopupMenu.this.impulseCheckList.clearAll(); - - } - }); - sub.add(clearImpulse,"split 2"); - - JButton selectImpulse = new JButton(trans.get("TCMotorSelPan.btn.checkAll")); - selectImpulse.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - MotorFilterPopupMenu.this.impulseCheckList.checkAll(); - - } - }); - sub.add(selectImpulse,"wrap"); - - root.add(sub,"grow, wrap"); - - // Diameter selection - - sub = new JPanel(new MigLayout("fill")); - border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.DIAMETER")); - GUIUtil.changeFontStyle(border, Font.BOLD); - sub.setBorder(border); - - JRadioButton showAllDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc1") ); - showAllDiametersButton.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showMode = SHOW_ALL; - MotorFilterPopupMenu.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.ALL); - onSelectionChanged(); - } - }); - showAllDiametersButton.setSelected( showMode == SHOW_ALL); - sub.add(showAllDiametersButton, "growx,wrap"); - - JRadioButton showSmallerDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2") ); - showSmallerDiametersButton.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showMode = SHOW_SMALLER; - MotorFilterPopupMenu.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.SMALLER); - onSelectionChanged(); - } - }); - showSmallerDiametersButton.setSelected( showMode == SHOW_SMALLER); - sub.add(showSmallerDiametersButton, "growx,wrap"); - - JRadioButton showExactDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3") ); - showExactDiametersButton.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showMode = SHOW_EXACT; - MotorFilterPopupMenu.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.EXACT); - onSelectionChanged(); - } - }); - showExactDiametersButton.setSelected( showMode == SHOW_EXACT ); - sub.add(showExactDiametersButton, "growx,wrap"); - - root.add(sub, "grow,wrap"); - ButtonGroup comboGroup = new ButtonGroup(); - comboGroup.add( showAllDiametersButton ); - comboGroup.add( showSmallerDiametersButton ); - comboGroup.add( showExactDiametersButton ); - - - // Close button - JButton closeButton = new JButton(trans.get("TCMotorSelPan.btn.close")); - closeButton.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - MotorFilterPopupMenu.this.onClose(); - } - - }); - root.add(closeButton, "split 2"); - - this.add(root); - - } - - public void onClose() { - - ((SwingPreferences) Application.getPreferences()).setExcludedMotorManufacturers(filter.getExcludedManufacturers()); - - Application.getPreferences().putChoice("MotorDiameterMatch", showMode ); - - setVisible(false); - } - - public abstract void onSelectionChanged(); - -} +package net.sf.openrocket.gui.dialogs.motor.thrustcurve; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.border.TitledBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.SpinnerEditor; +import net.sf.openrocket.gui.adaptors.DoubleModel; +import net.sf.openrocket.gui.components.BasicSlider; +import net.sf.openrocket.gui.components.UnitSelector; +import net.sf.openrocket.gui.util.CheckList; +import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.gui.util.SwingPreferences; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.motor.Manufacturer; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.unit.UnitGroup; + +import com.itextpdf.text.Font; + +public abstract class MotorFilterPanel extends JPanel { + + private static final Translator trans = Application.getTranslator(); + + private final CheckList manufacturerCheckList; + + private final CheckList impulseCheckList; + + private final MotorRowFilter filter; + private final TitledBorder diameterTitleBorder; + private final DoubleModel mountDiameter = new DoubleModel(1); + + private int showMode = SHOW_ALL; + + private static final int SHOW_ALL = 0; + private static final int SHOW_SMALLER = 1; + private static final int SHOW_EXACT = 2; + private static final int SHOW_MAX = 2; + + public MotorFilterPanel(Collection allManufacturers, MotorRowFilter filter ) { + super(new MigLayout("fill", "[grow]")); + this.filter = filter; + + showMode = Application.getPreferences().getChoice(net.sf.openrocket.startup.Preferences.MOTOR_DIAMETER_FILTER, MotorFilterPanel.SHOW_MAX, MotorFilterPanel.SHOW_EXACT); + switch( showMode ) { + case SHOW_ALL: + filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.ALL); + break; + case SHOW_EXACT: + filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.EXACT); + break; + case SHOW_SMALLER: + filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.SMALLER); + break; + } + List unselectedManusFromPreferences = ((SwingPreferences) Application.getPreferences()).getExcludedMotorManufacturers(); + filter.setExcludedManufacturers(unselectedManusFromPreferences); + + // Manufacturer selection + JPanel sub = new JPanel(new MigLayout("fill")); + TitledBorder border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.MANUFACTURER")); + GUIUtil.changeFontStyle(border, Font.BOLD); + sub.setBorder(border); + + this.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); + + List manufacturers = new ArrayList(); + for (Manufacturer m : allManufacturers) { + manufacturers.add(m); + } + + Collections.sort(manufacturers, new Comparator() { + @Override + public int compare(Manufacturer o1, Manufacturer o2) { + return o1.getSimpleName().compareTo( o2.getSimpleName()); + } + + }); + + manufacturerCheckList = new CheckList.Builder().build(); + manufacturerCheckList.setData(manufacturers); + + manufacturerCheckList.setUncheckedItems(unselectedManusFromPreferences); + manufacturerCheckList.getModel().addListDataListener( new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + } + @Override + public void intervalRemoved(ListDataEvent e) { + } + + @Override + public void contentsChanged(ListDataEvent e) { + MotorFilterPanel.this.filter.setExcludedManufacturers( manufacturerCheckList.getUncheckedItems() ); + onSelectionChanged(); + } + }); + + sub.add(new JScrollPane(manufacturerCheckList.getList()), "grow,wrap"); + + JButton clearMotors = new JButton(trans.get("TCMotorSelPan.btn.checkNone")); + clearMotors.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPanel.this.manufacturerCheckList.clearAll(); + + } + }); + + sub.add(clearMotors,"split 2"); + + JButton selectMotors = new JButton(trans.get("TCMotorSelPan.btn.checkAll")); + selectMotors.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPanel.this.manufacturerCheckList.checkAll(); + + } + }); + + sub.add(selectMotors,"wrap"); + + this.add(sub,"grow, wrap"); + + // Impulse selection + sub = new JPanel(new MigLayout("fill")); + border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.TOTAL_IMPULSE")); + GUIUtil.changeFontStyle(border, Font.BOLD); + sub.setBorder(border); + + impulseCheckList = new CheckList.Builder().build(); + impulseCheckList.setData(Arrays.asList(ImpulseClass.values())); + impulseCheckList.checkAll(); + impulseCheckList.getModel().addListDataListener( new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + } + @Override + public void intervalRemoved(ListDataEvent e) { + } + @Override + public void contentsChanged(ListDataEvent e) { + MotorFilterPanel.this.filter.setExcludedImpulseClasses( impulseCheckList.getUncheckedItems() ); + onSelectionChanged(); + } + + }); + + sub.add(new JScrollPane(impulseCheckList.getList()), "grow,wrap"); + + JButton clearImpulse = new JButton(trans.get("TCMotorSelPan.btn.checkNone")); + clearImpulse.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPanel.this.impulseCheckList.clearAll(); + + } + }); + sub.add(clearImpulse,"split 2"); + + JButton selectImpulse = new JButton(trans.get("TCMotorSelPan.btn.checkAll")); + selectImpulse.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPanel.this.impulseCheckList.checkAll(); + + } + }); + sub.add(selectImpulse,"wrap"); + + this.add(sub,"grow, wrap"); + + // Diameter selection + + sub = new JPanel(new MigLayout("fill")); + diameterTitleBorder = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.DIAMETER")); + GUIUtil.changeFontStyle(diameterTitleBorder, Font.BOLD); + sub.setBorder(diameterTitleBorder); + + JRadioButton showAllDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc1") ); + showAllDiametersButton.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showMode = SHOW_ALL; + MotorFilterPanel.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.ALL); + saveMotorDiameterMatchPrefence(); + onSelectionChanged(); + } + }); + showAllDiametersButton.setSelected( showMode == SHOW_ALL); + sub.add(showAllDiametersButton, "growx,wrap"); + + JRadioButton showSmallerDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2") ); + showSmallerDiametersButton.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showMode = SHOW_SMALLER; + MotorFilterPanel.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.SMALLER); + saveMotorDiameterMatchPrefence(); + onSelectionChanged(); + } + }); + showSmallerDiametersButton.setSelected( showMode == SHOW_SMALLER); + sub.add(showSmallerDiametersButton, "growx,wrap"); + + JRadioButton showExactDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3") ); + showExactDiametersButton.addActionListener( new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showMode = SHOW_EXACT; + MotorFilterPanel.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.EXACT); + saveMotorDiameterMatchPrefence(); + onSelectionChanged(); + } + }); + showExactDiametersButton.setSelected( showMode == SHOW_EXACT ); + sub.add(showExactDiametersButton, "growx,wrap"); + ButtonGroup comboGroup = new ButtonGroup(); + comboGroup.add( showAllDiametersButton ); + comboGroup.add( showSmallerDiametersButton ); + comboGroup.add( showExactDiametersButton ); + + { + sub.add( new JLabel("Minimum diameter"), "split 4"); + final DoubleModel minDiameter = new DoubleModel(0, UnitGroup.UNITS_MOTOR_DIMENSIONS, 0, .2); + minDiameter.addChangeListener( new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + MotorFilterPanel.this.filter.setMinimumDiameter(minDiameter.getValue()); + onSelectionChanged(); + } + }); + JSpinner spin = new JSpinner(minDiameter.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin, "growx"); + + sub.add(new UnitSelector(minDiameter)); + sub.add(new BasicSlider(minDiameter.getSliderModel(0,0.5, mountDiameter)), "w 100lp, wrap"); + } + this.add(sub, "grow,wrap"); + + } + + public void setMotorMount( MotorMount mount ) { + filter.setMotorMount(mount); + onSelectionChanged(); + if ( mount == null ) { + // Disable diameter controls? + diameterTitleBorder.setTitle(trans.get("TCurveMotorCol.DIAMETER")); + mountDiameter.setValue(1.0); + } else { + mountDiameter.setValue(mount.getMotorMountDiameter()); + diameterTitleBorder.setTitle(trans.get("TCurveMotorCol.DIAMETER") + " " + + trans.get("TCMotorSelPan.lbl.Motormountdia") + " " + + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(mount.getMotorMountDiameter())); + + } + } + + private void saveMotorDiameterMatchPrefence() { + Application.getPreferences().putChoice("MotorDiameterMatch", showMode ); + } + + public abstract void onSelectionChanged(); + +} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java new file mode 100644 index 000000000..8e5fb2ebb --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java @@ -0,0 +1,315 @@ +package net.sf.openrocket.gui.dialogs.motor.thrustcurve; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Font; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JLayeredPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.gui.util.Icons; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.unit.UnitGroup; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.title.TextTitle; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +class MotorInformationPanel extends JPanel { + + private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50; + private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12; + + private static final Color NO_COMMENT_COLOR = Color.GRAY; + private static final Color WITH_COMMENT_COLOR = Color.BLACK; + + private static final Translator trans = Application.getTranslator(); + + // Motors in set + private List selectedMotorSet; + // Selected motor + private ThrustCurveMotor selectedMotor; + + private final JLabel totalImpulseLabel; + private final JLabel classificationLabel; + private final JLabel avgThrustLabel; + private final JLabel maxThrustLabel; + private final JLabel burnTimeLabel; + private final JLabel launchMassLabel; + private final JLabel emptyMassLabel; + private final JLabel dataPointsLabel; + private final JLabel digestLabel; + + private final JTextArea comment; + private final Font noCommentFont; + private final Font withCommentFont; + + private final JFreeChart chart; + private final ChartPanel chartPanel; + private final JLabel zoomIcon; + + public MotorInformationPanel() { + super(new MigLayout("fill")); + + // Thrust curve info + //// Total impulse: + { + this.add(new JLabel(trans.get("TCMotorSelPan.lbl.Totalimpulse"))); + totalImpulseLabel = new JLabel(); + this.add(totalImpulseLabel, "split"); + + classificationLabel = new JLabel(); + classificationLabel.setEnabled(false); // Gray out + this.add(classificationLabel, "gapleft unrel, wrap"); + + //// Avg. thrust: + this.add(new JLabel(trans.get("TCMotorSelPan.lbl.Avgthrust"))); + avgThrustLabel = new JLabel(); + this.add(avgThrustLabel, "wrap"); + + //// Max. thrust: + this.add(new JLabel(trans.get("TCMotorSelPan.lbl.Maxthrust"))); + maxThrustLabel = new JLabel(); + this.add(maxThrustLabel, "wrap"); + + //// Burn time: + this.add(new JLabel(trans.get("TCMotorSelPan.lbl.Burntime"))); + burnTimeLabel = new JLabel(); + this.add(burnTimeLabel, "wrap"); + + //// Launch mass: + this.add(new JLabel(trans.get("TCMotorSelPan.lbl.Launchmass"))); + launchMassLabel = new JLabel(); + this.add(launchMassLabel, "wrap"); + + //// Empty mass: + this.add(new JLabel(trans.get("TCMotorSelPan.lbl.Emptymass"))); + emptyMassLabel = new JLabel(); + this.add(emptyMassLabel, "wrap"); + + //// Data points: + this.add(new JLabel(trans.get("TCMotorSelPan.lbl.Datapoints"))); + dataPointsLabel = new JLabel(); + this.add(dataPointsLabel, "wrap para"); + + if (System.getProperty("openrocket.debug.motordigest") != null) { + //// Digest: + this.add(new JLabel(trans.get("TCMotorSelPan.lbl.Digest"))); + digestLabel = new JLabel(); + this.add(digestLabel, "w :300:, wrap para"); + } else { + digestLabel = null; + } + + + comment = new JTextArea(5, 5); + GUIUtil.changeFontSize(comment, -2); + withCommentFont = comment.getFont(); + noCommentFont = withCommentFont.deriveFont(Font.ITALIC); + comment.setLineWrap(true); + comment.setWrapStyleWord(true); + comment.setEditable(false); + JScrollPane scrollpane = new JScrollPane(comment); + this.add(scrollpane, "spanx, growx, wrap para"); + } + + // Thrust curve plot + { + chart = ChartFactory.createXYLineChart( + null, // title + null, // xAxisLabel + null, // yAxisLabel + null, // dataset + PlotOrientation.VERTICAL, + false, // legend + false, // tooltips + false // urls + ); + + // Add the data and formatting to the plot + XYPlot plot = chart.getXYPlot(); + + changeLabelFont(plot.getRangeAxis(), -2); + changeLabelFont(plot.getDomainAxis(), -2); + + //// Thrust curve: + chart.setTitle(new TextTitle(trans.get("TCMotorSelPan.title.Thrustcurve"), this.getFont())); + chart.setBackgroundPaint(this.getBackground()); + plot.setBackgroundPaint(Color.WHITE); + plot.setDomainGridlinePaint(Color.LIGHT_GRAY); + plot.setRangeGridlinePaint(Color.LIGHT_GRAY); + + chartPanel = new ChartPanel(chart, + false, // properties + false, // save + false, // print + false, // zoom + false); // tooltips + chartPanel.setMouseZoomable(false); + chartPanel.setPopupMenu(null); + chartPanel.setMouseWheelEnabled(false); + chartPanel.setRangeZoomable(false); + chartPanel.setDomainZoomable(false); + + chartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + chartPanel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (selectedMotor == null || selectedMotorSet == null) + return; + if (e.getButton() == MouseEvent.BUTTON1) { + // Open plot dialog + ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(selectedMotorSet, + selectedMotorSet.indexOf(selectedMotor), + SwingUtilities.getWindowAncestor(MotorInformationPanel.this)); + plotDialog.setVisible(true); + } + } + }); + + JLayeredPane layer = new CustomLayeredPane(); + + layer.setBorder(BorderFactory.createLineBorder(Color.BLUE)); + + layer.add(chartPanel, (Integer) 0); + + zoomIcon = new JLabel(Icons.ZOOM_IN); + zoomIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + layer.add(zoomIcon, (Integer) 1); + + this.add(layer, "width 300:300:, height 180:180:, grow, spanx"); + } + } + + public void clearData() { + totalImpulseLabel.setText(""); + totalImpulseLabel.setToolTipText(null); + classificationLabel.setText(""); + classificationLabel.setToolTipText(null); + avgThrustLabel.setText(""); + maxThrustLabel.setText(""); + burnTimeLabel.setText(""); + launchMassLabel.setText(""); + emptyMassLabel.setText(""); + dataPointsLabel.setText(""); + if (digestLabel != null) { + digestLabel.setText(""); + } + setComment(""); + chart.getXYPlot().setDataset(new XYSeriesCollection()); + } + + public void updateData( List motors, ThrustCurveMotor selectedMotor ) { + + this.selectedMotorSet = motors; + this.selectedMotor = selectedMotor; + + // Update thrust curve data + double impulse = selectedMotor.getTotalImpulseEstimate(); + MotorClass mc = MotorClass.getMotorClass(impulse); + totalImpulseLabel.setText(UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(impulse)); + classificationLabel.setText("(" + mc.getDescription(impulse) + ")"); + totalImpulseLabel.setToolTipText(mc.getClassDescription()); + classificationLabel.setToolTipText(mc.getClassDescription()); + + avgThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit( + selectedMotor.getAverageThrustEstimate())); + maxThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit( + selectedMotor.getMaxThrustEstimate())); + burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit( + selectedMotor.getBurnTimeEstimate())); + launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit( + selectedMotor.getLaunchCG().weight)); + emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit( + selectedMotor.getEmptyCG().weight)); + dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1)); + if (digestLabel != null) { + digestLabel.setText(selectedMotor.getDigest()); + } + + setComment(selectedMotor.getDescription()); + + // Update the plot + XYPlot plot = chart.getXYPlot(); + final int index = motors.indexOf(selectedMotor); + + XYSeriesCollection dataset = new XYSeriesCollection(); + for (int i = 0; i < motors.size(); i++) { + ThrustCurveMotor m = motors.get(i); + + //// Thrust + XYSeries series = new XYSeries(trans.get("TCMotorSelPan.title.Thrust") + " (" + i + ")"); + double[] time = m.getTimePoints(); + double[] thrust = m.getThrustPoints(); + + for (int j = 0; j < time.length; j++) { + series.add(time[j], thrust[j]); + } + + dataset.addSeries(series); + + boolean selected = (i == index); + plot.getRenderer().setSeriesStroke(i, new BasicStroke(selected ? 3 : 1)); + plot.getRenderer().setSeriesPaint(i, ThrustCurveMotorSelectionPanel.getColor(i)); + } + + plot.setDataset(dataset); + + } + + private void setComment(String s) { + s = s.trim(); + if (s.length() == 0) { + //// No description available. + comment.setText(trans.get("TCMotorSelPan.noDescription")); + comment.setFont(noCommentFont); + comment.setForeground(NO_COMMENT_COLOR); + } else { + comment.setText(s); + comment.setFont(withCommentFont); + comment.setForeground(WITH_COMMENT_COLOR); + } + comment.setCaretPosition(0); + } + + void changeLabelFont(ValueAxis axis, float size) { + Font font = axis.getTickLabelFont(); + font = font.deriveFont(font.getSize2D() + size); + axis.setTickLabelFont(font); + } + + /** + * Custom layered pane that sets the bounds of the components on every layout. + */ + public class CustomLayeredPane extends JLayeredPane { + @Override + public void doLayout() { + synchronized (getTreeLock()) { + int w = getWidth(); + int h = getHeight(); + chartPanel.setBounds(0, 0, w, h); + zoomIcon.setBounds(w - ZOOM_ICON_POSITION_NEGATIVE_X, ZOOM_ICON_POSITION_POSITIVE_Y, 50, 50); + } + } + } + +} \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java index a7961ec1d..568dd31e1 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java @@ -35,7 +35,10 @@ class MotorRowFilter extends RowFilter { // things which can be changed to modify filter behavior - // Collection of strings which match text in the moto + // Limit motors based on minimum diameter + private Double minimumDiameter; + + // Collection of strings which match text in the motor private List searchTerms = Collections. emptyList(); // Limit motors based on diameter of the motor mount @@ -76,6 +79,14 @@ class MotorRowFilter extends RowFilter { } } + Double getMinimumDiameter() { + return minimumDiameter; + } + + void setMinimumDiameter(Double minimumDiameter) { + this.minimumDiameter = minimumDiameter; + } + DiameterFilterControl getDiameterControl() { return diameterControl; } @@ -130,6 +141,13 @@ class MotorRowFilter extends RowFilter { } private boolean filterByDiameter(ThrustCurveMotorSet m) { + + if ( minimumDiameter != null ) { + if ( m.getDiameter() <= minimumDiameter - 0.0015 ) { + return false; + } + } + if (diameter == null) { return true; } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index 07d2ccc54..83b5e5b62 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -1,9 +1,7 @@ package net.sf.openrocket.gui.dialogs.motor.thrustcurve; -import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; -import java.awt.Cursor; import java.awt.Font; import java.awt.Paint; import java.awt.Rectangle; @@ -20,26 +18,24 @@ import java.util.List; import java.util.Set; import java.util.prefs.Preferences; -import javax.swing.BorderFactory; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; -import javax.swing.JLayeredPane; import javax.swing.JList; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSeparator; +import javax.swing.JTabbedPane; import javax.swing.JTable; -import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.RowSorter; import javax.swing.SortOrder; -import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; @@ -50,11 +46,9 @@ import javax.swing.table.TableRowSorter; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.database.motor.ThrustCurveMotorSet; import net.sf.openrocket.gui.components.StyledLabel; -import net.sf.openrocket.gui.components.StyledLabel.Style; import net.sf.openrocket.gui.dialogs.motor.CloseableDialog; import net.sf.openrocket.gui.dialogs.motor.MotorSelector; import net.sf.openrocket.gui.util.GUIUtil; -import net.sf.openrocket.gui.util.Icons; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.Markers; @@ -69,82 +63,50 @@ import net.sf.openrocket.util.BugException; import net.sf.openrocket.utils.MotorCorrelation; import org.jfree.chart.ChartColor; -import org.jfree.chart.ChartFactory; -import org.jfree.chart.ChartPanel; -import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.ValueAxis; -import org.jfree.chart.plot.PlotOrientation; -import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.title.TextTitle; -import org.jfree.data.xy.XYSeries; -import org.jfree.data.xy.XYSeriesCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelector { private static final Logger log = LoggerFactory.getLogger(ThrustCurveMotorSelectionPanel.class); - + private static final Translator trans = Application.getTranslator(); - + private static final double MOTOR_SIMILARITY_THRESHOLD = 0.95; - - private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50; - private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12; - + private static final Paint[] CURVE_COLORS = ChartColor.createDefaultPaintArray(); - - private static final Color NO_COMMENT_COLOR = Color.GRAY; - private static final Color WITH_COMMENT_COLOR = Color.BLACK; - + private static final ThrustCurveMotorComparator MOTOR_COMPARATOR = new ThrustCurveMotorComparator(); - + private List database; - + private CloseableDialog dialog = null; - - final ThrustCurveMotorDatabaseModel model; + + private final ThrustCurveMotorDatabaseModel model; private final JTable table; private final TableRowSorter sorter; private final MotorRowFilter rowFilter; - + private final JCheckBox hideSimilarBox; - + private final JTextField searchField; - String[] searchTerms = new String[0]; - - private final StyledLabel diameterLabel; + private final JLabel curveSelectionLabel; private final JComboBox curveSelectionBox; private final DefaultComboBoxModel curveSelectionModel; - - private final JLabel totalImpulseLabel; - private final JLabel classificationLabel; - private final JLabel avgThrustLabel; - private final JLabel maxThrustLabel; - private final JLabel burnTimeLabel; - private final JLabel launchMassLabel; - private final JLabel emptyMassLabel; - private final JLabel dataPointsLabel; - private final JLabel digestLabel; - - private final JTextArea comment; - private final Font noCommentFont; - private final Font withCommentFont; - - private final JFreeChart chart; - private final ChartPanel chartPanel; - private final JLabel zoomIcon; - private final JComboBox delayBox; + private final MotorInformationPanel motorInformationPanel; + private final MotorFilterPanel motorFilterPanel; + private ThrustCurveMotor selectedMotor; private ThrustCurveMotorSet selectedMotorSet; private double selectedDelay; - + public ThrustCurveMotorSelectionPanel(MotorMount mount, String currentConfig) { this(); setMotorMountAndConfig( mount, currentConfig ); - + } /** * Sole constructor. @@ -155,169 +117,199 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec */ public ThrustCurveMotorSelectionPanel() { super(new MigLayout("fill", "[grow][]")); - + // Construct the database (adding the current motor if not in the db already) - List db; - db = Application.getThrustCurveMotorSetDatabase().getMotorSets(); - - database = db; - + database = Application.getThrustCurveMotorSetDatabase().getMotorSets(); + model = new ThrustCurveMotorDatabaseModel(database); rowFilter = new MotorRowFilter(model); - - //// GUI - - JPanel panel; - JLabel label; - - panel = new JPanel(new MigLayout("fill")); - this.add(panel, "grow"); - - - - // Selection label - //// Select rocket motor: - label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Selrocketmotor"), Style.BOLD); - panel.add(label, "spanx, wrap para"); - - // Search field - //// Search: - label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Search")); - panel.add(label, "split"); - - 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+"); - rowFilter.setSearchTerms(Arrays.asList(split)); - sorter.sort(); - scrollSelectionVisible(); - } - }); - panel.add(searchField, "growx"); - + motorInformationPanel = new MotorInformationPanel(); + + //// MotorFilter { - // Find all the manufacturers: Set allManufacturers = new HashSet(); for (ThrustCurveMotorSet s : database) { allManufacturers.add(s.getManufacturer()); } - final MotorFilterPopupMenu popup = new MotorFilterPopupMenu(allManufacturers, rowFilter) { - + motorFilterPanel = new MotorFilterPanel(allManufacturers, rowFilter) { @Override public void onSelectionChanged() { sorter.sort(); scrollSelectionVisible(); } - }; - JButton manuFilter = new JButton(trans.get("TCMotorSelPan.btn.filter")); - manuFilter.addMouseListener(new MouseListener() { - + } + + //// GUI + JPanel panel = new JPanel(new MigLayout("fill","[][grow][]")); + this.add(panel, "grow"); + + //// Select thrust curve: + { + curveSelectionLabel = new JLabel(trans.get("TCMotorSelPan.lbl.Selectthrustcurve")); + panel.add(curveSelectionLabel); + + curveSelectionModel = new DefaultComboBoxModel(); + curveSelectionBox = new JComboBox(curveSelectionModel); + curveSelectionBox.setRenderer(new CurveSelectionRenderer(curveSelectionBox.getRenderer())); + curveSelectionBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Object value = curveSelectionBox.getSelectedItem(); + if (value != null) { + select(((MotorHolder) value).getMotor()); + } + } + }); + panel.add(curveSelectionBox, "growx"); + + final JPopupMenu popup = new JPopupMenu(); + popup.add( motorInformationPanel ); + JButton showDetailsButton = new JButton(trans.get("TCMotorSelPan.btn.details")); + showDetailsButton.addMouseListener(new MouseListener() { @Override public void mouseClicked(MouseEvent e) { } - @Override public void mousePressed(MouseEvent e) { } - @Override public void mouseReleased(MouseEvent e) { popup.show(e.getComponent(), e.getX(), e.getY()); } - @Override public void mouseEntered(MouseEvent e) { } - @Override public void mouseExited(MouseEvent e) { } - }); - panel.add(manuFilter, "gapleft para, wrap para"); - - + panel.add(showDetailsButton, "gapleft para, wrap"); + } - - // Motor selection table - table = new JTable(model); - - - // Set comparators and widths - table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - sorter = new TableRowSorter(model); - for (int i = 0; i < ThrustCurveMotorColumns.values().length; i++) { - ThrustCurveMotorColumns column = ThrustCurveMotorColumns.values()[i]; - sorter.setComparator(i, column.getComparator()); - table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth()); - } - table.setRowSorter(sorter); - // force initial sort order to by diameter, total impulse, manufacturer + + // Ejection charge delay: { - RowSorter.SortKey[] sortKeys = { - new RowSorter.SortKey(ThrustCurveMotorColumns.DIAMETER.ordinal(), SortOrder.ASCENDING), - new RowSorter.SortKey(ThrustCurveMotorColumns.TOTAL_IMPULSE.ordinal(), SortOrder.ASCENDING), - new RowSorter.SortKey(ThrustCurveMotorColumns.MANUFACTURER.ordinal(), SortOrder.ASCENDING) - }; - sorter.setSortKeys(Arrays.asList(sortKeys)); + panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Ejectionchargedelay"))); + + delayBox = new JComboBox(); + delayBox.setEditable(true); + delayBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JComboBox cb = (JComboBox) e.getSource(); + String sel = (String) cb.getSelectedItem(); + //// None + if (sel.equalsIgnoreCase(trans.get("TCMotorSelPan.equalsIgnoreCase.None"))) { + selectedDelay = Motor.PLUGGED; + } else { + try { + selectedDelay = Double.parseDouble(sel); + } catch (NumberFormatException ignore) { + } + } + setDelays(false); + } + }); + panel.add(delayBox, "growx"); + //// (Number of seconds or \"None\") + panel.add(new StyledLabel(trans.get("TCMotorSelPan.lbl.NumberofsecondsorNone"), -3), "wrap para"); + setDelays(false); } - - sorter.setRowFilter(rowFilter); - - // Set selection and double-click listeners - table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - int row = table.getSelectedRow(); - if (row >= 0) { - row = table.convertRowIndexToModel(row); - ThrustCurveMotorSet motorSet = model.getMotorSet(row); - log.info(Markers.USER_MARKER, "Selected table row " + row + ": " + motorSet); - if (motorSet != selectedMotorSet) { - select(selectMotor(motorSet)); - } - } else { - log.info(Markers.USER_MARKER, "Selected table row " + row + ", nothing selected"); + + // Search field + { + //// Search: + StyledLabel label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Search")); + panel.add(label); + 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+"); + rowFilter.setSearchTerms(Arrays.asList(split)); + sorter.sort(); + scrollSelectionVisible(); + } + }); + panel.add(searchField, "span, growx, wrap"); + } + + //// Motor selection table + { + table = new JTable(model); + + // Set comparators and widths + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + sorter = new TableRowSorter(model); + for (int i = 0; i < ThrustCurveMotorColumns.values().length; i++) { + ThrustCurveMotorColumns column = ThrustCurveMotorColumns.values()[i]; + sorter.setComparator(i, column.getComparator()); + table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth()); } - }); - table.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { - if (dialog != null) { - dialog.close(true); + table.setRowSorter(sorter); + // force initial sort order to by diameter, total impulse, manufacturer + { + RowSorter.SortKey[] sortKeys = { + new RowSorter.SortKey(ThrustCurveMotorColumns.DIAMETER.ordinal(), SortOrder.ASCENDING), + new RowSorter.SortKey(ThrustCurveMotorColumns.TOTAL_IMPULSE.ordinal(), SortOrder.ASCENDING), + new RowSorter.SortKey(ThrustCurveMotorColumns.MANUFACTURER.ordinal(), SortOrder.ASCENDING) + }; + sorter.setSortKeys(Arrays.asList(sortKeys)); + } + + sorter.setRowFilter(rowFilter); + + // Set selection and double-click listeners + table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + int row = table.getSelectedRow(); + if (row >= 0) { + row = table.convertRowIndexToModel(row); + ThrustCurveMotorSet motorSet = model.getMotorSet(row); + log.info(Markers.USER_MARKER, "Selected table row " + row + ": " + motorSet); + if (motorSet != selectedMotorSet) { + select(selectMotor(motorSet)); + } + } else { + log.info(Markers.USER_MARKER, "Selected table row " + row + ", nothing selected"); } } - } - }); - - - JScrollPane scrollpane = new JScrollPane(); - scrollpane.setViewportView(table); - panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para"); - + }); + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { + if (dialog != null) { + dialog.close(true); + } + } + } + }); + + JScrollPane scrollpane = new JScrollPane(); + scrollpane.setViewportView(table); + panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para"); + + } + + //// Hide used motor files { final JCheckBox hideUsedBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideUsed")); GUIUtil.changeFontSize(hideUsedBox, -1); @@ -331,230 +323,53 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec }); panel.add(hideUsedBox, "gapleft para, spanx, growx, wrap para"); } - - scrollSelectionVisible(); - + //// Hide very similar thrust curves - hideSimilarBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideSimilar")); - GUIUtil.changeFontSize(hideSimilarBox, -1); - hideSimilarBox.setSelected(Application.getPreferences().getBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, true)); - hideSimilarBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Application.getPreferences().putBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected()); - updateData(); - } - }); - panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para"); - - // Motor mount diameter label - //// Motor mount diameter: - diameterLabel = new StyledLabel(); - panel.add(label, "gapright 30lp, spanx, split"); - + { + hideSimilarBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideSimilar")); + GUIUtil.changeFontSize(hideSimilarBox, -1); + hideSimilarBox.setSelected(Application.getPreferences().getBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, true)); + hideSimilarBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Application.getPreferences().putBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected()); + updateData(); + } + }); + panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para"); + } + // Vertical split this.add(panel, "grow"); this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para"); - panel = new JPanel(new MigLayout("fill")); - - - - // Thrust curve selection - //// Select thrust curve: - curveSelectionLabel = new JLabel(trans.get("TCMotorSelPan.lbl.Selectthrustcurve")); - panel.add(curveSelectionLabel); - - curveSelectionModel = new DefaultComboBoxModel(); - curveSelectionBox = new JComboBox(curveSelectionModel); - curveSelectionBox.setRenderer(new CurveSelectionRenderer(curveSelectionBox.getRenderer())); - curveSelectionBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Object value = curveSelectionBox.getSelectedItem(); - if (value != null) { - select(((MotorHolder) value).getMotor()); - } - } - }); - panel.add(curveSelectionBox, "growx, wrap para"); - - // Ejection charge delay: - panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Ejectionchargedelay"))); - - delayBox = new JComboBox(); - delayBox.setEditable(true); - delayBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JComboBox cb = (JComboBox) e.getSource(); - String sel = (String) cb.getSelectedItem(); - //// None - if (sel.equalsIgnoreCase(trans.get("TCMotorSelPan.equalsIgnoreCase.None"))) { - selectedDelay = Motor.PLUGGED; - } else { - try { - selectedDelay = Double.parseDouble(sel); - } catch (NumberFormatException ignore) { - } - } - setDelays(false); - } - }); - panel.add(delayBox, "growx, wrap rel"); - //// (Number of seconds or \"None\") - panel.add(new StyledLabel(trans.get("TCMotorSelPan.lbl.NumberofsecondsorNone"), -3), "skip, wrap para"); - setDelays(false); - - - panel.add(new JSeparator(), "spanx, growx, wrap para"); - - - - // Thrust curve info - //// Total impulse: - panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Totalimpulse"))); - totalImpulseLabel = new JLabel(); - panel.add(totalImpulseLabel, "split"); - classificationLabel = new JLabel(); - classificationLabel.setEnabled(false); // Gray out - panel.add(classificationLabel, "gapleft unrel, wrap"); - - //// Avg. thrust: - panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Avgthrust"))); - avgThrustLabel = new JLabel(); - panel.add(avgThrustLabel, "wrap"); - - //// Max. thrust: - panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Maxthrust"))); - maxThrustLabel = new JLabel(); - panel.add(maxThrustLabel, "wrap"); - - //// Burn time: - panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Burntime"))); - burnTimeLabel = new JLabel(); - panel.add(burnTimeLabel, "wrap"); - - //// Launch mass: - panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Launchmass"))); - launchMassLabel = new JLabel(); - panel.add(launchMassLabel, "wrap"); - - //// Empty mass: - panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Emptymass"))); - emptyMassLabel = new JLabel(); - panel.add(emptyMassLabel, "wrap"); - - //// Data points: - panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Datapoints"))); - dataPointsLabel = new JLabel(); - panel.add(dataPointsLabel, "wrap para"); - - if (System.getProperty("openrocket.debug.motordigest") != null) { - //// Digest: - panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Digest"))); - digestLabel = new JLabel(); - panel.add(digestLabel, "w :300:, wrap para"); - } else { - digestLabel = null; - } - - - comment = new JTextArea(5, 5); - GUIUtil.changeFontSize(comment, -2); - withCommentFont = comment.getFont(); - noCommentFont = withCommentFont.deriveFont(Font.ITALIC); - comment.setLineWrap(true); - comment.setWrapStyleWord(true); - comment.setEditable(false); - scrollpane = new JScrollPane(comment); - panel.add(scrollpane, "spanx, growx, wrap para"); - - // Thrust curve plot - chart = ChartFactory.createXYLineChart( - null, // title - null, // xAxisLabel - null, // yAxisLabel - null, // dataset - PlotOrientation.VERTICAL, - false, // legend - false, // tooltips - false // urls - ); - - - // Add the data and formatting to the plot - XYPlot plot = chart.getXYPlot(); - - changeLabelFont(plot.getRangeAxis(), -2); - changeLabelFont(plot.getDomainAxis(), -2); - - //// Thrust curve: - chart.setTitle(new TextTitle(trans.get("TCMotorSelPan.title.Thrustcurve"), this.getFont())); - chart.setBackgroundPaint(this.getBackground()); - plot.setBackgroundPaint(Color.WHITE); - plot.setDomainGridlinePaint(Color.LIGHT_GRAY); - plot.setRangeGridlinePaint(Color.LIGHT_GRAY); - - chartPanel = new ChartPanel(chart, - false, // properties - false, // save - false, // print - false, // zoom - false); // tooltips - chartPanel.setMouseZoomable(false); - chartPanel.setPopupMenu(null); - chartPanel.setMouseWheelEnabled(false); - chartPanel.setRangeZoomable(false); - chartPanel.setDomainZoomable(false); - - chartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - chartPanel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (selectedMotor == null || selectedMotorSet == null) - return; - if (e.getButton() == MouseEvent.BUTTON1) { - // Open plot dialog - List motors = getFilteredCurves(); - ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(motors, - motors.indexOf(selectedMotor), - SwingUtilities.getWindowAncestor(ThrustCurveMotorSelectionPanel.this)); - plotDialog.setVisible(true); - } - } - }); - - JLayeredPane layer = new CustomLayeredPane(); - - layer.setBorder(BorderFactory.createLineBorder(Color.BLUE)); - - layer.add(chartPanel, (Integer) 0); - - zoomIcon = new JLabel(Icons.ZOOM_IN); - zoomIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - layer.add(zoomIcon, (Integer) 1); - - - panel.add(layer, "width 300:300:, height 180:180:, grow, spanx"); - - this.add(panel, "grow"); + JTabbedPane rightSide = new JTabbedPane(); + rightSide.add(trans.get("TCMotorSelPan.btn.filter"), motorFilterPanel); + rightSide.add(trans.get("TCMotorSelPan.btn.details"), motorInformationPanel); + + this.add(rightSide); + // Update the panel data + scrollSelectionVisible(); updateData(); setDelays(false); - + } - + public void setMotorMountAndConfig( MotorMount mount, String currentConfig ) { double diameter = 0; + if ( mount != null ) { + diameter = mount.getMotorMountDiameter(); + } + if (currentConfig != null && mount != null) { MotorConfiguration motorConf = mount.getMotorConfiguration().get(currentConfig); selectedMotor = (ThrustCurveMotor) motorConf.getMotor(); selectedDelay = motorConf.getEjectionDelay(); diameter = mount.getMotorMountDiameter(); } - + // If current motor is not found in db, add a new ThrustCurveMotorSet containing it if (selectedMotor != null) { for (ThrustCurveMotorSet motorSet : database) { @@ -575,46 +390,43 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec updateData(); setDelays(true); - - diameterLabel.setText(trans.get("TCMotorSelPan.lbl.Motormountdia") + " " + - UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(0)); - - rowFilter.setMotorMount(mount); + + motorFilterPanel.setMotorMount(mount); scrollSelectionVisible(); } - + @Override public Motor getSelectedMotor() { return selectedMotor; } - - + + @Override public double getSelectedDelay() { return selectedDelay; } - - + + @Override public JComponent getDefaultFocus() { return searchField; } - + @Override public void selectedMotor(Motor motorSelection) { if (!(motorSelection instanceof ThrustCurveMotor)) { log.error("Received argument that was not ThrustCurveMotor: " + motorSelection); return; } - + ThrustCurveMotor motor = (ThrustCurveMotor) motorSelection; ThrustCurveMotorSet set = findMotorSet(motor); if (set == null) { log.error("Could not find set for motor:" + motorSelection); return; } - + // Store selected motor in preferences node, set all others to false Preferences prefs = ((SwingPreferences) Application.getPreferences()).getNode(net.sf.openrocket.startup.Preferences.PREFERRED_THRUST_CURVE_MOTOR_NODE); for (ThrustCurveMotor m : set.getMotors()) { @@ -622,34 +434,27 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec prefs.putBoolean(digest, m == motor); } } - + public void setCloseableDialog(CloseableDialog dialog) { this.dialog = dialog; } - - - - private void changeLabelFont(ValueAxis axis, float size) { - Font font = axis.getTickLabelFont(); - font = font.deriveFont(font.getSize2D() + size); - axis.setTickLabelFont(font); - } - - + + + /** * Called when a different motor is selected from within the panel. */ private void select(ThrustCurveMotor motor) { if (selectedMotor == motor) return; - + ThrustCurveMotorSet set = findMotorSet(motor); if (set == null) { throw new BugException("Could not find motor from database, motor=" + motor); } - + boolean updateDelays = (selectedMotorSet != set); - + selectedMotor = motor; selectedMotorSet = set; updateData(); @@ -657,46 +462,30 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec setDelays(true); } } - - + + private void updateData() { - + if (selectedMotorSet == null) { // No motor selected curveSelectionModel.removeAllElements(); curveSelectionBox.setEnabled(false); curveSelectionLabel.setEnabled(false); - totalImpulseLabel.setText(""); - totalImpulseLabel.setToolTipText(null); - classificationLabel.setText(""); - classificationLabel.setToolTipText(null); - avgThrustLabel.setText(""); - maxThrustLabel.setText(""); - burnTimeLabel.setText(""); - launchMassLabel.setText(""); - emptyMassLabel.setText(""); - dataPointsLabel.setText(""); - if (digestLabel != null) { - digestLabel.setText(""); - } - setComment(""); - chart.getXYPlot().setDataset(new XYSeriesCollection()); + motorInformationPanel.clearData(); return; } - - + // Check which thrust curves to display List motors = getFilteredCurves(); final int index = motors.indexOf(selectedMotor); - - + // Update the thrust curve selection box curveSelectionModel.removeAllElements(); for (int i = 0; i < motors.size(); i++) { curveSelectionModel.addElement(new MotorHolder(motors.get(i), i)); } curveSelectionBox.setSelectedIndex(index); - + if (motors.size() > 1) { curveSelectionBox.setEnabled(true); curveSelectionLabel.setEnabled(true); @@ -705,60 +494,11 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec curveSelectionLabel.setEnabled(false); } - - // Update thrust curve data - double impulse = selectedMotor.getTotalImpulseEstimate(); - MotorClass mc = MotorClass.getMotorClass(impulse); - totalImpulseLabel.setText(UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(impulse)); - classificationLabel.setText("(" + mc.getDescription(impulse) + ")"); - totalImpulseLabel.setToolTipText(mc.getClassDescription()); - classificationLabel.setToolTipText(mc.getClassDescription()); - - avgThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit( - selectedMotor.getAverageThrustEstimate())); - maxThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit( - selectedMotor.getMaxThrustEstimate())); - burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit( - selectedMotor.getBurnTimeEstimate())); - launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit( - selectedMotor.getLaunchCG().weight)); - emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit( - selectedMotor.getEmptyCG().weight)); - dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1)); - if (digestLabel != null) { - digestLabel.setText(selectedMotor.getDigest()); - } - - setComment(selectedMotor.getDescription()); - - - // Update the plot - XYPlot plot = chart.getXYPlot(); - - XYSeriesCollection dataset = new XYSeriesCollection(); - for (int i = 0; i < motors.size(); i++) { - ThrustCurveMotor m = motors.get(i); - - //// Thrust - XYSeries series = new XYSeries(trans.get("TCMotorSelPan.title.Thrust") + " (" + i + ")"); - double[] time = m.getTimePoints(); - double[] thrust = m.getThrustPoints(); - - for (int j = 0; j < time.length; j++) { - series.add(time[j], thrust[j]); - } - - dataset.addSeries(series); - - boolean selected = (i == index); - plot.getRenderer().setSeriesStroke(i, new BasicStroke(selected ? 3 : 1)); - plot.getRenderer().setSeriesPaint(i, getColor(i)); - } - - plot.setDataset(dataset); + motorInformationPanel.updateData(motors, selectedMotor); + } - - private List getFilteredCurves() { + + List getFilteredCurves() { List motors = selectedMotorSet.getMotors(); if (hideSimilarBox.isSelected()) { List filtered = new ArrayList(motors.size()); @@ -768,7 +508,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec filtered.add(m); continue; } - + double similarity = MotorCorrelation.similarity(selectedMotor, m); log.debug("Motor similarity: " + similarity); if (similarity < MOTOR_SIMILARITY_THRESHOLD) { @@ -777,28 +517,13 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } motors = filtered; } - + Collections.sort(motors, MOTOR_COMPARATOR); - + return motors; } - - - private void setComment(String s) { - s = s.trim(); - if (s.length() == 0) { - //// No description available. - comment.setText(trans.get("TCMotorSelPan.noDescription")); - comment.setFont(noCommentFont); - comment.setForeground(NO_COMMENT_COLOR); - } else { - comment.setText(s); - comment.setFont(withCommentFont); - comment.setForeground(WITH_COMMENT_COLOR); - } - comment.setCaretPosition(0); - } - + + private void scrollSelectionVisible() { if (selectedMotorSet != null) { int index = table.convertRowIndexToView(model.getIndex(selectedMotorSet)); @@ -809,13 +534,13 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec table.scrollRectToVisible(rect); } } - - + + public static Color getColor(int index) { return (Color) CURVE_COLORS[index % CURVE_COLORS.length]; } - - + + /** * Find the ThrustCurveMotorSet that contains a motor. * @@ -828,12 +553,12 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec return set; } } - + return null; } - - - + + + /** * Select the default motor from this ThrustCurveMotorSet. This uses primarily motors * that the user has previously used, and secondarily a heuristic method of selecting which @@ -849,8 +574,8 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec if (set.getMotorCount() == 1) { return set.getMotors().get(0); } - - + + // Find which motor has been used the most recently List list = set.getMotors(); Preferences prefs = ((SwingPreferences) Application.getPreferences()).getNode(net.sf.openrocket.startup.Preferences.PREFERRED_THRUST_CURVE_MOTOR_NODE); @@ -860,13 +585,13 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec return m; } } - + // No motor has been used Collections.sort(list, MOTOR_COMPARATOR); return list.get(0); } - - + + /** * Set the values in the delay combo box. If reset is true * then sets the selected value as the value closest to selectedDelay, otherwise @@ -874,25 +599,25 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec */ private void setDelays(boolean reset) { if (selectedMotor == null) { - + //// None delayBox.setModel(new DefaultComboBoxModel(new String[] { trans.get("TCMotorSelPan.delayBox.None") })); delayBox.setSelectedIndex(0); - + } else { - + List delays = selectedMotorSet.getDelays(); String[] delayStrings = new String[delays.size()]; double currentDelay = selectedDelay; // Store current setting locally - + for (int i = 0; i < delays.size(); i++) { //// None delayStrings[i] = ThrustCurveMotor.getDelayString(delays.get(i), trans.get("TCMotorSelPan.delayBox.None")); } delayBox.setModel(new DefaultComboBoxModel(delayStrings)); - + if (reset) { - + // Find and set the closest value double closest = Double.NaN; for (int i = 0; i < delays.size(); i++) { @@ -908,60 +633,43 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } else { delayBox.setSelectedItem("None"); } - + } else { - + selectedDelay = currentDelay; //// None delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, trans.get("TCMotorSelPan.delayBox.None"))); - + } - + } } - - - - + ////////////////////// - - + + private class CurveSelectionRenderer implements ListCellRenderer { - + private final ListCellRenderer renderer; - + public CurveSelectionRenderer(ListCellRenderer renderer) { this.renderer = renderer; } - + @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - + Component c = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (value instanceof MotorHolder) { MotorHolder m = (MotorHolder) value; c.setForeground(getColor(m.getIndex())); } - + return c; } - - } - - - /** - * Custom layered pane that sets the bounds of the components on every layout. - */ - public class CustomLayeredPane extends JLayeredPane { - @Override - public void doLayout() { - synchronized (getTreeLock()) { - int w = getWidth(); - int h = getHeight(); - chartPanel.setBounds(0, 0, w, h); - zoomIcon.setBounds(w - ZOOM_ICON_POSITION_NEGATIVE_X, ZOOM_ICON_POSITION_POSITIVE_Y, 50, 50); - } - } + } + + } From 7221b6558e10c2506e273f9318e5d002df48b88d Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Fri, 4 Oct 2013 21:33:46 -0500 Subject: [PATCH 29/47] More cleanup. --- core/resources/l10n/messages.properties | 2 + .../motor/thrustcurve/MotorFilterPanel.java | 48 +++++++-- .../motor/thrustcurve/MotorRowFilter.java | 99 ++++++++++++------- .../ThrustCurveMotorSelectionPanel.java | 6 +- 4 files changed, 106 insertions(+), 49 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 55f9ece1a..09e96ae34 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1090,6 +1090,8 @@ TCMotorSelPan.checkbox.hideSimilar = Hide very similar thrust curves TCMotorSelPan.checkbox.hideUsed = Hide motors already used in the mount TCMotorSelPan.btn.details = Show Details TCMotorSelPan.btn.filter = Filter Motors +TCMotorSelPan.MotorSize = Motor Dimensions +TCMotorSelPan.limitByLength = Limit motors to motor mount length TCMotorSelPan.SHOW_DESCRIPTIONS.desc1 = Show all motors TCMotorSelPan.SHOW_DESCRIPTIONS.desc2 = Show motors with diameter less than that of the motor mount TCMotorSelPan.SHOW_DESCRIPTIONS.desc3 = Show motors with diameter equal to that of the motor mount diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java index 3c706c430..f39f4f7e8 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java @@ -12,6 +12,7 @@ import java.util.List; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; @@ -34,6 +35,7 @@ import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; @@ -48,7 +50,13 @@ public abstract class MotorFilterPanel extends JPanel { private final CheckList impulseCheckList; private final MotorRowFilter filter; - private final TitledBorder diameterTitleBorder; + + // Things we change the label on based on the MotorMount. + private final JCheckBox maximumLengthCheckBox; + private final JRadioButton showSmallerDiametersButton; + private final JRadioButton showExactDiametersButton; + + private Double mountLength; private final DoubleModel mountDiameter = new DoubleModel(1); private int showMode = SHOW_ALL; @@ -194,7 +202,7 @@ public abstract class MotorFilterPanel extends JPanel { // Diameter selection sub = new JPanel(new MigLayout("fill")); - diameterTitleBorder = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.DIAMETER")); + TitledBorder diameterTitleBorder = BorderFactory.createTitledBorder(trans.get("TCMotorSelPan.MotorSize")); GUIUtil.changeFontStyle(diameterTitleBorder, Font.BOLD); sub.setBorder(diameterTitleBorder); @@ -211,7 +219,7 @@ public abstract class MotorFilterPanel extends JPanel { showAllDiametersButton.setSelected( showMode == SHOW_ALL); sub.add(showAllDiametersButton, "growx,wrap"); - JRadioButton showSmallerDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2") ); + showSmallerDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2") ); showSmallerDiametersButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -224,7 +232,7 @@ public abstract class MotorFilterPanel extends JPanel { showSmallerDiametersButton.setSelected( showMode == SHOW_SMALLER); sub.add(showSmallerDiametersButton, "growx,wrap"); - JRadioButton showExactDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3") ); + showExactDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3") ); showExactDiametersButton.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -258,6 +266,23 @@ public abstract class MotorFilterPanel extends JPanel { sub.add(new UnitSelector(minDiameter)); sub.add(new BasicSlider(minDiameter.getSliderModel(0,0.5, mountDiameter)), "w 100lp, wrap"); } + + { + maximumLengthCheckBox = new JCheckBox(trans.get("TCMotorSelPan.limitByLength")); + maximumLengthCheckBox.addChangeListener( new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (maximumLengthCheckBox.isSelected() ) { + MotorFilterPanel.this.filter.setMaximumLength( mountLength ); + } else { + MotorFilterPanel.this.filter.setMaximumLength(null); + } + onSelectionChanged(); + } + + }); + sub.add(maximumLengthCheckBox); + } this.add(sub, "grow,wrap"); } @@ -267,13 +292,20 @@ public abstract class MotorFilterPanel extends JPanel { onSelectionChanged(); if ( mount == null ) { // Disable diameter controls? - diameterTitleBorder.setTitle(trans.get("TCurveMotorCol.DIAMETER")); + showSmallerDiametersButton.setText(trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2")); + showExactDiametersButton.setText(trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3")); + maximumLengthCheckBox.setText("Limit by length"); mountDiameter.setValue(1.0); + mountLength = null; } else { mountDiameter.setValue(mount.getMotorMountDiameter()); - diameterTitleBorder.setTitle(trans.get("TCurveMotorCol.DIAMETER") + " " - + trans.get("TCMotorSelPan.lbl.Motormountdia") + " " + - UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(mount.getMotorMountDiameter())); + mountLength = ((RocketComponent)mount).getLength(); + showSmallerDiametersButton.setText(trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2") + + " " + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(mount.getMotorMountDiameter())+ ")"); + showExactDiametersButton.setText(trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3") + + " (" + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(mount.getMotorMountDiameter())+ ")"); + maximumLengthCheckBox.setText("Limit by length" + + " (" + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(((RocketComponent)mount).getLength()) +")"); } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java index 568dd31e1..513179f5f 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java @@ -21,43 +21,45 @@ import net.sf.openrocket.rocketcomponent.MotorMount; * Abstract adapter class. */ class MotorRowFilter extends RowFilter { - + public enum DiameterFilterControl { ALL, EXACT, SMALLER }; - + // configuration data used in the filter process private final ThrustCurveMotorDatabaseModel model; private Double diameter; private List usedMotors = new ArrayList(); - + // things which can be changed to modify filter behavior - + + private Double maximumLength; + // Limit motors based on minimum diameter private Double minimumDiameter; - + // Collection of strings which match text in the motor private List searchTerms = Collections. emptyList(); - + // Limit motors based on diameter of the motor mount private DiameterFilterControl diameterControl = DiameterFilterControl.ALL; - + // Boolean which hides motors in the usedMotors list private boolean hideUsedMotors = false; - + // List of manufacturers to exclude. private List excludedManufacturers = new ArrayList(); - + // List of ImpulseClasses to exclude. private List excludedImpulseClass = new ArrayList(); - + public MotorRowFilter(ThrustCurveMotorDatabaseModel model) { super(); this.model = model; } - + public void setMotorMount( MotorMount mount ) { if (mount != null) { this.diameter = mount.getMotorMountDiameter(); @@ -68,7 +70,7 @@ class MotorRowFilter extends RowFilter { this.diameter = null; } } - + public void setSearchTerms(final List searchTerms) { this.searchTerms = new ArrayList(); for (String s : searchTerms) { @@ -78,7 +80,15 @@ class MotorRowFilter extends RowFilter { } } } - + + Double getMaximumLength() { + return maximumLength; + } + + void setMaximumLength(Double maximumLength) { + this.maximumLength = maximumLength; + } + Double getMinimumDiameter() { return minimumDiameter; } @@ -94,11 +104,11 @@ class MotorRowFilter extends RowFilter { void setDiameterControl(DiameterFilterControl diameterControl) { this.diameterControl = diameterControl; } - + void setHideUsedMotors(boolean hideUsedMotors) { this.hideUsedMotors = hideUsedMotors; } - + List getExcludedManufacturers() { return excludedManufacturers; } @@ -107,19 +117,19 @@ class MotorRowFilter extends RowFilter { this.excludedManufacturers.clear(); this.excludedManufacturers.addAll(excludedManufacturers); } - + void setExcludedImpulseClasses(Collection excludedImpulseClasses ) { this.excludedImpulseClass.clear(); this.excludedImpulseClass.addAll(excludedImpulseClasses); } - + @Override public boolean include(RowFilter.Entry entry) { int index = entry.getIdentifier(); ThrustCurveMotorSet m = model.getMotorSet(index); - return filterManufacturers(m) && filterUsed(m) && filterByDiameter(m) && filterByString(m) && filterByImpulseClass(m); + return filterManufacturers(m) && filterUsed(m) && filterBySize(m) && filterByString(m) && filterByImpulseClass(m); } - + private boolean filterManufacturers(ThrustCurveMotorSet m) { if (excludedManufacturers.contains(m.getManufacturer())) { return false; @@ -127,7 +137,7 @@ class MotorRowFilter extends RowFilter { return true; } } - + private boolean filterUsed(ThrustCurveMotorSet m) { if (!hideUsedMotors) { return true; @@ -139,30 +149,43 @@ class MotorRowFilter extends RowFilter { } return true; } - - private boolean filterByDiameter(ThrustCurveMotorSet m) { - + + private boolean filterBySize(ThrustCurveMotorSet m) { + if ( minimumDiameter != null ) { if ( m.getDiameter() <= minimumDiameter - 0.0015 ) { return false; } } + + if (diameter != null) { + switch (diameterControl) { + default: + case ALL: + break; + case EXACT: + if ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015)) { + break; + } + return false; + case SMALLER: + if (m.getDiameter() <= diameter + 0.0004) { + break; + } + return false; + } + } - if (diameter == null) { - return true; - } - switch (diameterControl) { - default: - case ALL: - return true; - case EXACT: - return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015)); - case SMALLER: - return (m.getDiameter() <= diameter + 0.0004); + if ( maximumLength != null ) { + if ( m.getLength() > maximumLength ) { + return false; + } } + + return true; } - - + + private boolean filterByString(ThrustCurveMotorSet m) { main: for (String s : searchTerms) { for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) { @@ -172,9 +195,9 @@ class MotorRowFilter extends RowFilter { } return false; } - return true; + return true; } - + private boolean filterByImpulseClass(ThrustCurveMotorSet m) { for( ImpulseClass c : excludedImpulseClass ) { if (c.isIn(m) ) { diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index 83b5e5b62..8e2016b07 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -305,7 +305,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec JScrollPane scrollpane = new JScrollPane(); scrollpane.setViewportView(table); - panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para"); + panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap"); } @@ -321,7 +321,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec scrollSelectionVisible(); } }); - panel.add(hideUsedBox, "gapleft para, spanx, growx, wrap para"); + panel.add(hideUsedBox, "gapleft para, spanx, growx, wrap"); } //// Hide very similar thrust curves @@ -336,7 +336,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec updateData(); } }); - panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para"); + panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap"); } // Vertical split From afcedf90f127a7f64e6e556eebcd48d8d224ca68 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Sat, 5 Oct 2013 15:43:12 -0500 Subject: [PATCH 30/47] Remove the show details button now that it's available in a tab. --- .../ThrustCurveMotorSelectionPanel.java | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index 8e2016b07..2091f33d5 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -144,7 +144,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } //// GUI - JPanel panel = new JPanel(new MigLayout("fill","[][grow][]")); + JPanel panel = new JPanel(new MigLayout("fill","[][grow]")); this.add(panel, "grow"); //// Select thrust curve: @@ -165,30 +165,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } }); panel.add(curveSelectionBox, "growx"); - - final JPopupMenu popup = new JPopupMenu(); - popup.add( motorInformationPanel ); - JButton showDetailsButton = new JButton(trans.get("TCMotorSelPan.btn.details")); - showDetailsButton.addMouseListener(new MouseListener() { - @Override - public void mouseClicked(MouseEvent e) { - } - @Override - public void mousePressed(MouseEvent e) { - } - @Override - public void mouseReleased(MouseEvent e) { - popup.show(e.getComponent(), e.getX(), e.getY()); - } - @Override - public void mouseEntered(MouseEvent e) { - } - @Override - public void mouseExited(MouseEvent e) { - } - }); - panel.add(showDetailsButton, "gapleft para, wrap"); - } // Ejection charge delay: @@ -214,7 +190,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec setDelays(false); } }); - panel.add(delayBox, "growx"); + panel.add(delayBox, "split 2, growx"); //// (Number of seconds or \"None\") panel.add(new StyledLabel(trans.get("TCMotorSelPan.lbl.NumberofsecondsorNone"), -3), "wrap para"); setDelays(false); From e5679259faa5c79983086aaf2aec32b598c475b2 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Mon, 7 Oct 2013 18:47:10 -0500 Subject: [PATCH 31/47] Rework the impulse and diameter to use a two thumbed slider. --- .../motor/thrustcurve/ImpulseClass.java | 13 +- .../motor/thrustcurve/MotorFilterPanel.java | 234 +++---- .../motor/thrustcurve/MotorRowFilter.java | 73 +-- .../openrocket/gui/widgets/MultiSlider.java | 553 ++++++++++++++++ .../openrocket/gui/widgets/MultiSliderUI.java | 612 ++++++++++++++++++ 5 files changed, 1301 insertions(+), 184 deletions(-) create mode 100644 swing/src/net/sf/openrocket/gui/widgets/MultiSlider.java create mode 100644 swing/src/net/sf/openrocket/gui/widgets/MultiSliderUI.java diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ImpulseClass.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ImpulseClass.java index 6b8fc6a9f..8f639c893 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ImpulseClass.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ImpulseClass.java @@ -1,9 +1,5 @@ package net.sf.openrocket.gui.dialogs.motor.thrustcurve; -import java.text.DecimalFormat; -import java.text.NumberFormat; - -import net.sf.openrocket.database.motor.ThrustCurveMotorSet; public enum ImpulseClass { @@ -32,10 +28,13 @@ public enum ImpulseClass { public String toString() { return name; } + + public double getLow() { + return low; + } - public boolean isIn( ThrustCurveMotorSet m ) { - long motorImpulse = m.getTotalImpuse(); - return motorImpulse >= low && motorImpulse <= high; + public double getHigh() { + return high; } private double low; diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java index f39f4f7e8..0cd62ef92 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java @@ -7,6 +7,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Hashtable; import java.util.List; import javax.swing.BorderFactory; @@ -32,6 +33,7 @@ import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.gui.util.CheckList; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.gui.util.SwingPreferences; +import net.sf.openrocket.gui.widgets.MultiSlider; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.rocketcomponent.MotorMount; @@ -45,43 +47,52 @@ public abstract class MotorFilterPanel extends JPanel { private static final Translator trans = Application.getTranslator(); + private static Hashtable diameterLabels = new Hashtable(); + private static double[] diameterValues = new double[] { + 0, + .013, + .018, + .024, + .029, + .038, + .054, + .075, + .098, + 1.000 + }; + static { + for( int i = 0; i< diameterValues.length; i++ ) { + if( i == diameterValues.length-1) { + diameterLabels.put( i, new JLabel("+")); + } else { + diameterLabels.put( i, new JLabel(UnitGroup.UNITS_MOTOR_DIMENSIONS.toString(diameterValues[i]))); + } + } + } + + private static Hashtable impulseLabels = new Hashtable(); + static { + int i =0; + for( ImpulseClass impulseClass : ImpulseClass.values() ) { + impulseLabels.put(i, new JLabel( impulseClass.name() )); + i++; + } + } + private final CheckList manufacturerCheckList; - private final CheckList impulseCheckList; - private final MotorRowFilter filter; - + // Things we change the label on based on the MotorMount. private final JCheckBox maximumLengthCheckBox; - private final JRadioButton showSmallerDiametersButton; - private final JRadioButton showExactDiametersButton; - - private Double mountLength; - private final DoubleModel mountDiameter = new DoubleModel(1); - - private int showMode = SHOW_ALL; + private final MultiSlider diameterSlider; - private static final int SHOW_ALL = 0; - private static final int SHOW_SMALLER = 1; - private static final int SHOW_EXACT = 2; - private static final int SHOW_MAX = 2; + private Double mountLength; public MotorFilterPanel(Collection allManufacturers, MotorRowFilter filter ) { super(new MigLayout("fill", "[grow]")); this.filter = filter; - showMode = Application.getPreferences().getChoice(net.sf.openrocket.startup.Preferences.MOTOR_DIAMETER_FILTER, MotorFilterPanel.SHOW_MAX, MotorFilterPanel.SHOW_EXACT); - switch( showMode ) { - case SHOW_ALL: - filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.ALL); - break; - case SHOW_EXACT: - filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.EXACT); - break; - case SHOW_SMALLER: - filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.SMALLER); - break; - } List unselectedManusFromPreferences = ((SwingPreferences) Application.getPreferences()).getExcludedMotorManufacturers(); filter.setExcludedManufacturers(unselectedManusFromPreferences); @@ -152,121 +163,64 @@ public abstract class MotorFilterPanel extends JPanel { this.add(sub,"grow, wrap"); // Impulse selection - sub = new JPanel(new MigLayout("fill")); - border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.TOTAL_IMPULSE")); - GUIUtil.changeFontStyle(border, Font.BOLD); - sub.setBorder(border); - - impulseCheckList = new CheckList.Builder().build(); - impulseCheckList.setData(Arrays.asList(ImpulseClass.values())); - impulseCheckList.checkAll(); - impulseCheckList.getModel().addListDataListener( new ListDataListener() { - @Override - public void intervalAdded(ListDataEvent e) { - } - @Override - public void intervalRemoved(ListDataEvent e) { - } - @Override - public void contentsChanged(ListDataEvent e) { - MotorFilterPanel.this.filter.setExcludedImpulseClasses( impulseCheckList.getUncheckedItems() ); - onSelectionChanged(); - } - - }); - - sub.add(new JScrollPane(impulseCheckList.getList()), "grow,wrap"); - - JButton clearImpulse = new JButton(trans.get("TCMotorSelPan.btn.checkNone")); - clearImpulse.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - MotorFilterPanel.this.impulseCheckList.clearAll(); - - } - }); - sub.add(clearImpulse,"split 2"); - - JButton selectImpulse = new JButton(trans.get("TCMotorSelPan.btn.checkAll")); - selectImpulse.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - MotorFilterPanel.this.impulseCheckList.checkAll(); - - } - }); - sub.add(selectImpulse,"wrap"); + { + sub = new JPanel(new MigLayout("fill")); + border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.TOTAL_IMPULSE")); + GUIUtil.changeFontStyle(border, Font.BOLD); + sub.setBorder(border); + final MultiSlider impulseSlider = new MultiSlider(MultiSlider.HORIZONTAL,0, ImpulseClass.values().length-1,0, ImpulseClass.values().length-1); + impulseSlider.setBounded(true); // thumbs cannot cross + impulseSlider.setMajorTickSpacing(1); + impulseSlider.setPaintTicks(true); + impulseSlider.setLabelTable(impulseLabels); + impulseSlider.setPaintLabels(true); + impulseSlider.addChangeListener( new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + int minimpulse = impulseSlider.getValueAt(0); + MotorFilterPanel.this.filter.setMinimumImpulse(ImpulseClass.values()[minimpulse]); + int maximpulse = impulseSlider.getValueAt(1); + MotorFilterPanel.this.filter.setMaximumImpulse(ImpulseClass.values()[maximpulse]); + onSelectionChanged(); + } + }); + sub.add( impulseSlider, "growx, wrap"); + } this.add(sub,"grow, wrap"); - + + // Diameter selection - sub = new JPanel(new MigLayout("fill")); TitledBorder diameterTitleBorder = BorderFactory.createTitledBorder(trans.get("TCMotorSelPan.MotorSize")); GUIUtil.changeFontStyle(diameterTitleBorder, Font.BOLD); sub.setBorder(diameterTitleBorder); - JRadioButton showAllDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc1") ); - showAllDiametersButton.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showMode = SHOW_ALL; - MotorFilterPanel.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.ALL); - saveMotorDiameterMatchPrefence(); - onSelectionChanged(); - } - }); - showAllDiametersButton.setSelected( showMode == SHOW_ALL); - sub.add(showAllDiametersButton, "growx,wrap"); - - showSmallerDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2") ); - showSmallerDiametersButton.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showMode = SHOW_SMALLER; - MotorFilterPanel.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.SMALLER); - saveMotorDiameterMatchPrefence(); - onSelectionChanged(); - } - }); - showSmallerDiametersButton.setSelected( showMode == SHOW_SMALLER); - sub.add(showSmallerDiametersButton, "growx,wrap"); - - showExactDiametersButton = new JRadioButton( trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3") ); - showExactDiametersButton.addActionListener( new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showMode = SHOW_EXACT; - MotorFilterPanel.this.filter.setDiameterControl(MotorRowFilter.DiameterFilterControl.EXACT); - saveMotorDiameterMatchPrefence(); - onSelectionChanged(); - } - }); - showExactDiametersButton.setSelected( showMode == SHOW_EXACT ); - sub.add(showExactDiametersButton, "growx,wrap"); - ButtonGroup comboGroup = new ButtonGroup(); - comboGroup.add( showAllDiametersButton ); - comboGroup.add( showSmallerDiametersButton ); - comboGroup.add( showExactDiametersButton ); - { - sub.add( new JLabel("Minimum diameter"), "split 4"); - final DoubleModel minDiameter = new DoubleModel(0, UnitGroup.UNITS_MOTOR_DIMENSIONS, 0, .2); - minDiameter.addChangeListener( new ChangeListener() { + sub.add( new JLabel("Minimum diameter"), "split 2, wrap"); + diameterSlider = new MultiSlider(MultiSlider.HORIZONTAL,0, diameterValues.length-1, 0, diameterValues.length-1); + diameterSlider.setBounded(true); // thumbs cannot cross + diameterSlider.setMajorTickSpacing(1); + diameterSlider.setPaintTicks(true); + diameterSlider.setLabelTable(diameterLabels); + diameterSlider.setPaintLabels(true); + diameterSlider.addChangeListener( new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { - MotorFilterPanel.this.filter.setMinimumDiameter(minDiameter.getValue()); + int minDiameter = diameterSlider.getValueAt(0); + MotorFilterPanel.this.filter.setMinimumDiameter(diameterValues[minDiameter]); + int maxDiameter = diameterSlider.getValueAt(1); + if( maxDiameter == diameterValues.length-1 ) { + MotorFilterPanel.this.filter.setMaximumDiameter(null); + } else { + MotorFilterPanel.this.filter.setMaximumDiameter(diameterValues[maxDiameter]); + } onSelectionChanged(); } }); - JSpinner spin = new JSpinner(minDiameter.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - sub.add(spin, "growx"); - - sub.add(new UnitSelector(minDiameter)); - sub.add(new BasicSlider(minDiameter.getSliderModel(0,0.5, mountDiameter)), "w 100lp, wrap"); + sub.add( diameterSlider, "growx, wrap"); } - + { maximumLengthCheckBox = new JCheckBox(trans.get("TCMotorSelPan.limitByLength")); maximumLengthCheckBox.addChangeListener( new ChangeListener() { @@ -279,7 +233,7 @@ public abstract class MotorFilterPanel extends JPanel { } onSelectionChanged(); } - + }); sub.add(maximumLengthCheckBox); } @@ -292,28 +246,30 @@ public abstract class MotorFilterPanel extends JPanel { onSelectionChanged(); if ( mount == null ) { // Disable diameter controls? - showSmallerDiametersButton.setText(trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2")); - showExactDiametersButton.setText(trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3")); maximumLengthCheckBox.setText("Limit by length"); - mountDiameter.setValue(1.0); mountLength = null; } else { - mountDiameter.setValue(mount.getMotorMountDiameter()); mountLength = ((RocketComponent)mount).getLength(); - showSmallerDiametersButton.setText(trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2") - + " " + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(mount.getMotorMountDiameter())+ ")"); - showExactDiametersButton.setText(trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3") - + " (" + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(mount.getMotorMountDiameter())+ ")"); + double mountDiameter = mount.getMotorMountDiameter(); + // find the next largest diameter + int i; + for( i =0; i< diameterValues.length; i++ ) { + if ( mountDiameter<= diameterValues[i] ) { + break; + } + } + if( i >= diameterValues.length-1 ) { + diameterSlider.setValueAt(1, diameterValues.length-1); + } else { + diameterSlider.setValueAt(1, i) ; + } + diameterSlider.setValueAt(1, i); maximumLengthCheckBox.setText("Limit by length" + " (" + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(((RocketComponent)mount).getLength()) +")"); } } - private void saveMotorDiameterMatchPrefence() { - Application.getPreferences().putChoice("MotorDiameterMatch", showMode ); - } - public abstract void onSelectionChanged(); } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java index 513179f5f..ca6dbeed7 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java @@ -22,15 +22,8 @@ import net.sf.openrocket.rocketcomponent.MotorMount; */ class MotorRowFilter extends RowFilter { - public enum DiameterFilterControl { - ALL, - EXACT, - SMALLER - }; - // configuration data used in the filter process private final ThrustCurveMotorDatabaseModel model; - private Double diameter; private List usedMotors = new ArrayList(); // things which can be changed to modify filter behavior @@ -39,21 +32,22 @@ class MotorRowFilter extends RowFilter { // Limit motors based on minimum diameter private Double minimumDiameter; + + private Double maximumDiameter; // Collection of strings which match text in the motor private List searchTerms = Collections. emptyList(); - // Limit motors based on diameter of the motor mount - private DiameterFilterControl diameterControl = DiameterFilterControl.ALL; - // Boolean which hides motors in the usedMotors list private boolean hideUsedMotors = false; // List of manufacturers to exclude. private List excludedManufacturers = new ArrayList(); - // List of ImpulseClasses to exclude. - private List excludedImpulseClass = new ArrayList(); + // Impulse class filtering + private ImpulseClass minimumImpulse; + private ImpulseClass maximumImpulse; + public MotorRowFilter(ThrustCurveMotorDatabaseModel model) { super(); @@ -62,12 +56,9 @@ class MotorRowFilter extends RowFilter { public void setMotorMount( MotorMount mount ) { if (mount != null) { - this.diameter = mount.getMotorMountDiameter(); for (MotorConfiguration m : mount.getMotorConfiguration()) { this.usedMotors.add((ThrustCurveMotor) m.getMotor()); } - } else { - this.diameter = null; } } @@ -97,12 +88,12 @@ class MotorRowFilter extends RowFilter { this.minimumDiameter = minimumDiameter; } - DiameterFilterControl getDiameterControl() { - return diameterControl; + Double getMaximumDiameter() { + return maximumDiameter; } - void setDiameterControl(DiameterFilterControl diameterControl) { - this.diameterControl = diameterControl; + void setMaximumDiameter(Double maximumDiameter) { + this.maximumDiameter = maximumDiameter; } void setHideUsedMotors(boolean hideUsedMotors) { @@ -118,9 +109,20 @@ class MotorRowFilter extends RowFilter { this.excludedManufacturers.addAll(excludedManufacturers); } - void setExcludedImpulseClasses(Collection excludedImpulseClasses ) { - this.excludedImpulseClass.clear(); - this.excludedImpulseClass.addAll(excludedImpulseClasses); + ImpulseClass getMinimumImpulse() { + return minimumImpulse; + } + + void setMinimumImpulse(ImpulseClass minimumImpulse) { + this.minimumImpulse = minimumImpulse; + } + + ImpulseClass getMaximumImpulse() { + return maximumImpulse; + } + + void setMaximumImpulse(ImpulseClass maximumImpulse) { + this.maximumImpulse = maximumImpulse; } @Override @@ -158,20 +160,8 @@ class MotorRowFilter extends RowFilter { } } - if (diameter != null) { - switch (diameterControl) { - default: - case ALL: - break; - case EXACT: - if ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015)) { - break; - } - return false; - case SMALLER: - if (m.getDiameter() <= diameter + 0.0004) { - break; - } + if ( maximumDiameter != null ) { + if ( m.getDiameter() >= maximumDiameter + 0.0004 ) { return false; } } @@ -199,11 +189,18 @@ class MotorRowFilter extends RowFilter { } private boolean filterByImpulseClass(ThrustCurveMotorSet m) { - for( ImpulseClass c : excludedImpulseClass ) { - if (c.isIn(m) ) { + if ( minimumImpulse != null ) { + if( m.getTotalImpuse() < minimumImpulse.getLow() ) { return false; } } + + if ( maximumImpulse != null ) { + if( m.getTotalImpuse() > maximumImpulse.getHigh() ) { + return false; + } + } + return true; } diff --git a/swing/src/net/sf/openrocket/gui/widgets/MultiSlider.java b/swing/src/net/sf/openrocket/gui/widgets/MultiSlider.java new file mode 100644 index 000000000..5b956c5a6 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/widgets/MultiSlider.java @@ -0,0 +1,553 @@ +package net.sf.openrocket.gui.widgets; +/* ------------------------------------------------------------------- + * GeoVISTA Center (Penn State, Dept. of Geography) + * + * Java source file for the class MultiSlider + * + * Copyright (c), 1999 - 2002, Masahiro Takatsuka and GeoVISTA Center + * All Rights Researved. + * + * Original Author: Masahiro Takatsuka + * $Author: eytanadar $ + * + * $Date: 2005/10/05 20:19:52 $ + * + * + * Reference: Document no: + * ___ ___ + * + * To Do: + * ___ + * + ------------------------------------------------------------------- */ + +/* --------------------------- Package ---------------------------- */ + +import java.awt.Color; + +import javax.accessibility.AccessibleContext; +import javax.accessibility.AccessibleState; +import javax.swing.BoundedRangeModel; +import javax.swing.DefaultBoundedRangeModel; +import javax.swing.JComponent; +import javax.swing.JSlider; +import javax.swing.plaf.SliderUI; + +/*==================================================================== + Implementation of class MultiSlider + ====================================================================*/ +/*** + * A component that lets the user graphically select values by slding + * multiple thumbs within a bounded interval. MultiSlider inherits all + * fields and methods from javax.swing.JSlider. + *

+ * + * @version $Revision: 1.1 $ + * @author Masahiro Takatsuka (masa@jbeans.net) + * @see JSlider + */ + +public class MultiSlider extends JSlider { + /*** + * @see #getUIClassID + * @see #readObject + */ + private static final String uiClassID = "MultiSliderUI"; + + /*** + * An array of data models that handle the numeric maximum values, + * minimum values, and current-position values for the multi slider. + */ + private BoundedRangeModel[] sliderModels; + + /*** + * If it is true, a thumb is bounded by adjacent thumbs. + */ + private boolean bounded = false; + + /*** + * This is a color to paint the current thumb + */ + private Color currentThumbColor = Color.red; + + transient private int valueBeforeStateChange; + + /*** + * Creates a slider with the specified orientation and the + * specified mimimum, maximum, and initial values. + * + * @exception IllegalArgumentException if orientation is not one of VERTICAL, HORIZONTAL + * + * @see #setOrientation + * @see #setMinimum + * @see #setMaximum + * @see #setValue + */ + public MultiSlider(int orientation, int min, int max, + int val1, int val2) { + checkOrientation(orientation); + this.orientation = orientation; + setNumberOfThumbs(min,max,new int[]{val1,val2}); + } + + /*** + * Creates a slider with the specified orientation and the + * specified mimimum, maximum, and the number of thumbs. + * + * @exception IllegalArgumentException if orientation is not one of VERTICAL, HORIZONTAL + * + * @see #setOrientation + * @see #setMinimum + * @see #setMaximum + * @see #setValue + */ + public MultiSlider(int orientation, int min, int max) { + checkOrientation(orientation); + this.orientation = orientation; + setNumberOfThumbs(min, max, 2); + } + + /*** + * Creates a horizontal slider with the range 0 to 100 and + * an intitial value of 50. + */ + public MultiSlider() { + this(HORIZONTAL, 0, 100); + } + + + /*** + * Creates a slider using the specified orientation with the + * range 0 to 100 and an intitial value of 50. + */ + public MultiSlider(int orientation) { + this(orientation, 0, 100); + } + + + /*** + * Creates a horizontal slider using the specified min and max + * with an intitial value of 50. + */ + public MultiSlider(int min, int max) { + this(HORIZONTAL, min, max); + } + + public void setCurrentThumbColor(Color c) { + this.currentThumbColor = c; + } + + public Color getCurrentThumbColor() { + return this.currentThumbColor; + } + + public int getTrackBuffer() { + return ((MultiSliderUI) this.ui).getTrackBuffer(); + } + + /*** + * Validates the orientation parameter. + */ + private void checkOrientation(int orientation) { + switch (orientation) { + case VERTICAL: + case HORIZONTAL: + break; + default: + throw new IllegalArgumentException("orientation must be one of: VERTICAL, HORIZONTAL"); + } + } + + /*** + * Notification from the UIFactory that the L&F has changed. + * Called to replace the UI with the latest version from the + * default UIFactory. + * + * @see JComponent#updateUI + */ + public void updateUI() { + updateLabelUIs(); + MultiSliderUI ui = new MultiSliderUI(); + if (this.sliderModels != null) { + ui.setThumbCount(this.sliderModels.length); + } + setUI((SliderUI) ui); + } + + /*** + * Returns the number of thumbs in the slider. + */ + public int getNumberOfThumbs() { + return this.sliderModels.length; + } + + /*** + * Sets the number of thumbs with the specified parameters. + */ + private void setNumberOfThumbs(int min, int max, int num, boolean useEndPoints) { + int [] values = createDefaultValues(min, max, num, useEndPoints); + setNumberOfThumbs(min, max, values); + } + + /*** + * Sets the number of thumbs with the specified parameters. + */ + private void setNumberOfThumbs(int min, int max, int num) { + setNumberOfThumbs(min, max, num, false); + } + + /*** + * Sets the number of thumbs with the specified parameters. + */ + private void setNumberOfThumbs(int min, int max, int[] values) { + if (values == null || values.length < 1) { + values = new int[] {50}; + } + int num = values.length; + this.sliderModels = new BoundedRangeModel[num]; + for (int i = 0; i < num; i++) { + this.sliderModels[i] = new DefaultBoundedRangeModel(values[i], 0, min, max); + this.sliderModels[i].addChangeListener(changeListener); + } + updateUI(); + } + + /*** + * Sets the number of thumbs. + */ + private void setNumberOfThumbs(int num) { + setNumberOfThumbs(num, false); + } + + /*** + * Sets the number of thumbs. + */ + private void setNumberOfThumbs(int num, boolean useEndPoints) { + if (getNumberOfThumbs() != num) { + setNumberOfThumbs(getMinimum(), getMaximum(), num, useEndPoints); + } + } + + /*** + * Sets the number of thumbs by specifying the initial values. + */ + private void setNumberOfThumbs(int[] values) { + setNumberOfThumbs(getMinimum(), getMaximum(), values); + } + + /*** + * creates evenly spaced values for thumbs. + */ + private int[] createDefaultValues(int min, int max, int num_of_values, boolean useEndPoints) { + int[] values = new int[num_of_values]; + int range = max - min; + + if (!useEndPoints) { + int step = range / (num_of_values + 1); + for (int i = 0; i < num_of_values; i++) { + values[i] = min + (i + 1) * step; + } + } else { + if (num_of_values < 1) { + return new int[0]; + } + values[0] = getMinimum(); + values[num_of_values - 1] = getMaximum(); + int[] def = createDefaultValues(getMinimum(), getMaximum(), num_of_values - 2, false); + for (int i = 0; i < def.length; i++) { + values[i + 1] = def[i]; + } + } + return values; + } + + /*** + * Returns the index number of currently operated thumb. + */ + public int getCurrentThumbIndex() { + return ((MultiSliderUI)ui).getCurrentIndex(); + } + + /*** + * Returns data model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #setModel + */ + public BoundedRangeModel getModel() { + return getModelAt(getCurrentThumbIndex()); + } + + /*** + * Returns data model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #setModel + */ + public BoundedRangeModel getModelAt(int index) { + if (this.sliderModels == null || index >= this.sliderModels.length) { + return null; + } + return this.sliderModels[index]; + } + + /*** + * Returns data model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #setModel + */ + public BoundedRangeModel[] getModels() { + return this.sliderModels; + } + + /*** + * Sets the model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #getModel + * @beaninfo + * bound: true + * description: The sliders BoundedRangeModel. + */ + public void setModel(BoundedRangeModel newModel) { + setModelAt(getCurrentThumbIndex(), newModel); + } + + /*** + * Sets the model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #getModel + * @beaninfo + * bound: true + * description: The sliders BoundedRangeModel. + */ + public void setModelAt(int index, BoundedRangeModel newModel) { + BoundedRangeModel oldModel = getModelAt(index); + + if (oldModel != null) { + oldModel.removeChangeListener(changeListener); + } + + this.sliderModels[index] = newModel; + + if (newModel != null) { + newModel.addChangeListener(changeListener); + + if (accessibleContext != null) { + accessibleContext.firePropertyChange( + AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, + (oldModel == null + ? null : new Integer(oldModel.getValue())), + (newModel == null + ? null : new Integer(newModel.getValue()))); + } + } + + firePropertyChange("model", oldModel, this.sliderModels[index]); + } + + /*** + * Sets the models minimum property. + * + * @see #getMinimum + * @see BoundedRangeModel#setMinimum + * @beaninfo + * bound: true + * preferred: true + * description: The sliders minimum value. + */ + public void setMinimum(int minimum) { + int count = getNumberOfThumbs(); + int oldMin = getModel().getMinimum(); + for (int i = 0; i < count; i++) { + getModelAt(i).setMinimum(minimum); + } + firePropertyChange( "minimum", new Integer( oldMin ), new Integer( minimum ) ); + } + + /*** + * Sets the models maximum property. + * + * @see #getMaximum + * @see BoundedRangeModel#setMaximum + * @beaninfo + * bound: true + * preferred: true + * description: The sliders maximum value. + */ + public void setMaximum(int maximum) { + int count = getNumberOfThumbs(); + int oldMax = getModel().getMaximum(); + for (int i = 0; i < count; i++) { + getModelAt(i).setMaximum(maximum); + } + firePropertyChange( "maximum", new Integer( oldMax ), new Integer( maximum ) ); + } + + /*** + * Returns the sliders value. + * @return the models value property + * @see #setValue + */ + public int getValue() { + return getValueAt(getCurrentThumbIndex()); + } + + /*** + * Returns the sliders value. + * @return the models value property + * @see #setValue + */ + public int getValueAt(int index) { + return getModelAt(index).getValue(); + } + + /*** + * Sets the sliders current value. This method just forwards + * the value to the model. + * + * @see #getValue + * @beaninfo + * preferred: true + * description: The sliders current value. + */ + public void setValue(int n) { + setValueAt(getCurrentThumbIndex(), n); + } + + /*** + * Sets the sliders current value. This method just forwards + * the value to the model. + * + * @see #getValue + * @beaninfo + * preferred: true + * description: The sliders current value. + */ + public void setValueAt(int index, int n) { + BoundedRangeModel m = getModelAt(index); + int oldValue = m.getValue(); + m.setValue(n); + + if (accessibleContext != null) { + accessibleContext.firePropertyChange( + AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, + new Integer(oldValue), + new Integer(m.getValue())); + } + } + + /*** + * True if the slider knob is being dragged. + * + * @return the value of the models valueIsAdjusting property + * @see #setValueIsAdjusting + */ + public boolean getValueIsAdjusting() { + boolean result = false; + int count = getNumberOfThumbs(); + for (int i = 0; i < count; i++) { + result = (result || getValueIsAdjustingAt(i)); + } + return result; + } + + /*** + * True if the slider knob is being dragged. + */ + public boolean getValueIsAdjustingAt(int index) { + return getModelAt(index).getValueIsAdjusting(); + } + + /*** + * Sets the models valueIsAdjusting property. Slider look and + * feel implementations should set this property to true when + * a knob drag begins, and to false when the drag ends. The + * slider model will not generate ChangeEvents while + * valueIsAdjusting is true. + * + * @see #getValueIsAdjusting + * @see BoundedRangeModel#setValueIsAdjusting + * @beaninfo + * expert: true + * description: True if the slider knob is being dragged. + */ + public void setValueIsAdjusting(boolean b) { + setValueIsAdjustingAt(getCurrentThumbIndex(), b); + } + + /*** + * Sets the models valueIsAdjusting property. Slider look and + * feel implementations should set this property to true when + * a knob drag begins, and to false when the drag ends. The + * slider model will not generate ChangeEvents while + * valueIsAdjusting is true. + */ + public void setValueIsAdjustingAt(int index, boolean b) { + BoundedRangeModel m = getModelAt(index); + boolean oldValue = m.getValueIsAdjusting(); + m.setValueIsAdjusting(b); + + if ((oldValue != b) && (accessibleContext != null)) { + accessibleContext.firePropertyChange( + AccessibleContext.ACCESSIBLE_STATE_PROPERTY, + ((oldValue) ? AccessibleState.BUSY : null), + ((b) ? AccessibleState.BUSY : null)); + } + } + + /*** + * Sets the size of the range "covered" by the knob. Most look + * and feel implementations will change the value by this amount + * if the user clicks on either side of the knob. + * + * @see #getExtent + * @see BoundedRangeModel#setExtent + * @beaninfo + * expert: true + * description: Size of the range covered by the knob. + */ + public void setExtent(int extent) { + int count = getNumberOfThumbs(); + for (int i = 0; i < count; i++) { + getModelAt(i).setExtent(extent); + } + } + + + /*** + * Sets a bounded attribute of a slider thumb. + *

+     * 
+ * + * @param b + * @return void + */ + public void setBounded(boolean b) { + this.bounded = b; + } + + /*** + * Returns a bounded attribute of a slider thumb. + *
+     * 
+ * + * @return boolean + */ + public boolean isBounded() { + return this.bounded; + } + + public int getValueBeforeStateChange() { + return this.valueBeforeStateChange; + } + + void setValueBeforeStateChange(int v) { + this.valueBeforeStateChange = v; + } +} + + + diff --git a/swing/src/net/sf/openrocket/gui/widgets/MultiSliderUI.java b/swing/src/net/sf/openrocket/gui/widgets/MultiSliderUI.java new file mode 100644 index 000000000..5bbfa31d4 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/widgets/MultiSliderUI.java @@ -0,0 +1,612 @@ +package net.sf.openrocket.gui.widgets; +/* ------------------------------------------------------------------- + * GeoVISTA Center (Penn State, Dept. of Geography) + * + * Java source file for the class MultiSliderUI + * + * Copyright (c), 1999 - 2002, Masahiro Takatsuka and GeoVISTA Center + * All Rights Researved. + * + * Original Author: Masahiro Takatsuka + * $Author: eytanadar $ + * + * $Date: 2005/10/05 20:19:52 $ + * + * + * Reference: Document no: + * ___ ___ + * + * To Do: + * ___ + * +------------------------------------------------------------------- */ + +/* --------------------------- Package ---------------------------- */ +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; + +import javax.swing.AbstractAction; +import javax.swing.BoundedRangeModel; +import javax.swing.JComponent; +import javax.swing.JSlider; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.basic.BasicSliderUI; +import javax.swing.plaf.metal.MetalSliderUI; + +/*==================================================================== + Implementation of class MultiSliderUI + ====================================================================*/ +/*** + * A Basic L&F implementation of SliderUI. + * + * @version $Revision: 1.1 $ + * @author Masahiro Takatsuka (masa@jbeans.net) + * @see MetalSliderUI + */ + +class MultiSliderUI extends BasicSliderUI { + private Rectangle[] thumbRects = null; + + private int thumbCount; + transient private int currentIndex = 0; + + transient private boolean isDragging; + transient private int[] minmaxIndices = new int[2]; + + /*** + * ComponentUI Interface Implementation methods + */ + public static ComponentUI createUI(JComponent b) { + return new MultiSliderUI(); + } + + /*** + * Construct a new MultiSliderUI object. + */ + public MultiSliderUI() { + super(null); + } + + int getTrackBuffer() { + return this.trackBuffer; + } + + /*** + * Sets the number of Thumbs. + */ + public void setThumbCount(int count) { + this.thumbCount = count; + } + + /*** + * Returns the index number of the thumb currently operated. + */ + protected int getCurrentIndex() { + return this.currentIndex; + } + + public void installUI(JComponent c) { + this.thumbRects = new Rectangle[this.thumbCount]; + for (int i = 0; i < this.thumbCount; i++) { + this.thumbRects[i] = new Rectangle(); + } + this.currentIndex = 0; + if (this.thumbCount > 0) { + thumbRect = this.thumbRects[this.currentIndex]; + } + super.installUI(c); + } + + public void uninstallUI(JComponent c) { + super.uninstallUI(c); + for (int i = 0; i < this.thumbCount; i++) { + this.thumbRects[i] = null; + } + this.thumbRects = null; + } + + protected void installListeners( JSlider slider ) { + slider.addMouseListener(trackListener); + slider.addMouseMotionListener(trackListener); + slider.addFocusListener(focusListener); + slider.addComponentListener(componentListener); + slider.addPropertyChangeListener( propertyChangeListener ); + for (int i = 0; i < this.thumbCount; i++) { + ((MultiSlider)slider).getModelAt(i).addChangeListener(changeListener); + } + } + + protected void uninstallListeners( JSlider slider ) { + slider.removeMouseListener(trackListener); + slider.removeMouseMotionListener(trackListener); + slider.removeFocusListener(focusListener); + slider.removeComponentListener(componentListener); + slider.removePropertyChangeListener( propertyChangeListener ); + for (int i = 0; i < this.thumbCount; i++) { + BoundedRangeModel model = ((MultiSlider)slider).getModelAt(i); + if (model != null) { + model.removeChangeListener(changeListener); + } + } + } + + protected void calculateThumbSize() { + Dimension size = getThumbSize(); + for (int i = 0; i < this.thumbCount; i++) { + this.thumbRects[i].setSize(size.width, size.height); + } + thumbRect.setSize(size.width, size.height); + } + + protected void calculateThumbLocation() { + MultiSlider slider = (MultiSlider) this.slider; + int majorTickSpacing = slider.getMajorTickSpacing(); + int minorTickSpacing = slider.getMinorTickSpacing(); + int tickSpacing = 0; + + if (minorTickSpacing > 0) { + tickSpacing = minorTickSpacing; + } else if (majorTickSpacing > 0) { + tickSpacing = majorTickSpacing; + } + for (int i = 0; i < this.thumbCount; i++) { + if (slider.getSnapToTicks()) { + int sliderValue = slider.getValueAt(i); + int snappedValue = sliderValue; + if (tickSpacing != 0) { + // If it's not on a tick, change the value + if ((sliderValue - slider.getMinimum()) % tickSpacing != 0 ) { + float temp = (float)(sliderValue - slider.getMinimum()) / (float)tickSpacing; + int whichTick = Math.round(temp); + snappedValue = slider.getMinimum() + (whichTick * tickSpacing); + } + + if( snappedValue != sliderValue ) { + slider.setValueAt(i, snappedValue); + } + } + } + + if (slider.getOrientation() == JSlider.HORIZONTAL) { + int valuePosition = xPositionForValue(slider.getValueAt(i)); + this.thumbRects[i].x = valuePosition - (this.thumbRects[i].width / 2); + this.thumbRects[i].y = trackRect.y; + } else { + int valuePosition = yPositionForValue(slider.getValueAt(i)); + this.thumbRects[i].x = trackRect.x; + this.thumbRects[i].y = valuePosition - (this.thumbRects[i].height / 2); + } + } + } + + public void paint(Graphics g, JComponent c) { + recalculateIfInsetsChanged(); + recalculateIfOrientationChanged(); + Rectangle clip = g.getClipBounds(); + + if (slider.getPaintTrack() && clip.intersects(trackRect)) { + paintTrack( g ); + } + if (slider.getPaintTicks() && clip.intersects(tickRect)) { + paintTicks( g ); + } + if (slider.getPaintLabels() && clip.intersects(labelRect)) { + paintLabels( g ); + } + if (slider.hasFocus() && clip.intersects(focusRect)) { + paintFocus( g ); + } + + // first paint unfocused thumbs. + for (int i = 0; i < this.thumbCount; i++) { + if (i != this.currentIndex) { + if (clip.intersects(this.thumbRects[i])) { + thumbRect = this.thumbRects[i]; + paintThumb(g); + } + } + } + // then paint currently focused thumb. + if (clip.intersects(this.thumbRects[this.currentIndex])) { + thumbRect = this.thumbRects[this.currentIndex]; + paintThumb(g); + } + } + + public void paintThumb(Graphics g) { + super.paintThumb(g); + } + + public void paintTrack(Graphics g) { + super.paintTrack(g); + } + + public void scrollByBlock(int direction) { + synchronized(slider) { + int oldValue = ((MultiSlider)slider).getValueAt(this.currentIndex); + int blockIncrement = slider.getMaximum() / 10; + int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL); + ((MultiSlider)slider).setValueAt(this.currentIndex, oldValue + delta); + } + } + + public void scrollByUnit(int direction) { + synchronized(slider) { + int oldValue = ((MultiSlider)slider).getValueAt(this.currentIndex); + int delta = 1 * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL); + ((MultiSlider)slider).setValueAt(this.currentIndex, oldValue + delta); + } + } + + protected TrackListener createTrackListener( JSlider slider ) { + return new MultiTrackListener(); + } + + /*** + * Track Listener Class tracks mouse movements. + */ + class MultiTrackListener extends BasicSliderUI.TrackListener { + int _trackTop; + int _trackBottom; + int _trackLeft; + int _trackRight; + transient private int[] firstXY = new int[2]; + + /*** + * If the mouse is pressed above the "thumb" component + * then reduce the scrollbars value by one page ("page up"), + * otherwise increase it by one page. If there is no + * thumb then page up if the mouse is in the upper half + * of the track. + */ + public void mousePressed(MouseEvent e) { + int[] neighbours = new int[2]; + boolean bounded = ((MultiSlider)slider).isBounded(); + if (!slider.isEnabled()) { + return; + } + + currentMouseX = e.getX(); + currentMouseY = e.getY(); + firstXY[0] = currentMouseX; + firstXY[1] = currentMouseY; + + slider.requestFocus(); + // Clicked in the Thumb area? + minmaxIndices[0] = -1; + minmaxIndices[1] = -1; + for (int i = 0; i < MultiSliderUI.this.thumbCount; i++) { + if (MultiSliderUI.this.thumbRects[i].contains(currentMouseX, currentMouseY)) { + if (minmaxIndices[0] == -1) { + minmaxIndices[0] = i; + MultiSliderUI.this.currentIndex = i; + } + if (minmaxIndices[1] < i) { + minmaxIndices[1] = i; + } + switch (slider.getOrientation()) { + case JSlider.VERTICAL: + offset = currentMouseY - MultiSliderUI.this.thumbRects[i].y; + break; + case JSlider.HORIZONTAL: + offset = currentMouseX - MultiSliderUI.this.thumbRects[i].x; + break; + } + MultiSliderUI.this.isDragging = true; + thumbRect = MultiSliderUI.this.thumbRects[i]; + if (bounded) { + neighbours[0] = ((i - 1) < 0) ? -1 : (i - 1); + neighbours[1] = ((i + 1) >= MultiSliderUI.this.thumbCount) ? -1 : (i + 1); + //findClosest(currentMouseX, currentMouseY, neighbours, i); + } else { + MultiSliderUI.this.currentIndex = i; + ((MultiSlider)slider).setValueIsAdjustingAt(i, true); + neighbours[0] = -1; + neighbours[1] = -1; + } + setThumbBounds(neighbours); + //return; + } + } + if (minmaxIndices[0] > -1) { + return; + } + + MultiSliderUI.this.currentIndex = findClosest(currentMouseX, currentMouseY, neighbours, -1); + thumbRect = MultiSliderUI.this.thumbRects[MultiSliderUI.this.currentIndex]; + MultiSliderUI.this.isDragging = false; + ((MultiSlider)slider).setValueIsAdjustingAt(MultiSliderUI.this.currentIndex, true); + + Dimension sbSize = slider.getSize(); + int direction = POSITIVE_SCROLL; + + switch (slider.getOrientation()) { + case JSlider.VERTICAL: + if (thumbRect.isEmpty()) { + int scrollbarCenter = sbSize.height / 2; + if (!drawInverted()) { + direction = (currentMouseY < scrollbarCenter) ? POSITIVE_SCROLL : NEGATIVE_SCROLL; + } else { + direction = (currentMouseY < scrollbarCenter) ? NEGATIVE_SCROLL : POSITIVE_SCROLL; + } + } else { + int thumbY = thumbRect.y; + if (!drawInverted()) { + direction = (currentMouseY < thumbY) ? POSITIVE_SCROLL : NEGATIVE_SCROLL; + } else { + direction = (currentMouseY < thumbY) ? NEGATIVE_SCROLL : POSITIVE_SCROLL; + } + } + break; + case JSlider.HORIZONTAL: + if (thumbRect.isEmpty() ) { + int scrollbarCenter = sbSize.width / 2; + if (!drawInverted()) { + direction = (currentMouseX < scrollbarCenter) ? NEGATIVE_SCROLL : POSITIVE_SCROLL; + } else { + direction = (currentMouseX < scrollbarCenter) ? POSITIVE_SCROLL : NEGATIVE_SCROLL; + } + } else { + int thumbX = thumbRect.x; + if (!drawInverted()) { + direction = (currentMouseX < thumbX) ? NEGATIVE_SCROLL : POSITIVE_SCROLL; + } else { + direction = (currentMouseX < thumbX) ? POSITIVE_SCROLL : NEGATIVE_SCROLL; + } + } + break; + } + scrollDueToClickInTrack(direction); + Rectangle r = thumbRect; + if ( !r.contains(currentMouseX, currentMouseY) ) { + if (shouldScroll(direction) ) { + scrollTimer.stop(); + scrollListener.setDirection(direction); + scrollTimer.start(); + } + } + } + + /*** + * Sets a track bound for th thumb currently operated. + */ + private void setThumbBounds(int[] neighbours) { + int halfThumbWidth = thumbRect.width / 2; + int halfThumbHeight = thumbRect.height / 2; + + switch (slider.getOrientation()) { + case JSlider.VERTICAL: + _trackTop = (neighbours[1] == -1) ? trackRect.y : MultiSliderUI.this.thumbRects[neighbours[1]].y + halfThumbHeight; + _trackBottom = (neighbours[0] == -1) ? trackRect.y + (trackRect.height - 1) : MultiSliderUI.this.thumbRects[neighbours[0]].y + halfThumbHeight; + break; + case JSlider.HORIZONTAL: + _trackLeft = (neighbours[0] == -1) ? trackRect.x : MultiSliderUI.this.thumbRects[neighbours[0]].x + halfThumbWidth; + _trackRight = (neighbours[1] == -1) ? trackRect.x + (trackRect.width - 1) : MultiSliderUI.this.thumbRects[neighbours[1]].x + halfThumbWidth; + break; + } + } + + /* + * this is a very lazy way to find the closest. One might want to + * implement a much faster algorithm. + */ + private int findClosest(int x, int y, int[] neighbours, int excluded) { + int orientation = slider.getOrientation(); + int rightmin = Integer.MAX_VALUE; // for dxw, dy + int leftmin = -Integer.MAX_VALUE; // for dx, dyh + int dx = 0; + int dxw = 0; + int dy = 0; + int dyh = 0; + neighbours[0] = -1; // left + neighbours[1] = -1; // right + for (int i = 0; i < MultiSliderUI.this.thumbCount; i++) { + if (i == excluded) { + continue; + } + switch (orientation) { + case JSlider.VERTICAL: + dy = MultiSliderUI.this.thumbRects[i].y - y; + dyh = (MultiSliderUI.this.thumbRects[i].y + MultiSliderUI.this.thumbRects[i].height) - y; + if (dyh <= 0) { + if (dyh > leftmin) { // has to be > and not >= + leftmin = dyh; + neighbours[0] = i; + } + } + if (dy >= 0) { + if (dy <= rightmin) { + rightmin = dy; + neighbours[1] = i; + } + } + break; + case JSlider.HORIZONTAL: + dx = MultiSliderUI.this.thumbRects[i].x - x; + dxw = (MultiSliderUI.this.thumbRects[i].x + MultiSliderUI.this.thumbRects[i].width) - x; + if (dxw <= 0) { + if (dxw >= leftmin) { + leftmin = dxw; + neighbours[0] = i; + } + } + if (dx >= 0) { + if (dx < rightmin) { // has to be < and not <= + rightmin = dx; + neighbours[1] = i; + } + } + break; + } + } + //System.out.println("neighbours = " + neighbours[0] + ", " + neighbours[1]); + int closest = (Math.abs(leftmin) <= Math.abs(rightmin)) ? neighbours[0] : neighbours[1]; + return (closest == -1) ? 0 : closest; + } + + /*** + * Set the models value to the position of the top/left + * of the thumb relative to the origin of the track. + */ + public void mouseDragged( MouseEvent e ) { + ((MultiSlider) MultiSliderUI.this.slider).setValueBeforeStateChange(((MultiSlider) MultiSliderUI.this.slider).getValueAt(MultiSliderUI.this.currentIndex)); + int thumbMiddle = 0; + boolean bounded = ((MultiSlider)slider).isBounded(); + + if (!slider.isEnabled()) { + return; + } + + currentMouseX = e.getX(); + currentMouseY = e.getY(); + + if (! MultiSliderUI.this.isDragging) { + return; + } + + switch (slider.getOrientation()) { + case JSlider.VERTICAL: + int halfThumbHeight = thumbRect.height / 2; + int thumbTop = e.getY() - offset; + if (bounded) { + int[] neighbours = new int[2]; + int idx = -1; + int diff = e.getY() - firstXY[1]; + //System.out.println("diff = " + diff); + if (e.getY() - firstXY[1] > 0) { + idx = minmaxIndices[0]; + } else { + idx = minmaxIndices[1]; + } + minmaxIndices[0] = minmaxIndices[1] = idx; + //System.out.println("idx = " + idx); + if (idx == -1) { + break; + } + + //System.out.println("thumbTop = " + thumbTop); + neighbours[0] = ((idx - 1) < 0) ? -1 : (idx - 1); + neighbours[1] = ((idx + 1) >= MultiSliderUI.this.thumbCount) ? -1 : (idx + 1); + thumbRect = MultiSliderUI.this.thumbRects[idx]; + MultiSliderUI.this.currentIndex = idx; + ((MultiSlider)slider).setValueIsAdjustingAt(idx, true); + setThumbBounds(neighbours); + } + + thumbTop = Math.max(thumbTop, _trackTop - halfThumbHeight); + thumbTop = Math.min(thumbTop, _trackBottom - halfThumbHeight); + + setThumbLocation(thumbRect.x, thumbTop); + + thumbMiddle = thumbTop + halfThumbHeight; + ((MultiSlider)slider).setValueAt(MultiSliderUI.this.currentIndex, valueForYPosition(thumbMiddle) ); + break; + case JSlider.HORIZONTAL: + int halfThumbWidth = thumbRect.width / 2; + int thumbLeft = e.getX() - offset; + if (bounded) { + int[] neighbours = new int[2]; + int idx = -1; + if (e.getX() - firstXY[0] <= 0) { + idx = minmaxIndices[0]; + } else { + idx = minmaxIndices[1]; + } + minmaxIndices[0] = minmaxIndices[1] = idx; + //System.out.println("idx = " + idx); + if (idx == -1) { + break; + } + //System.out.println("thumbLeft = " + thumbLeft); + neighbours[0] = ((idx - 1) < 0) ? -1 : (idx - 1); + neighbours[1] = ((idx + 1) >= MultiSliderUI.this.thumbCount) ? -1 : (idx + 1); + thumbRect = MultiSliderUI.this.thumbRects[idx]; + MultiSliderUI.this.currentIndex = idx; + ((MultiSlider)slider).setValueIsAdjustingAt(idx, true); + setThumbBounds(neighbours); + } + + thumbLeft = Math.max(thumbLeft, _trackLeft - halfThumbWidth); + thumbLeft = Math.min(thumbLeft, _trackRight - halfThumbWidth); + + setThumbLocation(thumbLeft, thumbRect.y); + + thumbMiddle = thumbLeft + halfThumbWidth; + + ((MultiSlider)slider).setValueAt(MultiSliderUI.this.currentIndex, valueForXPosition(thumbMiddle)); + break; + default: + return; + } + } + + public void mouseReleased(MouseEvent e) { + if (!slider.isEnabled()) { + return; + } + + offset = 0; + scrollTimer.stop(); + + if (slider.getSnapToTicks()) { + MultiSliderUI.this.isDragging = false; + ((MultiSlider)slider).setValueIsAdjustingAt(MultiSliderUI.this.currentIndex, false); + } else { + ((MultiSlider)slider).setValueIsAdjustingAt(MultiSliderUI.this.currentIndex, false); + MultiSliderUI.this.isDragging = false; + } + + slider.repaint(); + } + } + + /*** + * A static version of the above. + */ + static class SharedActionScroller extends AbstractAction { + int _dir; + boolean _block; + + public SharedActionScroller(int dir, boolean block) { + _dir = dir; + _block = block; + } + + public void actionPerformed(ActionEvent e) { + JSlider slider = (JSlider)e.getSource(); + MultiSliderUI ui = (MultiSliderUI)slider.getUI(); + if ( _dir == NEGATIVE_SCROLL || _dir == POSITIVE_SCROLL ) { + int realDir = _dir; + if (slider.getInverted()) { + realDir = _dir == NEGATIVE_SCROLL ? POSITIVE_SCROLL : NEGATIVE_SCROLL; + } + if (_block) { + ui.scrollByBlock(realDir); + } else { + ui.scrollByUnit(realDir); + } + } else { + if (slider.getInverted()) { + if (_dir == MIN_SCROLL) { + ((MultiSlider)slider).setValueAt(ui.currentIndex, + slider.getMaximum()); + } else if (_dir == MAX_SCROLL) { + ((MultiSlider)slider).setValueAt(ui.currentIndex, + slider.getMinimum()); + } + } else { + if (_dir == MIN_SCROLL) { + ((MultiSlider)slider).setValueAt(ui.currentIndex, + slider.getMinimum()); + } else if (_dir == MAX_SCROLL) { + ((MultiSlider)slider).setValueAt(ui.currentIndex, + slider.getMaximum()); + } + } + } + } + } +} From 3e57ef205d9ee67b953658ea36c7fb5b342aeaf0 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Mon, 7 Oct 2013 22:18:22 -0500 Subject: [PATCH 32/47] Be smart about displaying decimals. --- core/src/net/sf/openrocket/unit/Unit.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/net/sf/openrocket/unit/Unit.java b/core/src/net/sf/openrocket/unit/Unit.java index 7ac073a8d..a0522c2ba 100644 --- a/core/src/net/sf/openrocket/unit/Unit.java +++ b/core/src/net/sf/openrocket/unit/Unit.java @@ -103,6 +103,10 @@ public abstract class Unit { } val = roundForDecimalFormat(val); + // Check for approximate integer + if (Math.abs(val - Math.floor(val)) < 0.001) { + return intFormat.format(val); + } return decFormat.format(val); } From 2127908b2a2a3406fe1471f585c8bb020c46f30b Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Mon, 7 Oct 2013 22:19:07 -0500 Subject: [PATCH 33/47] Wire up motor length filter using ChangeListener and DoubleModel. --- core/resources/l10n/messages.properties | 8 +- .../motor/thrustcurve/MotorFilterPanel.java | 86 ++++++++++++------- .../motor/thrustcurve/MotorRowFilter.java | 65 ++++++++++---- 3 files changed, 109 insertions(+), 50 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 09e96ae34..82dbfbc26 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1091,11 +1091,9 @@ TCMotorSelPan.checkbox.hideUsed = Hide motors already used in the mount TCMotorSelPan.btn.details = Show Details TCMotorSelPan.btn.filter = Filter Motors TCMotorSelPan.MotorSize = Motor Dimensions -TCMotorSelPan.limitByLength = Limit motors to motor mount length -TCMotorSelPan.SHOW_DESCRIPTIONS.desc1 = Show all motors -TCMotorSelPan.SHOW_DESCRIPTIONS.desc2 = Show motors with diameter less than that of the motor mount -TCMotorSelPan.SHOW_DESCRIPTIONS.desc3 = Show motors with diameter equal to that of the motor mount -TCMotorSelPan.lbl.Motormountdia = Motor mount diameter: +TCMotorSelPan.Diameter = Daimeter +TCMotorSelPan.Length = Length +TCMotorSelPan.MotorMountDimensions = Motor mount dimensions: TCMotorSelPan.lbl.Search = Search: TCMotorSelPan.lbl.Selectthrustcurve = Select thrust curve: TCMotorSelPan.lbl.Ejectionchargedelay = Ejection charge delay: diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java index 0cd62ef92..485c693a4 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java @@ -3,7 +3,6 @@ package net.sf.openrocket.gui.dialogs.motor.thrustcurve; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -11,12 +10,9 @@ import java.util.Hashtable; import java.util.List; import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; import javax.swing.JButton; -import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.border.TitledBorder; @@ -28,7 +24,6 @@ import javax.swing.event.ListDataListener; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.gui.SpinnerEditor; import net.sf.openrocket.gui.adaptors.DoubleModel; -import net.sf.openrocket.gui.components.BasicSlider; import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.gui.util.CheckList; import net.sf.openrocket.gui.util.GUIUtil; @@ -84,11 +79,10 @@ public abstract class MotorFilterPanel extends JPanel { private final MotorRowFilter filter; // Things we change the label on based on the MotorMount. - private final JCheckBox maximumLengthCheckBox; + private final JLabel motorMountDimension; + private final MultiSlider lengthSlider; private final MultiSlider diameterSlider; - private Double mountLength; - public MotorFilterPanel(Collection allManufacturers, MotorRowFilter filter ) { super(new MigLayout("fill", "[grow]")); this.filter = filter; @@ -188,16 +182,19 @@ public abstract class MotorFilterPanel extends JPanel { sub.add( impulseSlider, "growx, wrap"); } this.add(sub,"grow, wrap"); - - + + // Diameter selection sub = new JPanel(new MigLayout("fill")); TitledBorder diameterTitleBorder = BorderFactory.createTitledBorder(trans.get("TCMotorSelPan.MotorSize")); GUIUtil.changeFontStyle(diameterTitleBorder, Font.BOLD); sub.setBorder(diameterTitleBorder); + motorMountDimension = new JLabel(); + GUIUtil.changeFontSize(motorMountDimension, -1); + sub.add(motorMountDimension,"growx,wrap"); { - sub.add( new JLabel("Minimum diameter"), "split 2, wrap"); + sub.add( new JLabel("Diameter"), "split 2, wrap"); diameterSlider = new MultiSlider(MultiSlider.HORIZONTAL,0, diameterValues.length-1, 0, diameterValues.length-1); diameterSlider.setBounded(true); // thumbs cannot cross diameterSlider.setMajorTickSpacing(1); @@ -222,20 +219,49 @@ public abstract class MotorFilterPanel extends JPanel { } { - maximumLengthCheckBox = new JCheckBox(trans.get("TCMotorSelPan.limitByLength")); - maximumLengthCheckBox.addChangeListener( new ChangeListener() { + sub.add( new JLabel(trans.get("TCMotorSelPan.Length")), "split 2, wrap"); + + final DoubleModel minimumLength = new DoubleModel(filter, "MinimumLength", UnitGroup.UNITS_MOTOR_DIMENSIONS, 0); + final DoubleModel maximumLength = new DoubleModel(filter, "MaximumLength", UnitGroup.UNITS_MOTOR_DIMENSIONS, 0); + + JSpinner spin = new JSpinner(minimumLength.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin, "split 5, growx"); + + sub.add(new UnitSelector(minimumLength), ""); + + lengthSlider = new MultiSlider(MultiSlider.HORIZONTAL,0, 1000, 0, 1000); + lengthSlider.setBounded(true); // thumbs cannot cross + lengthSlider.setMajorTickSpacing(100); + lengthSlider.setPaintTicks(true); + lengthSlider.setLabelTable(diameterLabels); + lengthSlider.addChangeListener( new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { - if (maximumLengthCheckBox.isSelected() ) { - MotorFilterPanel.this.filter.setMaximumLength( mountLength ); - } else { - MotorFilterPanel.this.filter.setMaximumLength(null); - } + int minLength = lengthSlider.getValueAt(0); + minimumLength.setValue(minLength/1000.0); + int maxLength = lengthSlider.getValueAt(1); + maximumLength.setValue(maxLength/1000.0); onSelectionChanged(); } + }); + + minimumLength.addChangeListener( new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + lengthSlider.setValueAt(0, (int)(1000* minimumLength.getValue())); + lengthSlider.setValueAt(1, (int) (1000* maximumLength.getValue())); + } }); - sub.add(maximumLengthCheckBox); + + sub.add( lengthSlider, "growx"); + + spin = new JSpinner(maximumLength.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + sub.add(spin, "growx"); + + sub.add(new UnitSelector(maximumLength), "wrap"); } this.add(sub, "grow,wrap"); @@ -246,27 +272,27 @@ public abstract class MotorFilterPanel extends JPanel { onSelectionChanged(); if ( mount == null ) { // Disable diameter controls? - maximumLengthCheckBox.setText("Limit by length"); - mountLength = null; + lengthSlider.setValueAt(1, 1000); + motorMountDimension.setText(""); } else { - mountLength = ((RocketComponent)mount).getLength(); + double mountLength = ((RocketComponent)mount).getLength(); + lengthSlider.setValueAt(1, (int) Math.min(1000,Math.round(1000*mountLength))); + double mountDiameter = mount.getMotorMountDiameter(); // find the next largest diameter int i; for( i =0; i< diameterValues.length; i++ ) { - if ( mountDiameter<= diameterValues[i] ) { + if ( mountDiameter< diameterValues[i] ) { break; } } - if( i >= diameterValues.length-1 ) { - diameterSlider.setValueAt(1, diameterValues.length-1); - } else { - diameterSlider.setValueAt(1, i) ; + if (i >= diameterValues.length ) { + i--; } - diameterSlider.setValueAt(1, i); - maximumLengthCheckBox.setText("Limit by length" - + " (" + UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(((RocketComponent)mount).getLength()) +")"); + diameterSlider.setValueAt(1, i-1); + motorMountDimension.setText( trans.get("TCMotorSelPan.MotorMountDimensions") + " " + + UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountDiameter)+ "x" + UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountLength)); } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java index ca6dbeed7..b4ac85e9f 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorRowFilter.java @@ -14,25 +14,32 @@ import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.rocketcomponent.MotorConfiguration; import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.util.AbstractChangeSource; +import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.StateChangeListener; //////// Row filters /** * Abstract adapter class. */ -class MotorRowFilter extends RowFilter { +public class MotorRowFilter extends RowFilter implements ChangeSource { // configuration data used in the filter process private final ThrustCurveMotorDatabaseModel model; private List usedMotors = new ArrayList(); + private final AbstractChangeSource changeSourceDelegate = new AbstractChangeSource(); + private final Object change = new Object(); + // things which can be changed to modify filter behavior - private Double maximumLength; + // Limit motors based on length + private double minimumLength = 0; + private double maximumLength = Double.MAX_VALUE; - // Limit motors based on minimum diameter + // Limit motors based on diameter private Double minimumDiameter; - private Double maximumDiameter; // Collection of strings which match text in the motor @@ -47,7 +54,7 @@ class MotorRowFilter extends RowFilter { // Impulse class filtering private ImpulseClass minimumImpulse; private ImpulseClass maximumImpulse; - + public MotorRowFilter(ThrustCurveMotorDatabaseModel model) { super(); @@ -72,12 +79,26 @@ class MotorRowFilter extends RowFilter { } } - Double getMaximumLength() { + public double getMinimumLength() { + return minimumLength; + } + + public void setMinimumLength(double minimumLength) { + if ( this.minimumLength != minimumLength ) { + this.minimumLength = minimumLength; + fireChangeEvent(change); + } + } + + public double getMaximumLength() { return maximumLength; } - void setMaximumLength(Double maximumLength) { - this.maximumLength = maximumLength; + public void setMaximumLength(double maximumLength) { + if ( this.maximumLength != maximumLength ) { + this.maximumLength = maximumLength; + fireChangeEvent(change); + } } Double getMinimumDiameter() { @@ -165,13 +186,15 @@ class MotorRowFilter extends RowFilter { return false; } } - - if ( maximumLength != null ) { - if ( m.getLength() > maximumLength ) { - return false; - } + + if ( m.getLength() > maximumLength ) { + return false; } - + + if ( m.getLength() < minimumLength ) { + return false; + } + return true; } @@ -194,7 +217,7 @@ class MotorRowFilter extends RowFilter { return false; } } - + if ( maximumImpulse != null ) { if( m.getTotalImpuse() > maximumImpulse.getHigh() ) { return false; @@ -204,4 +227,16 @@ class MotorRowFilter extends RowFilter { return true; } + public final void addChangeListener(StateChangeListener listener) { + changeSourceDelegate.addChangeListener(listener); + } + + public final void removeChangeListener(StateChangeListener listener) { + changeSourceDelegate.removeChangeListener(listener); + } + + public void fireChangeEvent(Object source) { + changeSourceDelegate.fireChangeEvent(source); + } + } From 9420390ea8a2e21fc517ec9ff6add0c50d6699db Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Tue, 8 Oct 2013 21:08:05 -0500 Subject: [PATCH 34/47] Final tweaking of motor filter in the MotorChooserPanel. --- .../gui/dialogs/motor/MotorChooserDialog.java | 2 +- .../motor/thrustcurve/MotorFilterPanel.java | 18 +++- .../ThrustCurveMotorSelectionPanel.java | 85 ++++++++----------- 3 files changed, 53 insertions(+), 52 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java index a6718116f..5ff870e1b 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java @@ -39,7 +39,7 @@ public class MotorChooserDialog extends JDialog implements CloseableDialog { selectionPanel = new ThrustCurveMotorSelectionPanel(); - panel.add(selectionPanel, "grow, wrap para"); + panel.add(selectionPanel, "grow, wrap"); // OK / Cancel buttons diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java index 485c693a4..e23216b68 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java @@ -11,6 +11,7 @@ import java.util.List; import javax.swing.BorderFactory; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -90,6 +91,21 @@ public abstract class MotorFilterPanel extends JPanel { List unselectedManusFromPreferences = ((SwingPreferences) Application.getPreferences()).getExcludedMotorManufacturers(); filter.setExcludedManufacturers(unselectedManusFromPreferences); + //// Hide used motor files + { + final JCheckBox hideUsedBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideUsed")); + GUIUtil.changeFontSize(hideUsedBox, -1); + hideUsedBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPanel.this.filter.setHideUsedMotors(hideUsedBox.isSelected()); + onSelectionChanged(); + } + }); + this.add(hideUsedBox, "gapleft para, spanx, growx, wrap"); + } + + // Manufacturer selection JPanel sub = new JPanel(new MigLayout("fill")); TitledBorder border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.MANUFACTURER")); @@ -292,7 +308,7 @@ public abstract class MotorFilterPanel extends JPanel { diameterSlider.setValueAt(1, i-1); motorMountDimension.setText( trans.get("TCMotorSelPan.MotorMountDimensions") + " " + - UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountDiameter)+ "x" + UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountLength)); + UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountDiameter)+ " x " + UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountLength)); } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index 2091f33d5..e12b07dd9 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -164,7 +164,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } } }); - panel.add(curveSelectionBox, "growx"); + panel.add(curveSelectionBox, "growx, wrap"); } // Ejection charge delay: @@ -190,40 +190,25 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec setDelays(false); } }); - panel.add(delayBox, "split 2, growx"); + panel.add(delayBox, "growx,wrap"); //// (Number of seconds or \"None\") - panel.add(new StyledLabel(trans.get("TCMotorSelPan.lbl.NumberofsecondsorNone"), -3), "wrap para"); + panel.add(new StyledLabel(trans.get("TCMotorSelPan.lbl.NumberofsecondsorNone"), -3), "skip, wrap"); setDelays(false); } - // Search field + //// Hide very similar thrust curves { - //// Search: - StyledLabel label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Search")); - panel.add(label); - searchField = new JTextField(); - searchField.getDocument().addDocumentListener(new DocumentListener() { + hideSimilarBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideSimilar")); + GUIUtil.changeFontSize(hideSimilarBox, -1); + hideSimilarBox.setSelected(Application.getPreferences().getBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, true)); + hideSimilarBox.addActionListener(new ActionListener() { @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+"); - rowFilter.setSearchTerms(Arrays.asList(split)); - sorter.sort(); - scrollSelectionVisible(); + public void actionPerformed(ActionEvent e) { + Application.getPreferences().putBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected()); + updateData(); } }); - panel.add(searchField, "span, growx, wrap"); + panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap"); } //// Motor selection table @@ -285,38 +270,38 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec } - //// Hide used motor files + // Search field { - final JCheckBox hideUsedBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideUsed")); - GUIUtil.changeFontSize(hideUsedBox, -1); - hideUsedBox.addActionListener(new ActionListener() { + //// Search: + StyledLabel label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Search")); + panel.add(label); + searchField = new JTextField(); + searchField.getDocument().addDocumentListener(new DocumentListener() { @Override - public void actionPerformed(ActionEvent e) { - rowFilter.setHideUsedMotors(hideUsedBox.isSelected()); + 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+"); + rowFilter.setSearchTerms(Arrays.asList(split)); sorter.sort(); scrollSelectionVisible(); } }); - panel.add(hideUsedBox, "gapleft para, spanx, growx, wrap"); - } - - //// Hide very similar thrust curves - { - hideSimilarBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideSimilar")); - GUIUtil.changeFontSize(hideSimilarBox, -1); - hideSimilarBox.setSelected(Application.getPreferences().getBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, true)); - hideSimilarBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - Application.getPreferences().putBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected()); - updateData(); - } - }); - panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap"); + panel.add(searchField, "span, growx"); } + this.add(panel, "grow"); // Vertical split - this.add(panel, "grow"); this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para"); JTabbedPane rightSide = new JTabbedPane(); From 88ddb86758126061848e2d9bdd833ab6a2a39432 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 9 Oct 2013 12:25:12 -0500 Subject: [PATCH 35/47] Update flight configuration buttons to have better names. --- core/resources/l10n/messages.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 6e7baf546..336dd9dc5 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -176,10 +176,10 @@ MotorChooserDialog.title = Select a rocket motor ! Edit Motor configuration dialog edtmotorconfdlg.col.configuration = Configuration -edtmotorconfdlg.but.Removeconfiguration = Remove -edtmotorconfdlg.but.Renameconfiguration = Rename -edtmotorconfdlg.but.Newconfiguration = New -edtmotorconfdlg.but.Copyconfiguration = Copy +edtmotorconfdlg.but.Removeconfiguration = Remove Configuration +edtmotorconfdlg.but.Renameconfiguration = Rename Configuration +edtmotorconfdlg.but.Newconfiguration = New Configuration +edtmotorconfdlg.but.Copyconfiguration = Copy Configuration edtmotorconfdlg.title.Editmotorconf = Edit Flight configurations edtmotorconfdlg.title.Renameconf = Rename Flight Configuration edtmotorconfdlg.title.Selectdeploymentconf = Select Deployment Configuration From e091f3341abc159bbb3871c838cfff1b67130a1f Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 9 Oct 2013 12:26:35 -0500 Subject: [PATCH 36/47] Refactor MotorMountTable UI currently used in flight configuration dialog in order to provide reuse. --- .../MotorConfigurationPanel.java | 141 +++++++++--------- .../MotorMountConfigurationPanel.java | 48 ++++++ .../MotorMountTableModel.java | 6 +- 3 files changed, 118 insertions(+), 77 deletions(-) create mode 100644 swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java index b47d910f7..95692a29b 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java @@ -33,63 +33,56 @@ import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; public class MotorConfigurationPanel extends JPanel { - + private static final Translator trans = Application.getTranslator(); - + private final FlightConfigurationDialog flightConfigurationDialog; private final Rocket rocket; - + private final JTable configurationTable; private final MotorConfigurationTableModel configurationTableModel; private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton; - + private final MotorChooserDialog dialog; - + MotorConfigurationPanel(FlightConfigurationDialog flightConfigurationDialog, Rocket rocket) { super(new MigLayout("fill")); dialog = new MotorChooserDialog(flightConfigurationDialog); this.flightConfigurationDialog = flightConfigurationDialog; this.rocket = rocket; - + DescriptionArea desc = new DescriptionArea(trans.get("description"), 3, -1); this.add(desc, "spanx, growx, wrap para"); - - + + //// Motor mount selection JLabel label = new StyledLabel(trans.get("lbl.motorMounts"), Style.BOLD); this.add(label, ""); - + //// Motor selection label = new StyledLabel(trans.get("lbl.motorConfiguration"), Style.BOLD); this.add(label, "wrap rel"); - - + + //// Motor Mount selection - JTable table = new JTable(new MotorMountTableModel(this, rocket)); - 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); - - table.addMouseListener(new GUIUtil.BooleanTableClickListener(table)); - JScrollPane scroll = new JScrollPane(table); - this.add(scroll, "w 200lp, h 150lp, grow"); - - + { + MotorMountConfigurationPanel mountConfigPanel = new MotorMountConfigurationPanel(this,rocket) { + @Override + public void onDataChanged() { + MotorConfigurationPanel.this.fireTableDataChanged(); + + } + }; + this.add(mountConfigPanel, "w 200lp, h 150lp, grow"); + } + //// Motor selection table. configurationTableModel = new MotorConfigurationTableModel(rocket); configurationTable = new JTable(configurationTableModel); configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); configurationTable.setRowSelectionAllowed(true); configurationTable.setDefaultRenderer(Object.class, new MotorTableCellRenderer()); - + configurationTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -106,10 +99,10 @@ public class MotorConfigurationPanel extends JPanel { } } }); - - scroll = new JScrollPane(configurationTable); + + JScrollPane scroll = new JScrollPane(configurationTable); this.add(scroll, "w 500lp, h 150lp, grow, wrap"); - + //// Select motor selectMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectMotor")); selectMotorButton.addActionListener(new ActionListener() { @@ -119,7 +112,7 @@ public class MotorConfigurationPanel extends JPanel { } }); this.add(selectMotorButton, "skip, split, sizegroup button"); - + //// Remove motor button removeMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.removeMotor")); removeMotorButton.addActionListener(new ActionListener() { @@ -129,7 +122,7 @@ public class MotorConfigurationPanel extends JPanel { } }); this.add(removeMotorButton, "sizegroup button"); - + //// Select Ignition button selectIgnitionButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectIgnition")); selectIgnitionButton.addActionListener(new ActionListener() { @@ -139,7 +132,7 @@ public class MotorConfigurationPanel extends JPanel { } }); this.add(selectIgnitionButton, "sizegroup button"); - + //// Reset Ignition button resetIgnitionButton = new JButton(trans.get("MotorConfigurationPanel.btn.resetIgnition")); resetIgnitionButton.addActionListener(new ActionListener() { @@ -149,9 +142,9 @@ public class MotorConfigurationPanel extends JPanel { } }); this.add(resetIgnitionButton, "sizegroup button, wrap"); - + } - + public void fireTableDataChanged() { int selected = configurationTable.getSelectedRow(); configurationTableModel.fireTableDataChanged(); @@ -161,7 +154,7 @@ public class MotorConfigurationPanel extends JPanel { } updateButtonState(); } - + private void updateButtonState() { String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount currentMount = getCurrentMount(); @@ -170,18 +163,18 @@ public class MotorConfigurationPanel extends JPanel { selectIgnitionButton.setEnabled(currentMount != null && currentID != null); resetIgnitionButton.setEnabled(currentMount != null && currentID != null); } - - + + private MotorMount getCurrentMount() { int row = configurationTable.getSelectedRow(); if (row < 0) { return null; } - + return getMount(row); } - - + + private MotorMount getMount(int row) { int count = 0; for (RocketComponent c : rocket) { @@ -195,76 +188,76 @@ public class MotorConfigurationPanel extends JPanel { } } } - + throw new IndexOutOfBoundsException("Invalid row, row=" + row + " count=" + count); } - - - + + + private void selectMotor() { String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount mount = getCurrentMount(); if (id == null || mount == null) return; - + MotorConfiguration config = mount.getMotorConfiguration().get(id); - + dialog.setMotorMountAndConfig(mount, id); dialog.setVisible(true); Motor m = dialog.getSelectedMotor(); double d = dialog.getSelectedDelay(); - + if (m != null) { config = new MotorConfiguration(); config.setMotor(m); config.setEjectionDelay(d); mount.getMotorConfiguration().set(id, config); } - + fireTableDataChanged(); } - + private void removeMotor() { String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount mount = getCurrentMount(); if (id == null || mount == null) return; - + mount.getMotorConfiguration().resetDefault(id); - + fireTableDataChanged(); } - + private void selectIgnition() { String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount currentMount = getCurrentMount(); if (currentID == null || currentMount == null) return; - + IgnitionSelectionDialog dialog = new IgnitionSelectionDialog( this.flightConfigurationDialog, rocket, currentMount); dialog.setVisible(true); - + fireTableDataChanged(); } - - + + private void resetIgnition() { String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount currentMount = getCurrentMount(); if (currentID == null || currentMount == null) return; - + currentMount.getIgnitionConfiguration().resetDefault(currentID); - + fireTableDataChanged(); } - - + + private class MotorTableCellRenderer extends DefaultTableCellRenderer { - + @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); @@ -272,15 +265,15 @@ public class MotorConfigurationPanel extends JPanel { return c; } JLabel label = (JLabel) c; - + MotorMount mount = getMount(row); String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - + switch (column) { case 0: regular(label); break; - + case 1: if (mount.getMotorConfiguration().get(id).getMotor() != null) { regular(label); @@ -288,7 +281,7 @@ public class MotorConfigurationPanel extends JPanel { shaded(label); } break; - + case 2: if (mount.getIgnitionConfiguration().isDefault(id)) { shaded(label); @@ -297,20 +290,20 @@ public class MotorConfigurationPanel extends JPanel { } break; } - + return label; } - + private void shaded(JLabel label) { GUIUtil.changeFontStyle(label, Font.ITALIC); label.setForeground(Color.GRAY); } - + private void regular(JLabel label) { GUIUtil.changeFontStyle(label, Font.PLAIN); label.setForeground(Color.BLACK); } - + } - + } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java new file mode 100644 index 000000000..2dc0501e1 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java @@ -0,0 +1,48 @@ +package net.sf.openrocket.gui.dialogs.flightconfiguration; + +import java.awt.Component; + +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.rocketcomponent.Rocket; + +public abstract class MotorMountConfigurationPanel extends JPanel { + + + private final Rocket rocket; + private final Component parent; + + public MotorMountConfigurationPanel( Component parent, Rocket rocket ) { + super(new MigLayout("") ); + + this.parent = parent; + this.rocket = rocket; + + //// Motor Mount selection + JTable table = new JTable(new MotorMountTableModel(this, rocket)); + 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); + + table.addMouseListener(new GUIUtil.BooleanTableClickListener(table)); + JScrollPane scroll = new JScrollPane(table); + this.add(scroll, "w 200lp, h 150lp, grow"); + + } + + public abstract void onDataChanged(); +} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java index 29b1fab64..f4a1b9db2 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java @@ -14,14 +14,14 @@ import net.sf.openrocket.util.ArrayList; */ class MotorMountTableModel extends AbstractTableModel { - private final MotorConfigurationPanel motorConfigurationPanel; + private final MotorMountConfigurationPanel motorConfigurationPanel; private final List potentialMounts = new ArrayList(); /** * @param motorConfigurationPanel */ - MotorMountTableModel(MotorConfigurationPanel motorConfigurationPanel, Rocket rocket) { + MotorMountTableModel(MotorMountConfigurationPanel motorConfigurationPanel, Rocket rocket) { this.motorConfigurationPanel = motorConfigurationPanel; for (RocketComponent c : rocket) { @@ -82,6 +82,6 @@ class MotorMountTableModel extends AbstractTableModel { MotorMount mount = potentialMounts.get(row); mount.setMotorMount((Boolean) value); - this.motorConfigurationPanel.fireTableDataChanged(); + this.motorConfigurationPanel.onDataChanged(); } } \ No newline at end of file From d6b19eb4b6eb317cfbed32790607858ddf0da0b8 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 9 Oct 2013 13:53:05 -0500 Subject: [PATCH 37/47] Add motor mount selection table to the Flight Configuration tab. --- .../MotorConfigurationPanel.java | 113 ++++++++++-------- 1 file changed, 66 insertions(+), 47 deletions(-) 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 c4d780625..06d5a0f90 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java @@ -1,6 +1,5 @@ package net.sf.openrocket.gui.main.flightconfigpanel; -import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; @@ -8,13 +7,17 @@ import java.awt.event.MouseEvent; import javax.swing.JButton; import javax.swing.JLabel; +import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; -import javax.swing.table.DefaultTableCellRenderer; +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.gui.components.StyledLabel; +import net.sf.openrocket.gui.components.StyledLabel.Style; import net.sf.openrocket.gui.dialogs.flightconfiguration.IgnitionSelectionDialog; +import net.sf.openrocket.gui.dialogs.flightconfiguration.MotorMountConfigurationPanel; import net.sf.openrocket.gui.dialogs.motor.MotorChooserDialog; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.rocketcomponent.IgnitionConfiguration; @@ -25,22 +28,38 @@ import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.Chars; import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.Pair; public class MotorConfigurationPanel extends FlightConfigurablePanel { - + private static final String NONE = trans.get("edtmotorconfdlg.tbl.None"); - + private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton; - + protected FlightConfigurableTableModel configurationTableModel; MotorConfigurationPanel(final FlightConfigurationPanel flightConfigurationPanel, Rocket rocket) { super(flightConfigurationPanel,rocket); - + + { + //// Select motor mounts + JPanel subpanel = new JPanel(new MigLayout("")); + JLabel label = new StyledLabel(trans.get("lbl.motorMounts"), Style.BOLD); + subpanel.add(label, "wrap"); + + MotorMountConfigurationPanel mountConfigPanel = new MotorMountConfigurationPanel(this,rocket) { + @Override + public void onDataChanged() { + MotorConfigurationPanel.this.fireTableDataChanged(); + + } + }; + subpanel.add(mountConfigPanel, "grow"); + this.add(subpanel, "split, w 200lp, growy"); + } + JScrollPane scroll = new JScrollPane(table); this.add(scroll, "grow, wrap"); - + //// Select motor selectMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectMotor")); selectMotorButton.addActionListener(new ActionListener() { @@ -50,7 +69,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } }); this.add(selectMotorButton, "split, sizegroup button"); - + //// Remove motor button removeMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.removeMotor")); removeMotorButton.addActionListener(new ActionListener() { @@ -60,7 +79,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } }); this.add(removeMotorButton, "sizegroup button"); - + //// Select Ignition button selectIgnitionButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectIgnition")); selectIgnitionButton.addActionListener(new ActionListener() { @@ -70,7 +89,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } }); this.add(selectIgnitionButton, "sizegroup button"); - + //// Reset Ignition button resetIgnitionButton = new JButton(trans.get("MotorConfigurationPanel.btn.resetIgnition")); resetIgnitionButton.addActionListener(new ActionListener() { @@ -80,11 +99,11 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } }); this.add(resetIgnitionButton, "sizegroup button, wrap"); - + updateButtonState(); - + } - + @Override protected JTable initializeTable() { //// Motor selection table. @@ -94,13 +113,13 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel protected boolean includeComponent(MotorMount component) { return component.isMotorMount(); } - + }; JTable configurationTable = new JTable(configurationTableModel); configurationTable.setCellSelectionEnabled(true); configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); configurationTable.setDefaultRenderer(Object.class, new MotorTableCellRenderer()); - + configurationTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -125,7 +144,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } updateButtonState(); } - + private void updateButtonState() { String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount currentMount = getSelectedComponent(); @@ -134,16 +153,16 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel selectIgnitionButton.setEnabled(currentMount != null && currentID != null); resetIgnitionButton.setEnabled(currentMount != null && currentID != null); } - - + + private void selectMotor() { String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount mount = getSelectedComponent(); if (id == null || mount == null) return; - + MotorConfiguration config = mount.getMotorConfiguration().get(id); - + MotorChooserDialog dialog = new MotorChooserDialog( mount, id, @@ -151,59 +170,59 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel dialog.setVisible(true); Motor m = dialog.getSelectedMotor(); double d = dialog.getSelectedDelay(); - + if (m != null) { config = new MotorConfiguration(); config.setMotor(m); config.setEjectionDelay(d); mount.getMotorConfiguration().set(id, config); } - + fireTableDataChanged(); } - + private void removeMotor() { String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount mount = getSelectedComponent(); if (id == null || mount == null) return; - + mount.getMotorConfiguration().resetDefault(id); - + fireTableDataChanged(); } - + private void selectIgnition() { String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount currentMount = getSelectedComponent(); if (currentID == null || currentMount == null) return; - + IgnitionSelectionDialog dialog = new IgnitionSelectionDialog( SwingUtilities.getWindowAncestor(this.flightConfigurationPanel), rocket, currentMount); dialog.setVisible(true); - + fireTableDataChanged(); } - - + + private void resetIgnition() { String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); MotorMount currentMount = getSelectedComponent(); if (currentID == null || currentMount == null) return; - + currentMount.getIgnitionConfiguration().resetDefault(currentID); - + fireTableDataChanged(); } - - + + private class MotorTableCellRenderer extends FlightConfigurablePanel.FlightConfigurableCellRenderer { - - + + @Override protected void format(MotorMount mount, String configId, JLabel label) { MotorConfiguration motorConfig = mount.getMotorConfiguration().get(configId); @@ -214,10 +233,10 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel private String getMotorSpecification(MotorMount mount, MotorConfiguration motorConfig) { Motor motor = motorConfig.getMotor(); - + if (motor == null) return NONE; - + String str = motor.getDesignation(motorConfig.getEjectionDelay()); int count = getMountMultiplicity(mount); if (count > 1) { @@ -225,21 +244,21 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } return str; } - + private int getMountMultiplicity(MotorMount mount) { RocketComponent c = (RocketComponent) mount; return c.toAbsolute(Coordinate.NUL).length; } - - - + + + private String getIgnitionEventString(String id, MotorMount mount) { IgnitionConfiguration ignitionConfig = mount.getIgnitionConfiguration().get(id); IgnitionConfiguration.IgnitionEvent ignitionEvent = ignitionConfig.getIgnitionEvent(); - + Double ignitionDelay = ignitionConfig.getIgnitionDelay(); boolean isDefault = mount.getIgnitionConfiguration().isDefault(id); - + String str = trans.get("MotorMount.IgnitionEvent.short." + ignitionEvent.name()); if (ignitionDelay > 0.001) { str = str + " + " + UnitGroup.UNITS_SHORT_TIME.toStringUnit(ignitionDelay); @@ -250,7 +269,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } return str; } - + } - + } From e4afa7961e751b895478e0d473cb1446eeccb464 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 9 Oct 2013 14:00:33 -0500 Subject: [PATCH 38/47] Fixed jdk7 problem in Multislider and NPE when there is no motor in the ThrustCurveMotorSelectionPanel. --- .../ThrustCurveMotorSelectionPanel.java | 13 +- .../openrocket/gui/widgets/MultiSlider.java | 965 +++++++++--------- 2 files changed, 498 insertions(+), 480 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index e12b07dd9..e0eb134d1 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -95,7 +95,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec private final JComboBox curveSelectionBox; private final DefaultComboBoxModel curveSelectionModel; private final JComboBox delayBox; - + private final MotorInformationPanel motorInformationPanel; private final MotorFilterPanel motorFilterPanel; @@ -140,7 +140,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec scrollSelectionVisible(); } }; - + } //// GUI @@ -303,7 +303,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec // Vertical split this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para"); - + JTabbedPane rightSide = new JTabbedPane(); rightSide.add(trans.get("TCMotorSelPan.btn.filter"), motorFilterPanel); rightSide.add(trans.get("TCMotorSelPan.btn.details"), motorInformationPanel); @@ -323,7 +323,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec if ( mount != null ) { diameter = mount.getMotorMountDiameter(); } - + if (currentConfig != null && mount != null) { MotorConfiguration motorConf = mount.getMotorConfiguration().get(currentConfig); selectedMotor = (ThrustCurveMotor) motorConf.getMotor(); @@ -454,14 +454,14 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec curveSelectionBox.setEnabled(false); curveSelectionLabel.setEnabled(false); } - + motorInformationPanel.updateData(motors, selectedMotor); } List getFilteredCurves() { List motors = selectedMotorSet.getMotors(); - if (hideSimilarBox.isSelected()) { + if (hideSimilarBox.isSelected() && selectedMotor != null) { List filtered = new ArrayList(motors.size()); for (int i = 0; i < motors.size(); i++) { ThrustCurveMotor m = motors.get(i); @@ -469,7 +469,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec filtered.add(m); continue; } - double similarity = MotorCorrelation.similarity(selectedMotor, m); log.debug("Motor similarity: " + similarity); if (similarity < MOTOR_SIMILARITY_THRESHOLD) { diff --git a/swing/src/net/sf/openrocket/gui/widgets/MultiSlider.java b/swing/src/net/sf/openrocket/gui/widgets/MultiSlider.java index 5b956c5a6..544b91287 100644 --- a/swing/src/net/sf/openrocket/gui/widgets/MultiSlider.java +++ b/swing/src/net/sf/openrocket/gui/widgets/MultiSlider.java @@ -48,505 +48,524 @@ import javax.swing.plaf.SliderUI; */ public class MultiSlider extends JSlider { - /*** - * @see #getUIClassID - * @see #readObject - */ - private static final String uiClassID = "MultiSliderUI"; + /*** + * @see #getUIClassID + * @see #readObject + */ + private static final String uiClassID = "MultiSliderUI"; - /*** - * An array of data models that handle the numeric maximum values, - * minimum values, and current-position values for the multi slider. - */ - private BoundedRangeModel[] sliderModels; + /*** + * An array of data models that handle the numeric maximum values, + * minimum values, and current-position values for the multi slider. + */ + private BoundedRangeModel[] sliderModels; - /*** - * If it is true, a thumb is bounded by adjacent thumbs. - */ - private boolean bounded = false; + /*** + * If it is true, a thumb is bounded by adjacent thumbs. + */ + private boolean bounded = false; - /*** - * This is a color to paint the current thumb - */ - private Color currentThumbColor = Color.red; + /*** + * This is a color to paint the current thumb + */ + private Color currentThumbColor = Color.red; - transient private int valueBeforeStateChange; + transient private int valueBeforeStateChange; - /*** - * Creates a slider with the specified orientation and the - * specified mimimum, maximum, and initial values. - * - * @exception IllegalArgumentException if orientation is not one of VERTICAL, HORIZONTAL - * - * @see #setOrientation - * @see #setMinimum - * @see #setMaximum - * @see #setValue - */ - public MultiSlider(int orientation, int min, int max, - int val1, int val2) { - checkOrientation(orientation); - this.orientation = orientation; - setNumberOfThumbs(min,max,new int[]{val1,val2}); - } - - /*** - * Creates a slider with the specified orientation and the - * specified mimimum, maximum, and the number of thumbs. - * - * @exception IllegalArgumentException if orientation is not one of VERTICAL, HORIZONTAL - * - * @see #setOrientation - * @see #setMinimum - * @see #setMaximum - * @see #setValue - */ - public MultiSlider(int orientation, int min, int max) { - checkOrientation(orientation); - this.orientation = orientation; - setNumberOfThumbs(min, max, 2); - } - - /*** - * Creates a horizontal slider with the range 0 to 100 and - * an intitial value of 50. - */ - public MultiSlider() { - this(HORIZONTAL, 0, 100); - } - - - /*** - * Creates a slider using the specified orientation with the - * range 0 to 100 and an intitial value of 50. - */ - public MultiSlider(int orientation) { - this(orientation, 0, 100); - } - - - /*** - * Creates a horizontal slider using the specified min and max - * with an intitial value of 50. - */ - public MultiSlider(int min, int max) { - this(HORIZONTAL, min, max); - } - - public void setCurrentThumbColor(Color c) { - this.currentThumbColor = c; - } - - public Color getCurrentThumbColor() { - return this.currentThumbColor; - } - - public int getTrackBuffer() { - return ((MultiSliderUI) this.ui).getTrackBuffer(); - } - - /*** - * Validates the orientation parameter. - */ - private void checkOrientation(int orientation) { - switch (orientation) { - case VERTICAL: - case HORIZONTAL: - break; - default: - throw new IllegalArgumentException("orientation must be one of: VERTICAL, HORIZONTAL"); - } - } - - /*** - * Notification from the UIFactory that the L&F has changed. - * Called to replace the UI with the latest version from the - * default UIFactory. - * - * @see JComponent#updateUI - */ - public void updateUI() { - updateLabelUIs(); - MultiSliderUI ui = new MultiSliderUI(); - if (this.sliderModels != null) { - ui.setThumbCount(this.sliderModels.length); - } - setUI((SliderUI) ui); - } - - /*** - * Returns the number of thumbs in the slider. - */ - public int getNumberOfThumbs() { - return this.sliderModels.length; - } - - /*** - * Sets the number of thumbs with the specified parameters. - */ - private void setNumberOfThumbs(int min, int max, int num, boolean useEndPoints) { - int [] values = createDefaultValues(min, max, num, useEndPoints); - setNumberOfThumbs(min, max, values); - } - - /*** - * Sets the number of thumbs with the specified parameters. - */ - private void setNumberOfThumbs(int min, int max, int num) { - setNumberOfThumbs(min, max, num, false); - } - - /*** - * Sets the number of thumbs with the specified parameters. - */ - private void setNumberOfThumbs(int min, int max, int[] values) { - if (values == null || values.length < 1) { - values = new int[] {50}; - } - int num = values.length; - this.sliderModels = new BoundedRangeModel[num]; - for (int i = 0; i < num; i++) { - this.sliderModels[i] = new DefaultBoundedRangeModel(values[i], 0, min, max); - this.sliderModels[i].addChangeListener(changeListener); - } - updateUI(); - } - - /*** - * Sets the number of thumbs. - */ - private void setNumberOfThumbs(int num) { - setNumberOfThumbs(num, false); - } - - /*** - * Sets the number of thumbs. - */ - private void setNumberOfThumbs(int num, boolean useEndPoints) { - if (getNumberOfThumbs() != num) { - setNumberOfThumbs(getMinimum(), getMaximum(), num, useEndPoints); - } - } - - /*** - * Sets the number of thumbs by specifying the initial values. - */ - private void setNumberOfThumbs(int[] values) { - setNumberOfThumbs(getMinimum(), getMaximum(), values); - } - - /*** - * creates evenly spaced values for thumbs. - */ - private int[] createDefaultValues(int min, int max, int num_of_values, boolean useEndPoints) { - int[] values = new int[num_of_values]; - int range = max - min; - - if (!useEndPoints) { - int step = range / (num_of_values + 1); - for (int i = 0; i < num_of_values; i++) { - values[i] = min + (i + 1) * step; - } - } else { - if (num_of_values < 1) { - return new int[0]; - } - values[0] = getMinimum(); - values[num_of_values - 1] = getMaximum(); - int[] def = createDefaultValues(getMinimum(), getMaximum(), num_of_values - 2, false); - for (int i = 0; i < def.length; i++) { - values[i + 1] = def[i]; - } - } - return values; - } - - /*** - * Returns the index number of currently operated thumb. - */ - public int getCurrentThumbIndex() { - return ((MultiSliderUI)ui).getCurrentIndex(); - } - - /*** - * Returns data model that handles the sliders three - * fundamental properties: minimum, maximum, value. - * - * @see #setModel - */ - public BoundedRangeModel getModel() { - return getModelAt(getCurrentThumbIndex()); - } - - /*** - * Returns data model that handles the sliders three - * fundamental properties: minimum, maximum, value. - * - * @see #setModel - */ - public BoundedRangeModel getModelAt(int index) { - if (this.sliderModels == null || index >= this.sliderModels.length) { - return null; - } - return this.sliderModels[index]; - } - - /*** - * Returns data model that handles the sliders three - * fundamental properties: minimum, maximum, value. - * - * @see #setModel - */ - public BoundedRangeModel[] getModels() { - return this.sliderModels; - } - - /*** - * Sets the model that handles the sliders three - * fundamental properties: minimum, maximum, value. - * - * @see #getModel - * @beaninfo - * bound: true - * description: The sliders BoundedRangeModel. - */ - public void setModel(BoundedRangeModel newModel) { - setModelAt(getCurrentThumbIndex(), newModel); - } - - /*** - * Sets the model that handles the sliders three - * fundamental properties: minimum, maximum, value. - * - * @see #getModel - * @beaninfo - * bound: true - * description: The sliders BoundedRangeModel. - */ - public void setModelAt(int index, BoundedRangeModel newModel) { - BoundedRangeModel oldModel = getModelAt(index); - - if (oldModel != null) { - oldModel.removeChangeListener(changeListener); + /*** + * Creates a slider with the specified orientation and the + * specified mimimum, maximum, and initial values. + * + * @exception IllegalArgumentException if orientation is not one of VERTICAL, HORIZONTAL + * + * @see #setOrientation + * @see #setMinimum + * @see #setMaximum + * @see #setValue + */ + public MultiSlider(int orientation, int min, int max, + int val1, int val2) { + checkOrientation(orientation); + this.orientation = orientation; + setNumberOfThumbs(min,max,new int[]{val1,val2}); } - this.sliderModels[index] = newModel; - - if (newModel != null) { - newModel.addChangeListener(changeListener); - - if (accessibleContext != null) { - accessibleContext.firePropertyChange( - AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, - (oldModel == null - ? null : new Integer(oldModel.getValue())), - (newModel == null - ? null : new Integer(newModel.getValue()))); - } + /*** + * Creates a slider with the specified orientation and the + * specified mimimum, maximum, and the number of thumbs. + * + * @exception IllegalArgumentException if orientation is not one of VERTICAL, HORIZONTAL + * + * @see #setOrientation + * @see #setMinimum + * @see #setMaximum + * @see #setValue + */ + public MultiSlider(int orientation, int min, int max) { + checkOrientation(orientation); + this.orientation = orientation; + setNumberOfThumbs(min, max, 2); } - firePropertyChange("model", oldModel, this.sliderModels[index]); - } - - /*** - * Sets the models minimum property. - * - * @see #getMinimum - * @see BoundedRangeModel#setMinimum - * @beaninfo - * bound: true - * preferred: true - * description: The sliders minimum value. - */ - public void setMinimum(int minimum) { - int count = getNumberOfThumbs(); - int oldMin = getModel().getMinimum(); - for (int i = 0; i < count; i++) { - getModelAt(i).setMinimum(minimum); + /*** + * Creates a horizontal slider with the range 0 to 100 and + * an intitial value of 50. + */ + public MultiSlider() { + this(HORIZONTAL, 0, 100); } - firePropertyChange( "minimum", new Integer( oldMin ), new Integer( minimum ) ); - } - /*** - * Sets the models maximum property. - * - * @see #getMaximum - * @see BoundedRangeModel#setMaximum - * @beaninfo - * bound: true - * preferred: true - * description: The sliders maximum value. - */ - public void setMaximum(int maximum) { - int count = getNumberOfThumbs(); - int oldMax = getModel().getMaximum(); - for (int i = 0; i < count; i++) { - getModelAt(i).setMaximum(maximum); + + /*** + * Creates a slider using the specified orientation with the + * range 0 to 100 and an intitial value of 50. + */ + public MultiSlider(int orientation) { + this(orientation, 0, 100); } - firePropertyChange( "maximum", new Integer( oldMax ), new Integer( maximum ) ); - } - /*** - * Returns the sliders value. - * @return the models value property - * @see #setValue - */ - public int getValue() { - return getValueAt(getCurrentThumbIndex()); - } - /*** - * Returns the sliders value. - * @return the models value property - * @see #setValue - */ - public int getValueAt(int index) { - return getModelAt(index).getValue(); - } - - /*** - * Sets the sliders current value. This method just forwards - * the value to the model. - * - * @see #getValue - * @beaninfo - * preferred: true - * description: The sliders current value. - */ - public void setValue(int n) { - setValueAt(getCurrentThumbIndex(), n); - } - - /*** - * Sets the sliders current value. This method just forwards - * the value to the model. - * - * @see #getValue - * @beaninfo - * preferred: true - * description: The sliders current value. - */ - public void setValueAt(int index, int n) { - BoundedRangeModel m = getModelAt(index); - int oldValue = m.getValue(); - m.setValue(n); - - if (accessibleContext != null) { - accessibleContext.firePropertyChange( - AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, - new Integer(oldValue), - new Integer(m.getValue())); + /*** + * Creates a horizontal slider using the specified min and max + * with an intitial value of 50. + */ + public MultiSlider(int min, int max) { + this(HORIZONTAL, min, max); } - } - /*** - * True if the slider knob is being dragged. - * - * @return the value of the models valueIsAdjusting property - * @see #setValueIsAdjusting - */ - public boolean getValueIsAdjusting() { - boolean result = false; - int count = getNumberOfThumbs(); - for (int i = 0; i < count; i++) { - result = (result || getValueIsAdjustingAt(i)); + public void setCurrentThumbColor(Color c) { + this.currentThumbColor = c; } - return result; - } - /*** - * True if the slider knob is being dragged. - */ - public boolean getValueIsAdjustingAt(int index) { - return getModelAt(index).getValueIsAdjusting(); - } - - /*** - * Sets the models valueIsAdjusting property. Slider look and - * feel implementations should set this property to true when - * a knob drag begins, and to false when the drag ends. The - * slider model will not generate ChangeEvents while - * valueIsAdjusting is true. - * - * @see #getValueIsAdjusting - * @see BoundedRangeModel#setValueIsAdjusting - * @beaninfo - * expert: true - * description: True if the slider knob is being dragged. - */ - public void setValueIsAdjusting(boolean b) { - setValueIsAdjustingAt(getCurrentThumbIndex(), b); - } - - /*** - * Sets the models valueIsAdjusting property. Slider look and - * feel implementations should set this property to true when - * a knob drag begins, and to false when the drag ends. The - * slider model will not generate ChangeEvents while - * valueIsAdjusting is true. - */ - public void setValueIsAdjustingAt(int index, boolean b) { - BoundedRangeModel m = getModelAt(index); - boolean oldValue = m.getValueIsAdjusting(); - m.setValueIsAdjusting(b); - - if ((oldValue != b) && (accessibleContext != null)) { - accessibleContext.firePropertyChange( - AccessibleContext.ACCESSIBLE_STATE_PROPERTY, - ((oldValue) ? AccessibleState.BUSY : null), - ((b) ? AccessibleState.BUSY : null)); + public Color getCurrentThumbColor() { + return this.currentThumbColor; } - } - /*** - * Sets the size of the range "covered" by the knob. Most look - * and feel implementations will change the value by this amount - * if the user clicks on either side of the knob. - * - * @see #getExtent - * @see BoundedRangeModel#setExtent - * @beaninfo - * expert: true - * description: Size of the range covered by the knob. - */ - public void setExtent(int extent) { - int count = getNumberOfThumbs(); - for (int i = 0; i < count; i++) { - getModelAt(i).setExtent(extent); + public int getTrackBuffer() { + return ((MultiSliderUI) this.ui).getTrackBuffer(); + } + + /*** + * Validates the orientation parameter. + */ + private void checkOrientation(int orientation) { + switch (orientation) { + case VERTICAL: + case HORIZONTAL: + break; + default: + throw new IllegalArgumentException("orientation must be one of: VERTICAL, HORIZONTAL"); + } + } + + /*** + * Notification from the UIFactory that the L&F has changed. + * Called to replace the UI with the latest version from the + * default UIFactory. + * + * @see JComponent#updateUI + */ + public void updateUI() { + updateLabelUIs(); + MultiSliderUI ui = new MultiSliderUI(); + if (this.sliderModels != null) { + ui.setThumbCount(this.sliderModels.length); + } + setUI((SliderUI) ui); + } + + /*** + * Returns the number of thumbs in the slider. + */ + public int getNumberOfThumbs() { + return this.sliderModels.length; + } + + /*** + * Sets the number of thumbs with the specified parameters. + */ + private void setNumberOfThumbs(int min, int max, int num, boolean useEndPoints) { + int [] values = createDefaultValues(min, max, num, useEndPoints); + setNumberOfThumbs(min, max, values); + } + + /*** + * Sets the number of thumbs with the specified parameters. + */ + private void setNumberOfThumbs(int min, int max, int num) { + setNumberOfThumbs(min, max, num, false); + } + + /*** + * Sets the number of thumbs with the specified parameters. + */ + private void setNumberOfThumbs(int min, int max, int[] values) { + if (values == null || values.length < 1) { + values = new int[] {50}; + } + int num = values.length; + this.sliderModels = new BoundedRangeModel[num]; + for (int i = 0; i < num; i++) { + this.sliderModels[i] = new DefaultBoundedRangeModel(values[i], 0, min, max); + this.sliderModels[i].addChangeListener(changeListener); + } + updateUI(); + } + + /*** + * Sets the number of thumbs. + */ + private void setNumberOfThumbs(int num) { + setNumberOfThumbs(num, false); + } + + /*** + * Sets the number of thumbs. + */ + private void setNumberOfThumbs(int num, boolean useEndPoints) { + if (getNumberOfThumbs() != num) { + setNumberOfThumbs(getMinimum(), getMaximum(), num, useEndPoints); + } + } + + /*** + * Sets the number of thumbs by specifying the initial values. + */ + private void setNumberOfThumbs(int[] values) { + setNumberOfThumbs(getMinimum(), getMaximum(), values); + } + + /*** + * creates evenly spaced values for thumbs. + */ + private int[] createDefaultValues(int min, int max, int num_of_values, boolean useEndPoints) { + int[] values = new int[num_of_values]; + int range = max - min; + + if (!useEndPoints) { + int step = range / (num_of_values + 1); + for (int i = 0; i < num_of_values; i++) { + values[i] = min + (i + 1) * step; + } + } else { + if (num_of_values < 1) { + return new int[0]; + } + values[0] = getMinimum(); + values[num_of_values - 1] = getMaximum(); + int[] def = createDefaultValues(getMinimum(), getMaximum(), num_of_values - 2, false); + for (int i = 0; i < def.length; i++) { + values[i + 1] = def[i]; + } + } + return values; + } + + /*** + * Returns the index number of currently operated thumb. + */ + public int getCurrentThumbIndex() { + return ((MultiSliderUI)ui).getCurrentIndex(); + } + + /*** + * Returns data model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #setModel + */ + public BoundedRangeModel getModel() { + return getModelAt(getCurrentThumbIndex()); + } + + /*** + * Returns data model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #setModel + */ + public BoundedRangeModel getModelAt(int index) { + if (this.sliderModels == null || index >= this.sliderModels.length) { + return null; + } + return this.sliderModels[index]; + } + + /*** + * Returns data model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #setModel + */ + public BoundedRangeModel[] getModels() { + return this.sliderModels; + } + + /*** + * Sets the model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #getModel + * @beaninfo + * bound: true + * description: The sliders BoundedRangeModel. + */ + public void setModel(BoundedRangeModel newModel) { + // Hack around jdk 7 problem: http://code.google.com/p/geoviz/issues/detail?id=80 + + try + { + setModelAt(getCurrentThumbIndex(), newModel); + + } + catch (Exception e) + { + this.sliderModel = newModel; + } + } + + /*** + * Sets the model that handles the sliders three + * fundamental properties: minimum, maximum, value. + * + * @see #getModel + * @beaninfo + * bound: true + * description: The sliders BoundedRangeModel. + */ + public void setModelAt(int index, BoundedRangeModel newModel) { + BoundedRangeModel oldModel = getModelAt(index); + + if (oldModel != null) { + oldModel.removeChangeListener(changeListener); + } + + this.sliderModels[index] = newModel; + + if (newModel != null) { + newModel.addChangeListener(changeListener); + + if (accessibleContext != null) { + accessibleContext.firePropertyChange( + AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, + (oldModel == null + ? null : new Integer(oldModel.getValue())), + (newModel == null + ? null : new Integer(newModel.getValue()))); + } + } + + firePropertyChange("model", oldModel, this.sliderModels[index]); + } + + /*** + * Sets the models minimum property. + * + * @see #getMinimum + * @see BoundedRangeModel#setMinimum + * @beaninfo + * bound: true + * preferred: true + * description: The sliders minimum value. + */ + public void setMinimum(int minimum) { + int count = getNumberOfThumbs(); + int oldMin = getModel().getMinimum(); + for (int i = 0; i < count; i++) { + getModelAt(i).setMinimum(minimum); + } + firePropertyChange( "minimum", new Integer( oldMin ), new Integer( minimum ) ); + } + + /*** + * Sets the models maximum property. + * + * @see #getMaximum + * @see BoundedRangeModel#setMaximum + * @beaninfo + * bound: true + * preferred: true + * description: The sliders maximum value. + */ + public void setMaximum(int maximum) { + int count = getNumberOfThumbs(); + int oldMax = getModel().getMaximum(); + for (int i = 0; i < count; i++) { + getModelAt(i).setMaximum(maximum); + } + firePropertyChange( "maximum", new Integer( oldMax ), new Integer( maximum ) ); + } + + /*** + * Returns the sliders value. + * @return the models value property + * @see #setValue + */ + public int getValue() { + // Hack around jdk 7 problem: http://code.google.com/p/geoviz/issues/detail?id=80 + try + { + return getValueAt(getCurrentThumbIndex()); + + } + catch (Exception e) + { + return 0; + } + } + + /*** + * Returns the sliders value. + * @return the models value property + * @see #setValue + */ + public int getValueAt(int index) { + return getModelAt(index).getValue(); + } + + /*** + * Sets the sliders current value. This method just forwards + * the value to the model. + * + * @see #getValue + * @beaninfo + * preferred: true + * description: The sliders current value. + */ + public void setValue(int n) { + setValueAt(getCurrentThumbIndex(), n); + } + + /*** + * Sets the sliders current value. This method just forwards + * the value to the model. + * + * @see #getValue + * @beaninfo + * preferred: true + * description: The sliders current value. + */ + public void setValueAt(int index, int n) { + BoundedRangeModel m = getModelAt(index); + int oldValue = m.getValue(); + m.setValue(n); + + if (accessibleContext != null) { + accessibleContext.firePropertyChange( + AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, + new Integer(oldValue), + new Integer(m.getValue())); + } + } + + /*** + * True if the slider knob is being dragged. + * + * @return the value of the models valueIsAdjusting property + * @see #setValueIsAdjusting + */ + public boolean getValueIsAdjusting() { + boolean result = false; + int count = getNumberOfThumbs(); + for (int i = 0; i < count; i++) { + result = (result || getValueIsAdjustingAt(i)); + } + return result; + } + + /*** + * True if the slider knob is being dragged. + */ + public boolean getValueIsAdjustingAt(int index) { + return getModelAt(index).getValueIsAdjusting(); + } + + /*** + * Sets the models valueIsAdjusting property. Slider look and + * feel implementations should set this property to true when + * a knob drag begins, and to false when the drag ends. The + * slider model will not generate ChangeEvents while + * valueIsAdjusting is true. + * + * @see #getValueIsAdjusting + * @see BoundedRangeModel#setValueIsAdjusting + * @beaninfo + * expert: true + * description: True if the slider knob is being dragged. + */ + public void setValueIsAdjusting(boolean b) { + setValueIsAdjustingAt(getCurrentThumbIndex(), b); + } + + /*** + * Sets the models valueIsAdjusting property. Slider look and + * feel implementations should set this property to true when + * a knob drag begins, and to false when the drag ends. The + * slider model will not generate ChangeEvents while + * valueIsAdjusting is true. + */ + public void setValueIsAdjustingAt(int index, boolean b) { + BoundedRangeModel m = getModelAt(index); + boolean oldValue = m.getValueIsAdjusting(); + m.setValueIsAdjusting(b); + + if ((oldValue != b) && (accessibleContext != null)) { + accessibleContext.firePropertyChange( + AccessibleContext.ACCESSIBLE_STATE_PROPERTY, + ((oldValue) ? AccessibleState.BUSY : null), + ((b) ? AccessibleState.BUSY : null)); + } + } + + /*** + * Sets the size of the range "covered" by the knob. Most look + * and feel implementations will change the value by this amount + * if the user clicks on either side of the knob. + * + * @see #getExtent + * @see BoundedRangeModel#setExtent + * @beaninfo + * expert: true + * description: Size of the range covered by the knob. + */ + public void setExtent(int extent) { + int count = getNumberOfThumbs(); + for (int i = 0; i < count; i++) { + getModelAt(i).setExtent(extent); + } } - } - /*** - * Sets a bounded attribute of a slider thumb. - *
-     * 
- * - * @param b - * @return void - */ - public void setBounded(boolean b) { - this.bounded = b; - } + /*** + * Sets a bounded attribute of a slider thumb. + *
+	 * 
+ * + * @param b + * @return void + */ + public void setBounded(boolean b) { + this.bounded = b; + } - /*** - * Returns a bounded attribute of a slider thumb. - *
-     * 
- * - * @return boolean - */ - public boolean isBounded() { - return this.bounded; - } + /*** + * Returns a bounded attribute of a slider thumb. + *
+	 * 
+ * + * @return boolean + */ + public boolean isBounded() { + return this.bounded; + } - public int getValueBeforeStateChange() { - return this.valueBeforeStateChange; - } + public int getValueBeforeStateChange() { + return this.valueBeforeStateChange; + } - void setValueBeforeStateChange(int v) { - this.valueBeforeStateChange = v; - } + void setValueBeforeStateChange(int v) { + this.valueBeforeStateChange = v; + } } From 1e0ac22592ba0808f04209acdd677ba4a30c731e Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 9 Oct 2013 14:20:38 -0500 Subject: [PATCH 39/47] Removed the old FlightConfigurationDialog. --- core/resources/l10n/messages.properties | 5 - .../adaptors/FlightConfigurationModel.java | 37 +-- .../FlightConfigurationDialog.java | 231 ------------- .../MotorConfigurationPanel.java | 309 ------------------ .../MotorConfigurationTableModel.java | 146 --------- .../RecoveryConfigurationPanel.java | 264 --------------- .../SeparationConfigurationPanel.java | 259 --------------- .../gui/scalefigure/RocketPanel.java | 19 +- .../gui/simulation/SimulationEditDialog.java | 15 +- 9 files changed, 4 insertions(+), 1281 deletions(-) delete mode 100644 swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/FlightConfigurationDialog.java delete mode 100644 swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java delete mode 100644 swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationTableModel.java delete mode 100644 swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RecoveryConfigurationPanel.java delete mode 100644 swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationConfigurationPanel.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 336dd9dc5..8246fd703 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -52,7 +52,6 @@ RocketPanel.FigTypeAct.Unfinished = 3D Unfinished RocketPanel.lbl.Flightcfg = Flight configuration: -RocketPanel.but.FlightcfgEdit = Edit RocketPanel.lbl.infoMessage = Click to select    Shift+click to select other    Double-click to edit    Click+drag to move RocketPanel.lbl.ViewType = View Type: @@ -327,7 +326,6 @@ simedtdlg.tab.Plotdata = Plot data simedtdlg.tab.CustomExpressions = Custom expressions simedtdlg.tab.Exportdata = Export data simedtdlg.lbl.Flightcfg = Flight configuration: -simedtdlg.but.FlightcfgEdit = Edit simedtdlg.lbl.ttip.Flightcfg = Select the flight configuration to use. simedtdlg.combo.ttip.Flightcfg = Select the flight configuration to use. simedtdlg.lbl.Wind = Wind @@ -1066,9 +1064,6 @@ TrapezoidFinSetCfg.lbl.plus = plus TrapezoidFinSetCfg.tab.General = General TrapezoidFinSetCfg.tab.Generalproperties = General properties -!MotorConfigurationModel -MotorCfgModel.Editcfg = Edit configurations - ! StorageOptionChooser StorageOptChooser.lbl.Simdatatostore = Simulated data to store: StorageOptChooser.rdbut.Allsimdata = All simulated data diff --git a/swing/src/net/sf/openrocket/gui/adaptors/FlightConfigurationModel.java b/swing/src/net/sf/openrocket/gui/adaptors/FlightConfigurationModel.java index e37c488db..a0717117c 100644 --- a/swing/src/net/sf/openrocket/gui/adaptors/FlightConfigurationModel.java +++ b/swing/src/net/sf/openrocket/gui/adaptors/FlightConfigurationModel.java @@ -6,14 +6,11 @@ import java.util.HashMap; import java.util.Map; import javax.swing.ComboBoxModel; -import javax.swing.SwingUtilities; import javax.swing.event.EventListenerList; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import net.sf.openrocket.formatting.RocketDescriptor; -import net.sf.openrocket.gui.dialogs.flightconfiguration.FlightConfigurationDialog; -import net.sf.openrocket.gui.main.BasicFrame; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.Configuration; @@ -28,8 +25,6 @@ import net.sf.openrocket.util.StateChangeListener; public class FlightConfigurationModel implements ComboBoxModel, StateChangeListener { private static final Translator trans = Application.getTranslator(); - private static final String EDIT = trans.get("MotorCfgModel.Editcfg"); - private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); @@ -37,20 +32,13 @@ public class FlightConfigurationModel implements ComboBoxModel, StateChangeListe private final Configuration config; private final Rocket rocket; - private final boolean showEditElement; private Map map = new HashMap(); public FlightConfigurationModel(Configuration config) { - this(config, true); - } - - - public FlightConfigurationModel(Configuration config, boolean showEditElement) { this.config = config; this.rocket = config.getRocket(); - this.showEditElement = showEditElement; config.addChangeListener(this); } @@ -61,23 +49,15 @@ public class FlightConfigurationModel implements ComboBoxModel, StateChangeListe if (index < 0) return null; - if ((showEditElement && index > ids.length) || - (!showEditElement && index >= ids.length)) + if ( index >= ids.length) return null; - if (index == ids.length) - return EDIT; - return get(ids[index]); } @Override public int getSize() { - if (showEditElement) { - return rocket.getFlightConfigurationIDs().length + 1; - } else { - return rocket.getFlightConfigurationIDs().length; - } + return rocket.getFlightConfigurationIDs().length; } @Override @@ -91,19 +71,6 @@ public class FlightConfigurationModel implements ComboBoxModel, StateChangeListe // Clear selection - huh? return; } - if (item == EDIT) { - - // Open edit dialog in the future, after combo box has closed - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - new FlightConfigurationDialog(rocket, BasicFrame.findFrame(rocket)) - .setVisible(true); - } - }); - - return; - } if (!(item instanceof ID)) { throw new IllegalArgumentException("MotorConfigurationModel item=" + item); } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/FlightConfigurationDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/FlightConfigurationDialog.java deleted file mode 100644 index 487b0ac66..000000000 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/FlightConfigurationDialog.java +++ /dev/null @@ -1,231 +0,0 @@ -package net.sf.openrocket.gui.dialogs.flightconfiguration; - -import java.awt.Window; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTabbedPane; - -import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.document.OpenRocketDocument; -import net.sf.openrocket.document.Simulation; -import net.sf.openrocket.gui.adaptors.FlightConfigurationModel; -import net.sf.openrocket.gui.main.BasicFrame; -import net.sf.openrocket.gui.util.GUIUtil; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.startup.Application; - -/** - * Dialog for configuring all flight-configuration specific properties. - * Content of individual tabs are in separate classes. - */ -public class FlightConfigurationDialog extends JDialog { - - private static final Translator trans = Application.getTranslator(); - - private final Rocket rocket; - - private FlightConfigurationModel flightConfigurationModel; - - private final JButton renameConfButton, removeConfButton, copyConfButton; - - private final MotorConfigurationPanel motorConfigurationPanel; - private final RecoveryConfigurationPanel recoveryConfigurationPanel; - private final SeparationConfigurationPanel separationConfigurationPanel; - - - public FlightConfigurationDialog(final Rocket rocket, Window parent) { - //// Edit motor configurations - super(parent, trans.get("edtmotorconfdlg.title.Editmotorconf"), ModalityType.APPLICATION_MODAL); - - if (parent != null) - this.setModalityType(ModalityType.DOCUMENT_MODAL); - else - this.setModalityType(ModalityType.APPLICATION_MODAL); - - this.rocket = rocket; - - JPanel panel = new JPanel(new MigLayout("fill")); - - JLabel label = new JLabel(trans.get("edtmotorconfdlg.lbl.Selectedconf")); - panel.add(label, "span, split"); - - flightConfigurationModel = new FlightConfigurationModel(rocket.getDefaultConfiguration(), false); - JComboBox configSelector = new JComboBox(flightConfigurationModel); - configSelector.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - configurationChanged(); - } - }); - - panel.add(configSelector, "growx, gapright para"); - - JButton newConfButton = new JButton(trans.get("edtmotorconfdlg.but.Newconfiguration")); - newConfButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - addConfiguration(); - } - - }); - - panel.add(newConfButton); - - renameConfButton = new JButton(trans.get("edtmotorconfdlg.but.Renameconfiguration")); - renameConfButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - renameConfiguration(); - } - }); - panel.add(renameConfButton); - - removeConfButton = new JButton(trans.get("edtmotorconfdlg.but.Removeconfiguration")); - removeConfButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - removeConfiguration(); - } - }); - panel.add(removeConfButton); - - copyConfButton = new JButton(trans.get("edtmotorconfdlg.but.Copyconfiguration")); - copyConfButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - copyConfiguration(); - } - }); - panel.add(copyConfButton, "wrap para"); - - - //// Tabs for advanced view. - JTabbedPane tabs = new JTabbedPane(); - panel.add(tabs, "grow, spanx, w 700lp, h 500lp, wrap"); - - //// Motor tabs - motorConfigurationPanel = new MotorConfigurationPanel(this, rocket); - tabs.add(trans.get("edtmotorconfdlg.lbl.Motortab"), motorConfigurationPanel); - //// Recovery tab - recoveryConfigurationPanel = new RecoveryConfigurationPanel(this, rocket); - tabs.add(trans.get("edtmotorconfdlg.lbl.Recoverytab"), recoveryConfigurationPanel); - - //// Stage tab - separationConfigurationPanel = new SeparationConfigurationPanel(this, rocket); - if (rocket.getStageCount() > 1) { - tabs.add(trans.get("edtmotorconfdlg.lbl.Stagetab"), separationConfigurationPanel); - } - - - //// Close button - JButton close = new JButton(trans.get("dlg.but.close")); - close.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - FlightConfigurationDialog.this.dispose(); - } - }); - panel.add(close, "spanx, right"); - - this.add(panel); - this.validate(); - this.pack(); - - updateButtonState(); - - this.setLocationByPlatform(true); - GUIUtil.setDisposableDialogOptions(this, close); - - // Undo description - final OpenRocketDocument document = BasicFrame.findDocument(rocket); - if (document != null) { - //// Edit motor configurations - document.startUndo(trans.get("edtmotorconfdlg.title.Editmotorconf")); - this.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - document.stopUndo(); - } - }); - } - } - - private void configurationChanged() { - motorConfigurationPanel.fireTableDataChanged(); - recoveryConfigurationPanel.fireTableDataChanged(); - separationConfigurationPanel.fireTableDataChanged(); - updateButtonState(); - } - - private void addConfiguration() { - String newId = rocket.newFlightConfigurationID(); - rocket.getDefaultConfiguration().setFlightConfigurationID(newId); - - // Create a new simulation for this configuration. - createSimulationForNewConfiguration(); - - configurationChanged(); - } - - private void copyConfiguration() { - String currentId = rocket.getDefaultConfiguration().getFlightConfigurationID(); - - // currentID is the currently selected configuration. - String newConfigId = rocket.newFlightConfigurationID(); - String oldName = rocket.getFlightConfigurationName(currentId); - - for (RocketComponent c : rocket) { - if (c instanceof FlightConfigurableComponent) { - ((FlightConfigurableComponent) c).cloneFlightConfiguration(currentId, newConfigId); - } - } - rocket.setFlightConfigurationName(currentId, oldName); - rocket.getDefaultConfiguration().setFlightConfigurationID(newConfigId); - - // Create a new simulation for this configuration. - createSimulationForNewConfiguration(); - - configurationChanged(); - } - - /** - * prereq - assumes that the new configuration has been set as the default configuration. - */ - private void createSimulationForNewConfiguration() { - Simulation newSim = new Simulation(rocket); - OpenRocketDocument doc = BasicFrame.findDocument(rocket); - newSim.setName(doc.getNextSimulationName()); - doc.addSimulation(newSim); - } - - private void renameConfiguration() { - new RenameConfigDialog(this, rocket).setVisible(true); - } - - private void removeConfiguration() { - String currentId = rocket.getDefaultConfiguration().getFlightConfigurationID(); - if (currentId == null) - return; - rocket.removeFlightConfigurationID(currentId); - rocket.getDefaultConfiguration().setFlightConfigurationID(null); - configurationChanged(); - } - - private void updateButtonState() { - String currentId = rocket.getDefaultConfiguration().getFlightConfigurationID(); - removeConfButton.setEnabled(currentId != null); - renameConfButton.setEnabled(currentId != null); - } - -} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java deleted file mode 100644 index 95692a29b..000000000 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationPanel.java +++ /dev/null @@ -1,309 +0,0 @@ -package net.sf.openrocket.gui.dialogs.flightconfiguration; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; - -import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.gui.components.DescriptionArea; -import net.sf.openrocket.gui.components.StyledLabel; -import net.sf.openrocket.gui.components.StyledLabel.Style; -import net.sf.openrocket.gui.dialogs.motor.MotorChooserDialog; -import net.sf.openrocket.gui.util.GUIUtil; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.motor.Motor; -import net.sf.openrocket.rocketcomponent.MotorConfiguration; -import net.sf.openrocket.rocketcomponent.MotorMount; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.startup.Application; - -public class MotorConfigurationPanel extends JPanel { - - private static final Translator trans = Application.getTranslator(); - - private final FlightConfigurationDialog flightConfigurationDialog; - private final Rocket rocket; - - private final JTable configurationTable; - private final MotorConfigurationTableModel configurationTableModel; - private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton; - - private final MotorChooserDialog dialog; - - MotorConfigurationPanel(FlightConfigurationDialog flightConfigurationDialog, Rocket rocket) { - super(new MigLayout("fill")); - dialog = new MotorChooserDialog(flightConfigurationDialog); - this.flightConfigurationDialog = flightConfigurationDialog; - this.rocket = rocket; - - DescriptionArea desc = new DescriptionArea(trans.get("description"), 3, -1); - this.add(desc, "spanx, growx, wrap para"); - - - //// Motor mount selection - JLabel label = new StyledLabel(trans.get("lbl.motorMounts"), Style.BOLD); - this.add(label, ""); - - //// Motor selection - label = new StyledLabel(trans.get("lbl.motorConfiguration"), Style.BOLD); - this.add(label, "wrap rel"); - - - //// Motor Mount selection - { - MotorMountConfigurationPanel mountConfigPanel = new MotorMountConfigurationPanel(this,rocket) { - @Override - public void onDataChanged() { - MotorConfigurationPanel.this.fireTableDataChanged(); - - } - }; - this.add(mountConfigPanel, "w 200lp, h 150lp, grow"); - } - - //// Motor selection table. - configurationTableModel = new MotorConfigurationTableModel(rocket); - configurationTable = new JTable(configurationTableModel); - configurationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - configurationTable.setRowSelectionAllowed(true); - configurationTable.setDefaultRenderer(Object.class, new MotorTableCellRenderer()); - - configurationTable.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - updateButtonState(); - int selectedColumn = configurationTable.getSelectedColumn(); - if (e.getClickCount() == 2) { - if (selectedColumn == 2) { - // user double clicked in ignition column - selectIgnition(); - } else { - // Double-click edits motor - selectMotor(); - } - } - } - }); - - JScrollPane scroll = new JScrollPane(configurationTable); - this.add(scroll, "w 500lp, h 150lp, grow, wrap"); - - //// Select motor - selectMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectMotor")); - selectMotorButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - selectMotor(); - } - }); - this.add(selectMotorButton, "skip, split, sizegroup button"); - - //// Remove motor button - removeMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.removeMotor")); - removeMotorButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - removeMotor(); - } - }); - this.add(removeMotorButton, "sizegroup button"); - - //// Select Ignition button - selectIgnitionButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectIgnition")); - selectIgnitionButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - selectIgnition(); - } - }); - this.add(selectIgnitionButton, "sizegroup button"); - - //// Reset Ignition button - resetIgnitionButton = new JButton(trans.get("MotorConfigurationPanel.btn.resetIgnition")); - resetIgnitionButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - resetIgnition(); - } - }); - this.add(resetIgnitionButton, "sizegroup button, wrap"); - - } - - public void fireTableDataChanged() { - int selected = configurationTable.getSelectedRow(); - configurationTableModel.fireTableDataChanged(); - if (selected >= 0) { - selected = Math.min(selected, configurationTable.getRowCount() - 1); - configurationTable.getSelectionModel().setSelectionInterval(selected, selected); - } - updateButtonState(); - } - - private void updateButtonState() { - String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); - MotorMount currentMount = getCurrentMount(); - selectMotorButton.setEnabled(currentMount != null && currentID != null); - removeMotorButton.setEnabled(currentMount != null && currentID != null); - selectIgnitionButton.setEnabled(currentMount != null && currentID != null); - resetIgnitionButton.setEnabled(currentMount != null && currentID != null); - } - - - private MotorMount getCurrentMount() { - int row = configurationTable.getSelectedRow(); - if (row < 0) { - return null; - } - - return getMount(row); - } - - - private MotorMount getMount(int row) { - int count = 0; - for (RocketComponent c : rocket) { - if (c instanceof MotorMount) { - MotorMount mount = (MotorMount) c; - if (mount.isMotorMount()) { - count++; - } - if (count > row) { - return mount; - } - } - } - - throw new IndexOutOfBoundsException("Invalid row, row=" + row + " count=" + count); - } - - - - private void selectMotor() { - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - MotorMount mount = getCurrentMount(); - if (id == null || mount == null) - return; - - MotorConfiguration config = mount.getMotorConfiguration().get(id); - - dialog.setMotorMountAndConfig(mount, id); - dialog.setVisible(true); - Motor m = dialog.getSelectedMotor(); - double d = dialog.getSelectedDelay(); - - if (m != null) { - config = new MotorConfiguration(); - config.setMotor(m); - config.setEjectionDelay(d); - mount.getMotorConfiguration().set(id, config); - } - - fireTableDataChanged(); - } - - private void removeMotor() { - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - MotorMount mount = getCurrentMount(); - if (id == null || mount == null) - return; - - mount.getMotorConfiguration().resetDefault(id); - - fireTableDataChanged(); - } - - private void selectIgnition() { - String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); - MotorMount currentMount = getCurrentMount(); - if (currentID == null || currentMount == null) - return; - - IgnitionSelectionDialog dialog = new IgnitionSelectionDialog( - this.flightConfigurationDialog, - rocket, - currentMount); - dialog.setVisible(true); - - fireTableDataChanged(); - } - - - private void resetIgnition() { - String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID(); - MotorMount currentMount = getCurrentMount(); - if (currentID == null || currentMount == null) - return; - - currentMount.getIgnitionConfiguration().resetDefault(currentID); - - fireTableDataChanged(); - } - - - private class MotorTableCellRenderer extends DefaultTableCellRenderer { - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (!(c instanceof JLabel)) { - return c; - } - JLabel label = (JLabel) c; - - MotorMount mount = getMount(row); - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - - switch (column) { - case 0: - regular(label); - break; - - case 1: - if (mount.getMotorConfiguration().get(id).getMotor() != null) { - regular(label); - } else { - shaded(label); - } - break; - - case 2: - if (mount.getIgnitionConfiguration().isDefault(id)) { - shaded(label); - } else { - regular(label); - } - break; - } - - return label; - } - - private void shaded(JLabel label) { - GUIUtil.changeFontStyle(label, Font.ITALIC); - label.setForeground(Color.GRAY); - } - - private void regular(JLabel label) { - GUIUtil.changeFontStyle(label, Font.PLAIN); - label.setForeground(Color.BLACK); - } - - } - -} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationTableModel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationTableModel.java deleted file mode 100644 index ce900e72f..000000000 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorConfigurationTableModel.java +++ /dev/null @@ -1,146 +0,0 @@ -package net.sf.openrocket.gui.dialogs.flightconfiguration; - -import javax.swing.table.AbstractTableModel; - -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.motor.Motor; -import net.sf.openrocket.rocketcomponent.IgnitionConfiguration; -import net.sf.openrocket.rocketcomponent.MotorConfiguration; -import net.sf.openrocket.rocketcomponent.MotorMount; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.unit.UnitGroup; -import net.sf.openrocket.util.Chars; -import net.sf.openrocket.util.Coordinate; - -/** - * The table model for selecting and editing the motor configurations. - */ -class MotorConfigurationTableModel extends AbstractTableModel { - - private static final Translator trans = Application.getTranslator(); - - private static final String NONE = trans.get("edtmotorconfdlg.tbl.None"); - private static final String MOTOR_MOUNT = trans.get("edtmotorconfdlg.tbl.Mountheader"); - private static final String MOTOR = trans.get("edtmotorconfdlg.tbl.Motorheader"); - private static final String IGNITION = trans.get("edtmotorconfdlg.tbl.Ignitionheader"); - - private final Rocket rocket; - - - public MotorConfigurationTableModel(Rocket rocket) { - this.rocket = rocket; - } - - @Override - public int getColumnCount() { - return 3; - } - - @Override - public int getRowCount() { - int count = 0; - for (RocketComponent c : rocket) { - if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) { - count++; - } - } - return count; - } - - @Override - public Object getValueAt(int row, int column) { - switch (column) { - case 0: { - MotorMount mount = findMount(row); - String name = mount.toString(); - int count = getMountMultiplicity(mount); - if (count > 1) { - name = name + " (" + Chars.TIMES + count + ")"; - } - return name; - } - case 1: { - MotorMount mount = findMount(row); - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - MotorConfiguration config = mount.getMotorConfiguration().get(id); - Motor motor = config.getMotor(); - - if (motor == null) - return NONE; - - String str = motor.getDesignation(config.getEjectionDelay()); - int count = getMountMultiplicity(mount); - if (count > 1) { - str = "" + count + Chars.TIMES + " " + str; - } - return str; - } - case 2: { - return getIgnitionEventString(row); - - } - default: - throw new IndexOutOfBoundsException("column=" + column); - } - } - - - - @Override - public String getColumnName(int column) { - switch (column) { - case 0: - return MOTOR_MOUNT; - case 1: - return MOTOR; - case 2: - return IGNITION; - default: - throw new IndexOutOfBoundsException("column=" + column); - } - } - - - private MotorMount findMount(int row) { - int count = row; - for (RocketComponent c : rocket) { - if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) { - count--; - if (count < 0) { - return (MotorMount) c; - } - } - } - throw new IndexOutOfBoundsException("Requesting row=" + row + " but only " + getRowCount() + " rows exist"); - } - - - private int getMountMultiplicity(MotorMount mount) { - RocketComponent c = (RocketComponent) mount; - return c.toAbsolute(Coordinate.NUL).length; - } - - - - private String getIgnitionEventString(int row) { - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - MotorMount mount = findMount(row); - IgnitionConfiguration ignitionConfig = mount.getIgnitionConfiguration().get(id); - - IgnitionConfiguration.IgnitionEvent ignitionEvent = ignitionConfig.getIgnitionEvent(); - Double ignitionDelay = ignitionConfig.getIgnitionDelay(); - boolean isDefault = mount.getIgnitionConfiguration().isDefault(id); - - String str = trans.get("MotorMount.IgnitionEvent.short." + ignitionEvent.name()); - if (ignitionDelay > 0.001) { - str = str + " + " + UnitGroup.UNITS_SHORT_TIME.toStringUnit(ignitionDelay); - } - if (isDefault) { - String def = trans.get("table.ignition.default"); - str = def.replace("{0}", str); - } - return str; - } -} \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RecoveryConfigurationPanel.java deleted file mode 100644 index 42b974c1c..000000000 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RecoveryConfigurationPanel.java +++ /dev/null @@ -1,264 +0,0 @@ -package net.sf.openrocket.gui.dialogs.flightconfiguration; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -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.ListSelectionModel; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.DefaultTableCellRenderer; - -import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.gui.util.GUIUtil; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; -import net.sf.openrocket.rocketcomponent.DeploymentConfiguration.DeployEvent; -import net.sf.openrocket.rocketcomponent.RecoveryDevice; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.unit.UnitGroup; - -public class RecoveryConfigurationPanel extends JPanel { - - private Translator trans = Application.getTranslator(); - - - private final FlightConfigurationDialog flightConfigurationDialog; - private final Rocket rocket; - - private final RecoveryTableModel recoveryTableModel; - private final JTable recoveryTable; - private final JButton selectDeploymentButton; - private final JButton resetDeploymentButton; - - - RecoveryConfigurationPanel(FlightConfigurationDialog flightConfigurationDialog, Rocket rocket) { - super(new MigLayout("fill")); - this.flightConfigurationDialog = flightConfigurationDialog; - this.rocket = rocket; - - //// Recovery selection - recoveryTableModel = new RecoveryTableModel(); - recoveryTable = new JTable(recoveryTableModel); - recoveryTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - recoveryTable.setRowSelectionAllowed(true); - recoveryTable.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - updateButtonState(); - - if (e.getClickCount() == 2) { - // Double-click edits - selectDeployment(); - } - } - }); - recoveryTable.setDefaultRenderer(Object.class, new RecoveryTableCellRenderer()); - - JScrollPane scroll = new JScrollPane(recoveryTable); - this.add(scroll, "span, grow, wrap"); - - //// Select deployment - selectDeploymentButton = new JButton(trans.get("edtmotorconfdlg.but.Selectdeployment")); - selectDeploymentButton.setEnabled(false); - selectDeploymentButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - selectDeployment(); - } - }); - this.add(selectDeploymentButton, "skip, split, sizegroup button"); - - //// Reset deployment - resetDeploymentButton = new JButton(trans.get("edtmotorconfdlg.but.Resetdeployment")); - resetDeploymentButton.setEnabled(false); - resetDeploymentButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - resetDeployment(); - } - }); - this.add(resetDeploymentButton, "sizegroup button, wrap"); - } - - public void fireTableDataChanged() { - int selected = recoveryTable.getSelectedRow(); - recoveryTableModel.fireTableDataChanged(); - if (selected >= 0) { - selected = Math.min(selected, recoveryTable.getRowCount() - 1); - recoveryTable.getSelectionModel().setSelectionInterval(selected, selected); - } - updateButtonState(); - } - - private void selectDeployment() { - RecoveryDevice c = getSelectedComponent(); - if (c == null) { - return; - } - JDialog d = new DeploymentSelectionDialog(flightConfigurationDialog, rocket, c); - d.setVisible(true); - fireTableDataChanged(); - } - - private void resetDeployment() { - RecoveryDevice c = getSelectedComponent(); - if (c == null) { - return; - } - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - c.getDeploymentConfiguration().resetDefault(id); - fireTableDataChanged(); - } - - public void updateButtonState() { - boolean componentSelected = getSelectedComponent() != null; - selectDeploymentButton.setEnabled(componentSelected); - resetDeploymentButton.setEnabled(componentSelected); - } - - - private RecoveryDevice getSelectedComponent() { - int row = recoveryTable.getSelectedRow(); - return findRecoveryDevice(row); - } - - private RecoveryDevice findRecoveryDevice(int count) { - RecoveryDevice d = null; - Iterator it = rocket.iterator(); - while (it.hasNext() && count >= 0) { - RocketComponent c = it.next(); - if (c instanceof RecoveryDevice) { - d = (RecoveryDevice) c; - count--; - } - } - return d; - } - - - - private class RecoveryTableModel extends AbstractTableModel { - - @Override - public int getRowCount() { - int count = 0; - Iterator it = rocket.iterator(); - while (it.hasNext()) { - RocketComponent c = it.next(); - if (c instanceof RecoveryDevice) { - count++; - } - } - return count; - } - - @Override - public int getColumnCount() { - return 2; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - RecoveryDevice d = findRecoveryDevice(rowIndex); - switch (columnIndex) { - case 0: - return d.getName(); - case 1: - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - DeploymentConfiguration config = d.getDeploymentConfiguration().get(id); - boolean isDefault = d.getDeploymentConfiguration().isDefault(id); - - String str; - - str = trans.get("RecoveryDevice.DeployEvent.short." + config.getDeployEvent().name()); - if (config.getDeployEvent() == DeployEvent.ALTITUDE) { - str += " " + UnitGroup.UNITS_DISTANCE.toStringUnit(config.getDeployAltitude()); - } - if (config.getDeployDelay() > 0.001) { - str += " + " + UnitGroup.UNITS_SHORT_TIME.toStringUnit(config.getDeployDelay()); - } - - - if (isDefault) { - String def = trans.get("table.deployment.default"); - str = def.replace("{0}", str); - } - return str; - - default: - throw new IndexOutOfBoundsException("columnIndex=" + columnIndex); - } - - } - - @Override - public String getColumnName(int column) { - switch (column) { - case 0: - return trans.get("edtmotorconfdlg.tbl.Recoveryheader"); - case 1: - return trans.get("edtmotorconfdlg.tbl.Deploymentheader"); - default: - return ""; - } - } - - } - - - private class RecoveryTableCellRenderer extends DefaultTableCellRenderer { - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (!(c instanceof JLabel)) { - return c; - } - JLabel label = (JLabel) c; - - RecoveryDevice recoveryDevice = findRecoveryDevice(row); - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - - switch (column) { - case 0: - regular(label); - break; - - case 1: - if (recoveryDevice.getDeploymentConfiguration().isDefault(id)) { - shaded(label); - } else { - regular(label); - } - break; - } - - return label; - } - - private void shaded(JLabel label) { - GUIUtil.changeFontStyle(label, Font.ITALIC); - label.setForeground(Color.GRAY); - } - - private void regular(JLabel label) { - GUIUtil.changeFontStyle(label, Font.PLAIN); - label.setForeground(Color.BLACK); - } - - } - -} diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationConfigurationPanel.java deleted file mode 100644 index 8094a35d0..000000000 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/SeparationConfigurationPanel.java +++ /dev/null @@ -1,259 +0,0 @@ -package net.sf.openrocket.gui.dialogs.flightconfiguration; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -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.ListSelectionModel; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.DefaultTableCellRenderer; - -import net.miginfocom.swing.MigLayout; -import net.sf.openrocket.gui.util.GUIUtil; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.rocketcomponent.Stage; -import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.unit.UnitGroup; - -public class SeparationConfigurationPanel extends JPanel { - - private static final Translator trans = Application.getTranslator(); - - private final FlightConfigurationDialog flightConfigurationDialog; - private final Rocket rocket; - private final Stage[] stages; - - private final JTable separationTable; - private final SeparationTableModel separationTableModel; - private final JButton selectSeparationButton; - private final JButton resetDeploymentButton; - - - SeparationConfigurationPanel(FlightConfigurationDialog flightConfigurationDialog, Rocket rocket) { - super(new MigLayout("fill")); - this.flightConfigurationDialog = flightConfigurationDialog; - this.rocket = rocket; - - - int stageCount = rocket.getStageCount() - 1; - stages = new Stage[stageCount]; - Iterator it = rocket.iterator(); - { - int stageIndex = -1; - while (it.hasNext()) { - RocketComponent c = it.next(); - if (c instanceof Stage) { - if (stageIndex >= 0) { - stages[stageIndex] = (Stage) c; - } - stageIndex++; - } - } - } - - //// Recovery selection - separationTableModel = new SeparationTableModel(); - separationTable = new JTable(separationTableModel); - separationTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - separationTable.setRowSelectionAllowed(true); - separationTable.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - updateButtonState(); - if (e.getClickCount() == 2) { - // Double-click edits - selectDeployment(); - } - } - }); - separationTable.setDefaultRenderer(Object.class, new SeparationTableCellRenderer()); - - JScrollPane scroll = new JScrollPane(separationTable); - this.add(scroll, "span, grow, wrap"); - - //// Select deployment - selectSeparationButton = new JButton(trans.get("edtmotorconfdlg.but.Selectseparation")); - selectSeparationButton.setEnabled(false); - selectSeparationButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - selectDeployment(); - } - }); - this.add(selectSeparationButton, "skip, split, sizegroup button"); - - //// Reset deployment - resetDeploymentButton = new JButton(trans.get("edtmotorconfdlg.but.Resetseparation")); - resetDeploymentButton.setEnabled(false); - resetDeploymentButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - resetDeployment(); - } - }); - this.add(resetDeploymentButton, "sizegroup button, wrap"); - - } - - public void fireTableDataChanged() { - int selected = separationTable.getSelectedRow(); - separationTableModel.fireTableDataChanged(); - if (selected >= 0) { - selected = Math.min(selected, separationTable.getRowCount() - 1); - separationTable.getSelectionModel().setSelectionInterval(selected, selected); - } - updateButtonState(); - } - - private Stage getSelectedStage() { - int row = separationTable.getSelectedRow(); - return getStage(row); - } - - private Stage getStage(int row) { - if (row >= 0 && row < stages.length) { - return stages[row]; - } - return null; - } - - private void selectDeployment() { - Stage stage = getSelectedStage(); - if (stage == null) { - return; - } - JDialog d = new SeparationSelectionDialog(flightConfigurationDialog, rocket, stage); - d.setVisible(true); - fireTableDataChanged(); - } - - private void resetDeployment() { - Stage stage = getSelectedStage(); - if (stage == null) { - return; - } - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - stage.getStageSeparationConfiguration().resetDefault(id); - fireTableDataChanged(); - } - - public void updateButtonState() { - boolean componentSelected = getSelectedStage() != null; - selectSeparationButton.setEnabled(componentSelected); - resetDeploymentButton.setEnabled(componentSelected); - } - - private class SeparationTableModel extends AbstractTableModel { - - @Override - public int getRowCount() { - return stages.length; - } - - @Override - public int getColumnCount() { - return 2; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - Stage d = SeparationConfigurationPanel.this.stages[rowIndex]; - switch (columnIndex) { - case 0: - return d.getName(); - case 1: - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - StageSeparationConfiguration config = d.getStageSeparationConfiguration().get(id); - - String str; - - str = config.getSeparationEvent().toString(); - if (config.getSeparationDelay() > 0.001) { - str += " + " + UnitGroup.UNITS_SHORT_TIME.toStringUnit(config.getSeparationDelay()); - } - - if (d.getStageSeparationConfiguration().isDefault(id)) { - String def = trans.get("SeparationConfigurationPanel.table.separation.default"); - str = def.replace("{0}", str); - } - - return str; - - default: - throw new IndexOutOfBoundsException("column=" + columnIndex); - } - - } - - @Override - public String getColumnName(int column) { - switch (column) { - case 0: - return trans.get("edtmotorconfdlg.tbl.Stageheader"); - case 1: - return trans.get("edtmotorconfdlg.tbl.Separationheader"); - default: - return ""; - } - } - } - - - private class SeparationTableCellRenderer extends DefaultTableCellRenderer { - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (!(c instanceof JLabel)) { - return c; - } - JLabel label = (JLabel) c; - - Stage stage = getStage(row); - String id = rocket.getDefaultConfiguration().getFlightConfigurationID(); - - switch (column) { - case 0: - regular(label); - break; - - case 1: - if (stage.getStageSeparationConfiguration().isDefault(id)) { - shaded(label); - } else { - regular(label); - } - break; - } - - return label; - } - - private void shaded(JLabel label) { - GUIUtil.changeFontStyle(label, Font.ITALIC); - label.setForeground(Color.GRAY); - } - - private void regular(JLabel label) { - GUIUtil.changeFontStyle(label, Font.PLAIN); - label.setForeground(Color.BLACK); - } - - } - - -} diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index 5ae1d1581..c458b8e66 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -6,7 +6,6 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Point; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.util.ArrayList; @@ -22,9 +21,7 @@ import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ComboBoxModel; import javax.swing.DefaultComboBoxModel; -import javax.swing.JButton; import javax.swing.JComboBox; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSlider; @@ -48,7 +45,6 @@ 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.dialogs.flightconfiguration.FlightConfigurationDialog; import net.sf.openrocket.gui.figure3d.RocketFigure3d; import net.sf.openrocket.gui.figureelements.CGCaret; import net.sf.openrocket.gui.figureelements.CPCaret; @@ -323,20 +319,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change JLabel label = new JLabel(trans.get("RocketPanel.lbl.Flightcfg")); label.setHorizontalAlignment(JLabel.RIGHT); add(label, "growx, right"); - add(new JComboBox(new FlightConfigurationModel(configuration)), ""); - - //// Edit button - JButton button = new JButton(trans.get("RocketPanel.but.FlightcfgEdit")); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JDialog configDialog = new FlightConfigurationDialog(document.getRocket(), SwingUtilities.windowForComponent(RocketPanel.this)); - configDialog.show(); - } - }); - add(button, "wrap"); - - + add(new JComboBox(new FlightConfigurationModel(configuration)), "wrap"); // Create slider and scroll pane diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java index ae0784c30..53468bb61 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java @@ -13,7 +13,6 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.JTextField; -import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; @@ -23,7 +22,6 @@ import net.miginfocom.swing.MigLayout; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.gui.adaptors.FlightConfigurationModel; -import net.sf.openrocket.gui.dialogs.flightconfiguration.FlightConfigurationDialog; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.Configuration; @@ -158,18 +156,7 @@ public class SimulationEditDialog extends JDialog { conditions.setMotorConfigurationID(configuration.getFlightConfigurationID()); } }); - panel.add(combo, "span, split"); - - //// Edit button - JButton button = new JButton(trans.get("simedtdlg.but.FlightcfgEdit")); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JDialog configDialog = new FlightConfigurationDialog(SimulationEditDialog.this.document.getRocket(), SwingUtilities.windowForComponent(SimulationEditDialog.this)); - configDialog.setVisible(true); - } - }); - panel.add(button, "align left"); + panel.add(combo, "span"); panel.add(new JPanel(), "growx, wrap"); From 2a7cc37d255831b876d7468fb07e1fd8c9e964af Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 9 Oct 2013 14:35:23 -0500 Subject: [PATCH 40/47] Added in plain junit formatter to get output from travis. --- core/build.xml | 1 + swing/build.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/core/build.xml b/core/build.xml index 819548e25..bc94e848c 100644 --- a/core/build.xml +++ b/core/build.xml @@ -105,6 +105,7 @@ +
diff --git a/swing/build.xml b/swing/build.xml index 748b52946..53afdf087 100644 --- a/swing/build.xml +++ b/swing/build.xml @@ -285,6 +285,7 @@ ${nonascii} +
From a601788dcdd244abd2d76b8dcebf67796b6bf708 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 9 Oct 2013 14:52:36 -0500 Subject: [PATCH 41/47] Fixed integer test value. Thank goodness for unit tests. --- core/src/net/sf/openrocket/unit/Unit.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/net/sf/openrocket/unit/Unit.java b/core/src/net/sf/openrocket/unit/Unit.java index a0522c2ba..75931d050 100644 --- a/core/src/net/sf/openrocket/unit/Unit.java +++ b/core/src/net/sf/openrocket/unit/Unit.java @@ -104,7 +104,7 @@ public abstract class Unit { val = roundForDecimalFormat(val); // Check for approximate integer - if (Math.abs(val - Math.floor(val)) < 0.001) { + if (Math.abs(val - Math.floor(val)) < 0.0001) { return intFormat.format(val); } return decFormat.format(val); From 6641f959c5a2baf4af1fc7fc41799874184ce69d Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Wed, 9 Oct 2013 16:06:30 -0500 Subject: [PATCH 42/47] When deleting a flight configuration, first change the configuration to a different one. --- core/src/net/sf/openrocket/rocketcomponent/Rocket.java | 7 +++++++ .../main/flightconfigpanel/FlightConfigurationPanel.java | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index 144706f7b..2bbe48757 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -545,7 +545,14 @@ public class Rocket extends RocketComponent { checkState(); if (id == null) return; + // Get current configuration: + String currentId = getDefaultConfiguration().getFlightConfigurationID(); + // If we're removing the current configuration, we need to switch to a different one first. + if (currentId != null && currentId.equals(id)) { + getDefaultConfiguration().setFlightConfigurationID(null); + } flightConfigurationIDs.remove(id); + // FIXME - remove corresponding simulations? fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); } 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 de8001ead..a9f3e2119 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurationPanel.java @@ -156,7 +156,6 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe if (currentId == null) return; rocket.removeFlightConfigurationID(currentId); - rocket.getDefaultConfiguration().setFlightConfigurationID(null); configurationChanged(); } From 86a273272b780212be254e998e471caeb0c2c9f6 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Thu, 10 Oct 2013 11:01:21 -0500 Subject: [PATCH 43/47] Rework the motor table entry. --- .../FlightConfigurablePanel.java | 3 - .../MotorConfigurationPanel.java | 84 +++++++++++++++++-- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java index 4b65690fc..4cfa8413a 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java @@ -139,9 +139,6 @@ public abstract class FlightConfigurablePanel { @@ -220,15 +234,62 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } - private class MotorTableCellRenderer extends FlightConfigurablePanel.FlightConfigurableCellRenderer { - + private class MotorTableCellRenderer implements TableCellRenderer { + private void setSelected( JComponent c, JTable table, boolean isSelected, boolean hasFocus ) { + c.setOpaque(true); + if ( isSelected) { + c.setBackground(table.getSelectionBackground()); + c.setForeground(table.getSelectionForeground()); + } else { + c.setBackground(table.getBackground()); + c.setForeground(table.getForeground()); + } + Border b = null; + if ( hasFocus ) { + if (isSelected) { + b = UIManager.getBorder("Table.focusSelectedCellHighlightBorder"); + } else { + b = UIManager.getBorder("Table.focusCellHighligtBorder"); + } + } else { + b = new EmptyBorder(1,1,1,1); + } + c.setBorder(b); + } + @Override - protected void format(MotorMount mount, String configId, JLabel label) { + public Component getTableCellRendererComponent(JTable table,Object value, boolean isSelected, boolean hasFocus, int row,int column) { + switch (column) { + case 0: { + JLabel label = new JLabel(descriptor.format(rocket, (String) value)); + setSelected(label, table, isSelected, hasFocus); + return label; + } + default: { + Pair v = (Pair) value; + String id = v.getU(); + MotorMount component = v.getV(); + JLabel label = format(component, id); + setSelected(label, table, isSelected, hasFocus); + return label; + } + } + } + + protected JLabel format(MotorMount mount, String configId) { + JLabel label = new JLabel(); + label.setLayout(new BoxLayout(label, BoxLayout.X_AXIS)); MotorConfiguration motorConfig = mount.getMotorConfiguration().get(configId); String motorString = getMotorSpecification(mount, motorConfig); - String ignitionString = getIgnitionEventString(configId, mount); - label.setText(motorString + " " + ignitionString); + JLabel motorDescriptionLabel = new JLabel(motorString); + label.add(motorDescriptionLabel); + label.add( Box.createRigidArea(new Dimension(10,0))); + JLabel ignitionLabel = getIgnitionEventString(configId, mount); + label.add(ignitionLabel); + label.validate(); + return label; +// label.setText(motorString + " " + ignitionString); } private String getMotorSpecification(MotorMount mount, MotorConfiguration motorConfig) { @@ -250,24 +311,31 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel return c.toAbsolute(Coordinate.NUL).length; } + protected final void shaded(JLabel label) { + GUIUtil.changeFontStyle(label, Font.ITALIC); + label.setForeground(Color.GRAY); + } - private String getIgnitionEventString(String id, MotorMount mount) { + private JLabel getIgnitionEventString(String id, MotorMount mount) { IgnitionConfiguration ignitionConfig = mount.getIgnitionConfiguration().get(id); IgnitionConfiguration.IgnitionEvent ignitionEvent = ignitionConfig.getIgnitionEvent(); Double ignitionDelay = ignitionConfig.getIgnitionDelay(); boolean isDefault = mount.getIgnitionConfiguration().isDefault(id); + JLabel label = new JLabel(); String str = trans.get("MotorMount.IgnitionEvent.short." + ignitionEvent.name()); - if (ignitionDelay > 0.001) { + if (ignitionEvent != IgnitionEvent.NEVER && ignitionDelay > 0.001) { str = str + " + " + UnitGroup.UNITS_SHORT_TIME.toStringUnit(ignitionDelay); } if (isDefault) { + shaded(label); String def = trans.get("MotorConfigurationTableModel.table.ignition.default"); str = def.replace("{0}", str); } - return str; + label.setText(str); + return label; } } From 30b89a54cfa17cc68f69fa558605dba654ba76f5 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Thu, 10 Oct 2013 19:30:54 -0500 Subject: [PATCH 44/47] Add a little fuzz to the motor diameter detection. --- .../gui/dialogs/motor/thrustcurve/MotorFilterPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java index e23216b68..4ce03897d 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java @@ -298,7 +298,7 @@ public abstract class MotorFilterPanel extends JPanel { // find the next largest diameter int i; for( i =0; i< diameterValues.length; i++ ) { - if ( mountDiameter< diameterValues[i] ) { + if ( mountDiameter< diameterValues[i] - 0.0005 ) { break; } } From 42e70d35521df97e6b9d853c0c2aba73bb1ad8b0 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Thu, 10 Oct 2013 20:53:48 -0500 Subject: [PATCH 45/47] Added boolean preference to toggle if the motor filter automatically limits the length to the motor mount length. --- core/resources/l10n/messages.properties | 1 + .../motor/thrustcurve/MotorFilterPanel.java | 39 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 8246fd703..1a4e93722 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1085,6 +1085,7 @@ StorageOptChooser.lbl.Saveopt = Save options TCMotorSelPan.lbl.Selrocketmotor = Select rocket motor: TCMotorSelPan.checkbox.hideSimilar = Hide very similar thrust curves TCMotorSelPan.checkbox.hideUsed = Hide motors already used in the mount +TCMotorSelPan.checkbox.limitlength = Limit motor length to mount length TCMotorSelPan.btn.details = Show Details TCMotorSelPan.btn.filter = Filter Motors TCMotorSelPan.MotorSize = Motor Dimensions diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java index 4ce03897d..71109d0ab 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java @@ -79,6 +79,10 @@ public abstract class MotorFilterPanel extends JPanel { private final MotorRowFilter filter; + private final JCheckBox limitLengthCheckBox; + private boolean limitLength = false; + private Double mountLength = null; + // Things we change the label on based on the MotorMount. private final JLabel motorMountDimension; private final MultiSlider lengthSlider; @@ -91,6 +95,8 @@ public abstract class MotorFilterPanel extends JPanel { List unselectedManusFromPreferences = ((SwingPreferences) Application.getPreferences()).getExcludedMotorManufacturers(); filter.setExcludedManufacturers(unselectedManusFromPreferences); + limitLength = ((SwingPreferences) Application.getPreferences()).getBoolean("motorFilterLimitLength", false); + //// Hide used motor files { final JCheckBox hideUsedBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideUsed")); @@ -237,6 +243,20 @@ public abstract class MotorFilterPanel extends JPanel { { sub.add( new JLabel(trans.get("TCMotorSelPan.Length")), "split 2, wrap"); + limitLengthCheckBox = new JCheckBox( trans.get("TCMotorSelPan.checkbox.limitlength")); + GUIUtil.changeFontSize(limitLengthCheckBox, -1); + limitLengthCheckBox.setSelected(limitLength); + limitLengthCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MotorFilterPanel.this.setLimitLength( limitLengthCheckBox.isSelected()); + onSelectionChanged(); + } + }); + + sub.add( limitLengthCheckBox, "gapleft para, spanx, growx, wrap" ); + + final DoubleModel minimumLength = new DoubleModel(filter, "MinimumLength", UnitGroup.UNITS_MOTOR_DIMENSIONS, 0); final DoubleModel maximumLength = new DoubleModel(filter, "MaximumLength", UnitGroup.UNITS_MOTOR_DIMENSIONS, 0); @@ -288,11 +308,16 @@ public abstract class MotorFilterPanel extends JPanel { onSelectionChanged(); if ( mount == null ) { // Disable diameter controls? + mountLength = null; lengthSlider.setValueAt(1, 1000); motorMountDimension.setText(""); } else { - double mountLength = ((RocketComponent)mount).getLength(); - lengthSlider.setValueAt(1, (int) Math.min(1000,Math.round(1000*mountLength))); + mountLength = ((RocketComponent)mount).getLength(); + if ( limitLength ) { + lengthSlider.setValueAt(1, (int) Math.min(1000,Math.round(1000*mountLength))); + } else { + lengthSlider.setValueAt(1, 1000); + } double mountDiameter = mount.getMotorMountDiameter(); // find the next largest diameter @@ -312,6 +337,16 @@ public abstract class MotorFilterPanel extends JPanel { } } + private void setLimitLength( boolean limitLength ) { + this.limitLength = limitLength; + ((SwingPreferences) Application.getPreferences()).putBoolean("motorFilterLimitLength", limitLength); + if ( mountLength != null & limitLength ) { + lengthSlider.setValueAt(1, (int) Math.min(1000,Math.round(1000*mountLength))); + } else { + lengthSlider.setValueAt(1, 1000); + } + } + public abstract void onSelectionChanged(); } From 858291de7cf5411918e9ff9d0c6f2c92a8f33d3a Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Fri, 11 Oct 2013 13:56:13 -0500 Subject: [PATCH 46/47] Added limit diameter checkbox and preference. Refactored some code in MotorFilterPanel to make it nicer. --- core/resources/l10n/messages.properties | 1 + .../motor/thrustcurve/MotorFilterPanel.java | 71 ++++++++++++------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 1a4e93722..3a3c4ab53 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1086,6 +1086,7 @@ TCMotorSelPan.lbl.Selrocketmotor = Select rocket motor: TCMotorSelPan.checkbox.hideSimilar = Hide very similar thrust curves TCMotorSelPan.checkbox.hideUsed = Hide motors already used in the mount TCMotorSelPan.checkbox.limitlength = Limit motor length to mount length +TCMotorSelPan.checkbox.limitdiameter = Limit motor diameter to mount diameter TCMotorSelPan.btn.details = Show Details TCMotorSelPan.btn.filter = Filter Motors TCMotorSelPan.MotorSize = Motor Dimensions diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java index 71109d0ab..31ebf72bd 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java @@ -83,6 +83,10 @@ public abstract class MotorFilterPanel extends JPanel { private boolean limitLength = false; private Double mountLength = null; + private final JCheckBox limitDiameterCheckBox; + private boolean limitDiameter = false; + private Double mountDiameter = null; + // Things we change the label on based on the MotorMount. private final JLabel motorMountDimension; private final MultiSlider lengthSlider; @@ -96,6 +100,7 @@ public abstract class MotorFilterPanel extends JPanel { filter.setExcludedManufacturers(unselectedManusFromPreferences); limitLength = ((SwingPreferences) Application.getPreferences()).getBoolean("motorFilterLimitLength", false); + limitDiameter = ((SwingPreferences) Application.getPreferences()).getBoolean("motorFilterLimitDiameter", false); //// Hide used motor files { @@ -111,7 +116,6 @@ public abstract class MotorFilterPanel extends JPanel { this.add(hideUsedBox, "gapleft para, spanx, growx, wrap"); } - // Manufacturer selection JPanel sub = new JPanel(new MigLayout("fill")); TitledBorder border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.MANUFACTURER")); @@ -206,7 +210,7 @@ public abstract class MotorFilterPanel extends JPanel { this.add(sub,"grow, wrap"); - // Diameter selection + // Motor Dimensions sub = new JPanel(new MigLayout("fill")); TitledBorder diameterTitleBorder = BorderFactory.createTitledBorder(trans.get("TCMotorSelPan.MotorSize")); GUIUtil.changeFontStyle(diameterTitleBorder, Font.BOLD); @@ -215,8 +219,23 @@ public abstract class MotorFilterPanel extends JPanel { motorMountDimension = new JLabel(); GUIUtil.changeFontSize(motorMountDimension, -1); sub.add(motorMountDimension,"growx,wrap"); + + // Diameter selection { - sub.add( new JLabel("Diameter"), "split 2, wrap"); + sub.add( new JLabel(trans.get("TCMotorSelPan.Diameter")), "split 2, wrap"); + limitDiameterCheckBox = new JCheckBox( trans.get("TCMotorSelPan.checkbox.limitdiameter")); + GUIUtil.changeFontSize(limitDiameterCheckBox, -1); + limitDiameterCheckBox.setSelected(limitDiameter); + limitDiameterCheckBox.addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent e) { + limitDiameter = limitDiameterCheckBox.isSelected(); + MotorFilterPanel.this.setLimitDiameter(); + onSelectionChanged(); + } + }); + sub.add( limitDiameterCheckBox, "gapleft para, spanx, growx, wrap" ); + diameterSlider = new MultiSlider(MultiSlider.HORIZONTAL,0, diameterValues.length-1, 0, diameterValues.length-1); diameterSlider.setBounded(true); // thumbs cannot cross diameterSlider.setMajorTickSpacing(1); @@ -242,14 +261,14 @@ public abstract class MotorFilterPanel extends JPanel { { sub.add( new JLabel(trans.get("TCMotorSelPan.Length")), "split 2, wrap"); - limitLengthCheckBox = new JCheckBox( trans.get("TCMotorSelPan.checkbox.limitlength")); GUIUtil.changeFontSize(limitLengthCheckBox, -1); limitLengthCheckBox.setSelected(limitLength); limitLengthCheckBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - MotorFilterPanel.this.setLimitLength( limitLengthCheckBox.isSelected()); + limitLength = limitLengthCheckBox.isSelected(); + MotorFilterPanel.this.setLimitLength(); onSelectionChanged(); } }); @@ -309,17 +328,30 @@ public abstract class MotorFilterPanel extends JPanel { if ( mount == null ) { // Disable diameter controls? mountLength = null; - lengthSlider.setValueAt(1, 1000); + mountDiameter = null; motorMountDimension.setText(""); } else { mountLength = ((RocketComponent)mount).getLength(); - if ( limitLength ) { - lengthSlider.setValueAt(1, (int) Math.min(1000,Math.round(1000*mountLength))); - } else { - lengthSlider.setValueAt(1, 1000); - } + mountDiameter = mount.getMotorMountDiameter(); + motorMountDimension.setText( trans.get("TCMotorSelPan.MotorMountDimensions") + " " + + UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountDiameter)+ " x " + UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountLength)); + } + setLimitLength(); + setLimitDiameter(); + } - double mountDiameter = mount.getMotorMountDiameter(); + private void setLimitLength( ) { + ((SwingPreferences) Application.getPreferences()).putBoolean("motorFilterLimitLength", limitLength); + if ( mountLength != null & limitLength ) { + lengthSlider.setValueAt(1, (int) Math.min(1000,Math.round(1000*mountLength))); + } else { + lengthSlider.setValueAt(1, 1000); + } + } + + private void setLimitDiameter( ) { + ((SwingPreferences) Application.getPreferences()).putBoolean("motorFilterLimitDiameter", limitDiameter); + if ( limitDiameter && mountDiameter != null) { // find the next largest diameter int i; for( i =0; i< diameterValues.length; i++ ) { @@ -331,22 +363,11 @@ public abstract class MotorFilterPanel extends JPanel { i--; } diameterSlider.setValueAt(1, i-1); - - motorMountDimension.setText( trans.get("TCMotorSelPan.MotorMountDimensions") + " " + - UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountDiameter)+ " x " + UnitGroup.UNITS_MOTOR_DIMENSIONS.toStringUnit(mountLength)); - } - } - - private void setLimitLength( boolean limitLength ) { - this.limitLength = limitLength; - ((SwingPreferences) Application.getPreferences()).putBoolean("motorFilterLimitLength", limitLength); - if ( mountLength != null & limitLength ) { - lengthSlider.setValueAt(1, (int) Math.min(1000,Math.round(1000*mountLength))); } else { - lengthSlider.setValueAt(1, 1000); + diameterSlider.setValueAt(1, diameterValues.length-1); } } - + public abstract void onSelectionChanged(); } From e58a34040e0ef75594548a09d64892ae1790766f Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Fri, 11 Oct 2013 14:07:12 -0500 Subject: [PATCH 47/47] Change alignment of lower buttons in configuration tab. --- .../gui/main/flightconfigpanel/MotorConfigurationPanel.java | 2 +- .../gui/main/flightconfigpanel/RecoveryConfigurationPanel.java | 2 +- .../main/flightconfigpanel/SeparationConfigurationPanel.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 e4d53e3ac..538eb9709 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java @@ -82,7 +82,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel selectMotor(); } }); - this.add(selectMotorButton, "split, sizegroup button"); + this.add(selectMotorButton, "split, align right, sizegroup button"); //// Remove motor button removeMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.removeMotor")); diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java index 135f74eb6..fc302c3b0 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java @@ -55,7 +55,7 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel selectDeployment(); } }); - this.add(selectSeparationButton, "split, sizegroup button"); + this.add(selectSeparationButton, "split, align right, sizegroup button"); //// Reset deployment resetDeploymentButton = new JButton(trans.get("edtmotorconfdlg.but.Resetseparation"));