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