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