package com.positive.charts.widgets;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.TypedListener;
import com.positive.colorchecker.StaticColorChecker;
public class TinyScrollbar extends Canvas implements PaintListener {
private static final int SCROLLBAR_THICKNESS = 4;
private static final int STATE_MASK = SWT.HORIZONTAL | SWT.VERTICAL;
/**
* The direction of the scrollbar, either {@link SWT#HORIZONTAL} or
* {@link SWT#VERTICAL}.
*/
private int direction;
/**
* Lower bound of the scrollbar range, in domain units.
*/
protected long minimum = 0;
/**
* Upper bound of the scrollbar range, in domain units.
*/
protected long maximum = 100;
/**
* Size of the scrollbar thumb, in domain units.
*/
protected long thumb = 10;
/**
* Currently selected value within scrollbar range, in domain units.
*/
protected long selection = 0;
private boolean dragStarted = false;
private Point dragStartPosition;
public TinyScrollbar(final Composite parent, final int style) {
super(parent, style & ~STATE_MASK | SWT.DOUBLE_BUFFERED);
this.direction = (style & SWT.VERTICAL) != 0 ? SWT.VERTICAL
: SWT.HORIZONTAL;
this.addPaintListener(this);
this.addMouseListener(new MouseAdapter() {
public void mouseDown(final MouseEvent e) {
TinyScrollbar.this.handleMouseDown(e);
}
public void mouseUp(final MouseEvent e) {
TinyScrollbar.this.handleMouseUp(e);
}
});
this.addMouseMoveListener(new MouseMoveListener() {
public void mouseMove(final MouseEvent e) {
TinyScrollbar.this.handleMouseMove(e);
}
});
}
/**
* Adds the listener to the collection of listeners who will be notified
* when the receiver's value changes, by sending it one of the messages
* defined in the <code>SelectionListener</code> interface.
* <p>
* When <code>widgetSelected</code> is called, the event object detail field
* contains one of the following values: <code>SWT.NONE</code> - for the end
* of a drag. <code>SWT.DRAG</code>. <code>SWT.HOME</code>.
* <code>SWT.END</code>. <code>SWT.ARROW_DOWN</code>.
* <code>SWT.ARROW_UP</code>. <code>SWT.PAGE_DOWN</code>.
* <code>SWT.PAGE_UP</code>. <code>widgetDefaultSelected</code> is not
* called.
* </p>
*
* @param listener
* the listener which should be notified
*
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*
* @see SelectionListener
* @see #removeSelectionListener
* @see SelectionEvent
*/
public void addSelectionListener(final SelectionListener listener) {
this.checkWidget();
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
final TypedListener typedListener = new TypedListener(listener);
this.addListener(SWT.Selection, typedListener);
this.addListener(SWT.DefaultSelection, typedListener);
}
public Rectangle computeTrim(final int x, final int y, final int width,
final int height) {
if (this.direction == SWT.HORIZONTAL) {
return super.computeTrim(x, y, width, SCROLLBAR_THICKNESS);
}
return super.computeTrim(x, y, SCROLLBAR_THICKNESS, height);
}
public int getDirection() {
return this.direction;
}
public long getMaximum() {
return this.maximum;
}
public long getMinimum() {
return this.minimum;
}
private double getRange() {
return (double) this.maximum - (double) this.minimum;
}
public long getSelection() {
return this.selection;
}
public long getThumb() {
return this.thumb;
}
/**
* @return
*/
private Rectangle getThumbRectangle() {
final double range = this.getRange();
final double thumbRatio = this.thumb / range;
final double thumbPosition = (this.selection - this.minimum) / range;
final Rectangle thumbRect = new Rectangle(0, 0, 0, 0);
final Rectangle clientArea = this.getClientArea();
final int width = clientArea.width;
final int height = clientArea.height;
if (this.direction == SWT.HORIZONTAL) {
thumbRect.y = clientArea.y;
thumbRect.height = clientArea.height;
thumbRect.width = (int) (width * thumbRatio);
thumbRect.x = clientArea.x
+ (int) (thumbPosition * (width - thumbRect.width));
} else {
thumbRect.x = clientArea.x;
thumbRect.width = clientArea.width;
thumbRect.height = (int) (height * thumbRatio);
thumbRect.y = clientArea.y
+ (int) (thumbPosition * (height - thumbRect.height));
}
return thumbRect;
}
private void handleMouseDown(final MouseEvent e) {
final Rectangle thumbRectangle = this.getThumbRectangle();
if (thumbRectangle.contains(e.x, e.y)) {
// If clicked within thumb, then it must be a drag...
this.dragStarted = e.button == 1;
} else {
// ...else it is an increment.
if (this.direction == SWT.HORIZONTAL) {
if (e.x > thumbRectangle.x + thumbRectangle.width) {
this.setSelection(this.selection + this.thumb);
} else {
this.setSelection(this.selection - this.thumb);
}
} else {
if (e.y > thumbRectangle.y + thumbRectangle.height) {
this.setSelection(this.selection + this.thumb);
} else {
this.setSelection(this.selection - this.thumb);
}
}
}
if (this.dragStarted) {
this.dragStartPosition = new Point(e.x, e.y);
}
this.handleMouseMove(e);
}
protected void handleMouseMove(final MouseEvent e) {
if (!this.dragStarted) {
return;
}
int positionDelta, length;
if (this.direction == SWT.HORIZONTAL) {
positionDelta = e.x - this.dragStartPosition.x;
this.dragStartPosition.x = e.x;
length = this.getBounds().width;
} else {
positionDelta = e.y - this.dragStartPosition.y;
this.dragStartPosition.y = e.y;
length = this.getBounds().height;
}
if (positionDelta == 0) {
return;
}
final long selectionDelta = (positionDelta
* (this.maximum - this.minimum) / length);
this.setSelection(this.getSelection() + selectionDelta);
}
private void handleMouseUp(final MouseEvent e) {
this.dragStarted &= e.button != 1;
if (!this.dragStarted) {
this.dragStartPosition = null;
}
this.handleMouseMove(e);
}
public void paintControl(final PaintEvent e) {
final GC gc = e.gc;
final Rectangle clientArea = this.getClientArea();
if ((clientArea.width == 0) || (clientArea.height == 0)) {
return;
}
this.paintScrollbar(gc);
}
private void paintScrollbar(final GC gc) {
gc.setBackground(StaticColorChecker
.dublicateColor(SWT.COLOR_WIDGET_DARK_SHADOW));
// this.getDisplay().getSystemColor(
// SWT.COLOR_WIDGET_DARK_SHADOW));
gc.fillRectangle(this.getClientArea());
gc.setBackground(StaticColorChecker
.dublicateColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
// this.getDisplay().getSystemColor(
// SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
gc.fillRectangle(this.getThumbRectangle());
}
/**
* Removes the listener from the collection of listeners who will be
* notified when the receiver's value changes.
*
* @param listener
* the listener which should no longer be notified
*
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*
* @see SelectionListener
* @see #addSelectionListener
*/
public void removeSelectionListener(final SelectionListener listener) {
this.checkWidget();
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
this.removeListener(SWT.Selection, listener);
this.removeListener(SWT.DefaultSelection, listener);
}
private void repaint() {
if (this.isDisposed()) {
return;
}
if (Thread.currentThread() == this.getDisplay().getThread()) {
this.redraw();
return;
}
this.getDisplay().asyncExec(new Runnable() {
public void run() {
TinyScrollbar.this.redraw();
}
});
}
public void setDirection(final int direction) {
// assert (direction & STATE_MASK) != 0 :
// "Direction must be one of SWT.HORIZONTAL or SWT.VERTICAL";
this.direction = direction;
this.repaint();
}
public void setMaximum(final long max) {
// assert max > minimum : "Maximum mast be greater than minimum";
this.maximum = max;
this.repaint();
}
public void setMinimum(final long minimum) {
// assert minimum < maximum : "Minimum must be less than maximum";
this.minimum = minimum;
this.repaint();
}
public void setSelection(final long selection) {
this.checkWidget();
// assert (selection >= minimum) && (selection <= maximum) : "Selection
// must lie within minimum and maximum";
this.selection = Math.max(selection, this.minimum);
this.selection = Math.min(this.selection, this.maximum);
this.notifyListeners(SWT.Selection, null);
this.repaint();
}
public void setThumb(final long thumb) {
// assert thumb > 0 : "Thumb size must be positive";
this.thumb = thumb;
this.repaint();
}
}