From e5679259faa5c79983086aaf2aec32b598c475b2 Mon Sep 17 00:00:00 2001 From: kruland2607 Date: Mon, 7 Oct 2013 18:47:10 -0500 Subject: [PATCH] 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()); + } + } + } + } + } +}