Merge pull request #151 from kruland2607/feature-motorfilter

Improve MotorSelectionDialog box by having more sophisticated filtering.
This commit is contained in:
kruland2607 2013-10-08 19:06:10 -07:00
commit 7687568f81
19 changed files with 3137 additions and 729 deletions

View File

@ -1087,10 +1087,13 @@ StorageOptChooser.lbl.Saveopt = Save options
! ThrustCurveMotorSelectionPanel
TCMotorSelPan.lbl.Selrocketmotor = Select rocket motor:
TCMotorSelPan.checkbox.hideSimilar = Hide very similar thrust curves
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.checkbox.hideUsed = Hide motors already used in the mount
TCMotorSelPan.btn.details = Show Details
TCMotorSelPan.btn.filter = Filter Motors
TCMotorSelPan.MotorSize = Motor Dimensions
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:
@ -1108,6 +1111,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

View File

@ -11,7 +11,7 @@ import net.sf.openrocket.util.ChangeSource;
*
* @param <E> the parameter type
*/
public interface FlightConfiguration<E extends ChangeSource> extends FlightConfigurableComponent {
public interface FlightConfiguration<E extends ChangeSource> extends FlightConfigurableComponent, Iterable<E> {
/**
* Return the default parameter value for this FlightConfiguration.

View File

@ -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<E extends FlightConfigurableParameter<E>> implemen
fireEvent();
}
@Override
public Iterator<E> iterator() {
return map.values().iterator();
}
@Override

View File

@ -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);
}

View File

@ -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();

View File

@ -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,11 +209,7 @@ public class MotorConfigurationPanel extends JPanel {
MotorConfiguration config = mount.getMotorConfiguration().get(id);
MotorChooserDialog dialog = new MotorChooserDialog(
config.getMotor(),
config.getEjectionDelay(),
mount.getMotorMountDiameter(),
flightConfigurationDialog);
dialog.setMotorMountAndConfig(mount, id);
dialog.setVisible(true);
Motor m = dialog.getSelectedMotor();
double d = dialog.getSelectedDelay();

View File

@ -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 {
@ -26,16 +26,20 @@ 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(Motor current, double delay, double diameter, Window owner) {
public MotorChooserDialog(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();
panel.add(selectionPanel, "grow, wrap para");
panel.add(selectionPanel, "grow, wrap");
// OK / Cancel buttons
@ -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 <code>null</code> if the selection has been aborted.

View File

@ -0,0 +1,44 @@
package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
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 double getLow() {
return low;
}
public double getHigh() {
return high;
}
private double low;
private double high;
private String name;
}

View File

@ -0,0 +1,317 @@
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.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
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;
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.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;
import net.sf.openrocket.rocketcomponent.RocketComponent;
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 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<Manufacturer> manufacturerCheckList;
private final MotorRowFilter filter;
// Things we change the label on based on the MotorMount.
private final JLabel motorMountDimension;
private final MultiSlider lengthSlider;
private final MultiSlider diameterSlider;
public MotorFilterPanel(Collection<Manufacturer> allManufacturers, MotorRowFilter filter ) {
super(new MigLayout("fill", "[grow]"));
this.filter = filter;
List<Manufacturer> 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"));
GUIUtil.changeFontStyle(border, Font.BOLD);
sub.setBorder(border);
this.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
List<Manufacturer> manufacturers = new ArrayList<Manufacturer>();
for (Manufacturer m : allManufacturers) {
manufacturers.add(m);
}
Collections.sort(manufacturers, new Comparator<Manufacturer>() {
@Override
public int compare(Manufacturer o1, Manufacturer o2) {
return o1.getSimpleName().compareTo( o2.getSimpleName());
}
});
manufacturerCheckList = new CheckList.Builder().<Manufacturer>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);
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);
motorMountDimension = new JLabel();
GUIUtil.changeFontSize(motorMountDimension, -1);
sub.add(motorMountDimension,"growx,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);
diameterSlider.setPaintTicks(true);
diameterSlider.setLabelTable(diameterLabels);
diameterSlider.setPaintLabels(true);
diameterSlider.addChangeListener( new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
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();
}
});
sub.add( diameterSlider, "growx, wrap");
}
{
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) {
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( 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");
}
public void setMotorMount( MotorMount mount ) {
filter.setMotorMount(mount);
onSelectionChanged();
if ( mount == null ) {
// Disable diameter controls?
lengthSlider.setValueAt(1, 1000);
motorMountDimension.setText("");
} else {
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] ) {
break;
}
}
if (i >= diameterValues.length ) {
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));
}
}
public abstract void onSelectionChanged();
}

View File

@ -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<ThrustCurveMotor> 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<ThrustCurveMotor> 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);
}
}
}
}

View File

@ -0,0 +1,242 @@
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;
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;
import net.sf.openrocket.util.AbstractChangeSource;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.StateChangeListener;
//////// Row filters
/**
* Abstract adapter class.
*/
public class MotorRowFilter extends RowFilter<TableModel, Integer> implements ChangeSource {
// configuration data used in the filter process
private final ThrustCurveMotorDatabaseModel model;
private List<ThrustCurveMotor> usedMotors = new ArrayList<ThrustCurveMotor>();
private final AbstractChangeSource changeSourceDelegate = new AbstractChangeSource();
private final Object change = new Object();
// things which can be changed to modify filter behavior
// Limit motors based on length
private double minimumLength = 0;
private double maximumLength = Double.MAX_VALUE;
// Limit motors based on diameter
private Double minimumDiameter;
private Double maximumDiameter;
// Collection of strings which match text in the motor
private List<String> searchTerms = Collections.<String> emptyList();
// Boolean which hides motors in the usedMotors list
private boolean hideUsedMotors = false;
// List of manufacturers to exclude.
private List<Manufacturer> excludedManufacturers = new ArrayList<Manufacturer>();
// Impulse class filtering
private ImpulseClass minimumImpulse;
private ImpulseClass maximumImpulse;
public MotorRowFilter(ThrustCurveMotorDatabaseModel model) {
super();
this.model = model;
}
public void setMotorMount( MotorMount mount ) {
if (mount != null) {
for (MotorConfiguration m : mount.getMotorConfiguration()) {
this.usedMotors.add((ThrustCurveMotor) m.getMotor());
}
}
}
public void setSearchTerms(final List<String> searchTerms) {
this.searchTerms = new ArrayList<String>();
for (String s : searchTerms) {
s = s.trim().toLowerCase(Locale.getDefault());
if (s.length() > 0) {
this.searchTerms.add(s);
}
}
}
public double getMinimumLength() {
return minimumLength;
}
public void setMinimumLength(double minimumLength) {
if ( this.minimumLength != minimumLength ) {
this.minimumLength = minimumLength;
fireChangeEvent(change);
}
}
public double getMaximumLength() {
return maximumLength;
}
public void setMaximumLength(double maximumLength) {
if ( this.maximumLength != maximumLength ) {
this.maximumLength = maximumLength;
fireChangeEvent(change);
}
}
Double getMinimumDiameter() {
return minimumDiameter;
}
void setMinimumDiameter(Double minimumDiameter) {
this.minimumDiameter = minimumDiameter;
}
Double getMaximumDiameter() {
return maximumDiameter;
}
void setMaximumDiameter(Double maximumDiameter) {
this.maximumDiameter = maximumDiameter;
}
void setHideUsedMotors(boolean hideUsedMotors) {
this.hideUsedMotors = hideUsedMotors;
}
List<Manufacturer> getExcludedManufacturers() {
return excludedManufacturers;
}
void setExcludedManufacturers(Collection<Manufacturer> excludedManufacturers) {
this.excludedManufacturers.clear();
this.excludedManufacturers.addAll(excludedManufacturers);
}
ImpulseClass getMinimumImpulse() {
return minimumImpulse;
}
void setMinimumImpulse(ImpulseClass minimumImpulse) {
this.minimumImpulse = minimumImpulse;
}
ImpulseClass getMaximumImpulse() {
return maximumImpulse;
}
void setMaximumImpulse(ImpulseClass maximumImpulse) {
this.maximumImpulse = maximumImpulse;
}
@Override
public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
int index = entry.getIdentifier();
ThrustCurveMotorSet m = model.getMotorSet(index);
return filterManufacturers(m) && filterUsed(m) && filterBySize(m) && filterByString(m) && filterByImpulseClass(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 filterBySize(ThrustCurveMotorSet m) {
if ( minimumDiameter != null ) {
if ( m.getDiameter() <= minimumDiameter - 0.0015 ) {
return false;
}
}
if ( maximumDiameter != null ) {
if ( m.getDiameter() >= maximumDiameter + 0.0004 ) {
return false;
}
}
if ( m.getLength() > maximumLength ) {
return false;
}
if ( m.getLength() < minimumLength ) {
return false;
}
return true;
}
private 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;
}
private boolean filterByImpulseClass(ThrustCurveMotorSet m) {
if ( minimumImpulse != null ) {
if( m.getTotalImpuse() < minimumImpulse.getLow() ) {
return false;
}
}
if ( maximumImpulse != null ) {
if( m.getTotalImpuse() > maximumImpulse.getHigh() ) {
return false;
}
}
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);
}
}

View File

@ -0,0 +1,189 @@
/*
* 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 <T> list item type
*/
public class CheckList<T> {
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 <T> CheckList<T> build() {
return new CheckList<T>(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<T> data) {
setModel(new DefaultCheckListModel<T>(data));
}
/**
* Sets the model for check list.
* @param model
*/
public void setModel(DefaultCheckListModel<T> model) {
list.setModel(model);
}
@SuppressWarnings("unchecked")
public DefaultCheckListModel<T> getModel() {
return (DefaultCheckListModel<T>) list.getModel();
}
/**
* Returns a collection of checked items.
* @return collection of checked items. Empty collection if nothing is selected
*/
public Collection<T> getCheckedItems() {
return getModel().getCheckedItems();
}
public Collection<T> getUncheckedItems() {
List<T> unchecked = new ArrayList<T>();
for (int i = getModel().getSize() - 1; i >= 0; i--) {
unchecked.add((T) getModel().getElementAt(i));
}
unchecked.removeAll(getCheckedItems());
return unchecked;
}
public void checkAll() {
getModel().checkAll();
}
public void clearAll() {
getModel().clearAll();
}
/**
* Resets checked elements
* @param elements
*/
public void setCheckedItems(Collection<T> elements) {
getModel().setCheckedItems(elements);
}
public void setUncheckedItems( Collection<T> elements ) {
getModel().setUncheckedItems(elements);
}
public void toggleIndex(int index) {
if (index >= 0 && index < list.getModel().getSize()) {
DefaultCheckListModel<T> model = getModel();
model.setCheckedIndex(index, !model.isCheckedIndex(index));
}
}
}

View File

@ -0,0 +1,87 @@
/*
* 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<Object> model = (DefaultCheckListModel<Object>) list.getModel();
if (e.getClickCount() > 1) {
// clear all and check selected for more then 1 clicks
// 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));
}
e.consume();
}
}
}

View File

@ -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 {
}
}

View File

@ -0,0 +1,144 @@
/*
* 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 <T> list element type
*/
public class DefaultCheckListModel<T> extends AbstractListModel {
private static final long serialVersionUID = 1L;
private final List<T> data = new ArrayList<T>();
private final Set<T> checks = new HashSet<T>();
public DefaultCheckListModel(Collection<? extends T> 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<T> 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<T> getCheckedItems() {
List<T> items = new ArrayList<T>(checks);
items.retainAll(data);
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<T> items) {
// if ( CollectionUtils.isEmpty(items)) return;
List<T> correctedItems = new ArrayList<T>(items);
correctedItems.retainAll(data);
checks.clear();
checks.addAll(correctedItems);
fireContentsChanged(this, 0, checks.size() - 1);
}
public void setUncheckedItems( Collection<T> items ) {
List<T> correctedItems = new ArrayList<T>(data);
correctedItems.removeAll(items);
checks.clear();
checks.addAll(correctedItems);
fireContentsChanged(this, 0, checks.size() - 1);
}
}

View File

@ -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 = "<SYSTEM>";
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<Manufacturer> getExcludedMotorManufacturers() {
Preferences prefs = PREFNODE.node("excludedMotorManufacturers");
List<Manufacturer> collection = new ArrayList<Manufacturer>();
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<Manufacturer> 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);
}
}
}

View File

@ -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.
* <p>
*
* @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.
* <PRE>
* </PRE>
*
* @param b
* @return void
*/
public void setBounded(boolean b) {
this.bounded = b;
}
/***
* Returns a bounded attribute of a slider thumb.
* <PRE>
* </PRE>
*
* @return boolean
*/
public boolean isBounded() {
return this.bounded;
}
public int getValueBeforeStateChange() {
return this.valueBeforeStateChange;
}
void setValueBeforeStateChange(int v) {
this.valueBeforeStateChange = v;
}
}

View File

@ -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());
}
}
}
}
}
}