/*******************************************************************************
* Copyright (c) 2010 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
******************************************************************************/
package org.eclipse.nebula.visualization.widgets.figures;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.draw2d.AbstractLayout;
import org.eclipse.draw2d.Cursors;
import org.eclipse.draw2d.Ellipse;
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.nebula.visualization.widgets.datadefinition.IManualValueChangeListener;
import org.eclipse.nebula.visualization.widgets.figureparts.Bulb;
import org.eclipse.nebula.visualization.widgets.figureparts.PolarPoint;
import org.eclipse.nebula.visualization.widgets.figureparts.RoundScale;
import org.eclipse.nebula.visualization.widgets.figureparts.RoundScaledRamp;
import org.eclipse.nebula.visualization.widgets.util.GraphicsUtil;
import org.eclipse.nebula.visualization.widgets.util.PointsUtil;
import org.eclipse.nebula.visualization.xygraph.util.XYGraphMediaFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Pattern;
import org.eclipse.swt.widgets.Display;
/**
* The figure of knob
* @author Xihui Chen
*
*/
public class KnobFigure extends AbstractRoundRampedFigure {
class KnobLayout extends AbstractLayout {
private static final int GAP_BTW_BULB_SCALE = 4;
/** Used as a constraint for the scale. */
public static final String SCALE = "scale"; //$NON-NLS-1$
/** Used as a constraint for the bulb. */
public static final String BULB = "bulb"; //$NON-NLS-1$
/** Used as a constraint for the Ramp */
public static final String RAMP = "ramp"; //$NON-NLS-1$
/** Used as a constraint for the thumb */
public static final String THUMB = "thumb"; //$NON-NLS-1$
/** Used as a constraint for the value label*/
public static final String VALUE_LABEL = "valueLabel"; //$NON-NLS-1$
private RoundScale scale;
private RoundScaledRamp ramp;
private Bulb bulb;
private Thumb thumb;
private Label valueLabel;
@Override
protected Dimension calculatePreferredSize(IFigure container, int w,
int h) {
Insets insets = container.getInsets();
Dimension d = new Dimension(256, 256);
d.expand(insets.getWidth(), insets.getHeight());
return d;
}
public void layout(IFigure container) {
Rectangle area = container.getClientArea();
area.width = Math.min(area.width, area.height);
area.height = area.width;
area.shrink(BORDER_WIDTH, BORDER_WIDTH);
Point center = area.getCenter();
Rectangle bulbBounds = null;
if(scale != null) {
scale.setBounds(area);
bulbBounds = area.getCopy();
bulbBounds.shrink(area.width/2 - scale.getInnerRadius() + GAP_BTW_BULB_SCALE,
area.height/2 - scale.getInnerRadius() + GAP_BTW_BULB_SCALE);
}
if(scale != null && ramp != null && ramp.isVisible()) {
Rectangle rampBounds = area.getCopy();
ramp.setBounds(rampBounds.shrink(area.width/2 - scale.getInnerRadius() - ramp.getRampWidth()+2,
area.height/2 - scale.getInnerRadius() - ramp.getRampWidth()+2));
}
if(valueLabel != null && valueLabel.isVisible()) {
Dimension labelSize = valueLabel.getPreferredSize();
valueLabel.setBounds(new Rectangle(bulbBounds.x + bulbBounds.width/2 - labelSize.width/2,
bulbBounds.y + bulbBounds.height * 3/4 - labelSize.height/2,
labelSize.width, labelSize.height));
}
if(bulb != null && scale != null && bulb.isVisible()) {
bulb.setBounds(bulbBounds);
}
if(scale != null && thumb != null && thumb.isVisible()){
Point thumbCenter = new Point(bulbBounds.x + bulbBounds.width*7.0/8.0,
bulbBounds.y + bulbBounds.height/2);
double valuePosition = 360 - scale.getValuePosition(getCoercedValue(), false);
thumbCenter = PointsUtil.rotate(thumbCenter, valuePosition, center);
int thumbDiameter = bulbBounds.width/6;
thumb.setBounds(new Rectangle(thumbCenter.x - thumbDiameter/2,
thumbCenter.y - thumbDiameter/2,
thumbDiameter, thumbDiameter));
}
}
@Override
public void setConstraint(IFigure child, Object constraint) {
if(constraint.equals(SCALE))
scale = (RoundScale)child;
else if (constraint.equals(RAMP))
ramp = (RoundScaledRamp) child;
else if (constraint.equals(BULB))
bulb = (Bulb) child;
else if (constraint.equals(THUMB))
thumb = (Thumb) child;
else if (constraint.equals(VALUE_LABEL))
valueLabel = (Label)child;
}
}
class Thumb extends Ellipse {
class ThumbDragger
extends MouseMotionListener.Stub
implements MouseListener {
private PolarPoint startPP;
protected double oldValuePosition;
protected boolean armed;
Point pole;
public void mouseDoubleClicked(MouseEvent me) {
}
public void mouseDragged(MouseEvent me) {
if (!armed)
return;
PolarPoint currentPP =
PolarPoint.point2PolarPoint(pole, me.getLocation());
//rotate axis to endAngle
currentPP.rotateAxis(((RoundScale)scale).getEndAngle(), false);
//coerce currentPP to min or max
if(currentPP.theta * 180.0/Math.PI > (((RoundScale)scale).getLengthInDegrees())) {
if(Math.abs(((RoundScale)scale).getValuePosition(getCoercedValue(), true)-
(((RoundScale)scale).getLengthInDegrees())) < ((RoundScale)scale).getLengthInDegrees()/2.0)
currentPP.theta = ((RoundScale)scale).getLengthInDegrees() * Math.PI/180.0;
else
currentPP.theta = 0;
}
double difference = currentPP.theta * 180.0/Math.PI - oldValuePosition;
double valueChange = calcValueChange(difference, value);
if(increment <= 0 || Math.abs(valueChange) > increment/2.0) {
// manualSetValue = true;
if(increment > 0)
manualSetValue(value + increment * Math.round(valueChange/increment));
else
manualSetValue(value + valueChange);
oldValuePosition = ((RoundScale)scale).getValuePosition(
value, true);
fireManualValueChange(value);
KnobFigure.this.revalidate();
KnobFigure.this.repaint();
}
me.consume();
}
public void mousePressed(MouseEvent me) {
if(me.button != 1)
return;
armed = true;
pole = scale.getBounds().getCenter();
startPP = PolarPoint.point2PolarPoint(pole, bounds.getCenter());
//rotate axis to endAngle
startPP.rotateAxis(((RoundScale)scale).getEndAngle(), false);
oldValuePosition = ((RoundScale)scale).getValuePosition(
getCoercedValue(), true);
me.consume();
}
public void mouseReleased(MouseEvent me) {
if(me.button != 1)
return;
if (!armed)
return;
armed = false;
me.consume();
}
}
public Thumb() {
setCursor(Cursors.HAND);
ThumbDragger thumbDragger = new ThumbDragger();
addMouseMotionListener(thumbDragger);
addMouseListener(thumbDragger);
}
@Override
public void setEnabled(boolean value) {
super.setEnabled(value);
}
@Override
protected void fillShape(Graphics graphics) {
graphics.setAntialias(SWT.ON);
Pattern pattern = null;
graphics.setBackgroundColor(thumbColor);
boolean support3D = GraphicsUtil.testPatternSupported(graphics);
if(support3D && effect3D){
try {
graphics.setBackgroundColor(thumbColor);
super.fillShape(graphics);
pattern = GraphicsUtil.createScaledPattern(graphics, Display.getCurrent(), bounds.x, bounds.y,
bounds.x + bounds.width, bounds.y + bounds.height,
WHITE_COLOR, 0, WHITE_COLOR, 255);
graphics.setBackgroundPattern(pattern);
} catch (Exception e) {
support3D = false;
pattern.dispose();
}
}
super.fillShape(graphics);
if(effect3D && support3D)
pattern.dispose();
graphics.setForegroundColor(thumbColor);
}
}
private final static Color WHITE_COLOR = XYGraphMediaFactory.getInstance().getColor(
XYGraphMediaFactory.COLOR_WHITE);
private final static Color GRAY_COLOR = XYGraphMediaFactory.getInstance().getColor(
XYGraphMediaFactory.COLOR_GRAY);
private final static Font DEFAULT_LABEL_FONT = XYGraphMediaFactory.getInstance().getFont(
new FontData("Arial", 12, SWT.BOLD));
private final static int BORDER_WIDTH = 2;
/** The alpha (0 is transparency and 255 is opaque) for disabled paint */
private static final int DISABLED_ALPHA = 100;
private boolean effect3D = true;
private double increment = 1;
// private boolean manualSetValue = false;
private Thumb thumb;
private Bulb bulb;
private Color thumbColor = GRAY_COLOR;
private Label valueLabel;
/**
* Listeners that react on slider events.
*/
private List<IManualValueChangeListener> knobListeners =
new ArrayList<IManualValueChangeListener>();
public KnobFigure() {
super();
transparent = true;
scale.setScaleLineVisible(false);
ramp.setRampWidth(12);
valueLabel = new Label();
valueLabel.setFont(DEFAULT_LABEL_FONT);
bulb = new Bulb();
thumb = new Thumb();
thumb.setOutline(false);
setLayoutManager(new KnobLayout());
add(ramp, KnobLayout.RAMP);
add(bulb, KnobLayout.BULB);
add(scale, KnobLayout.SCALE);
add(valueLabel, KnobLayout.VALUE_LABEL);
add(thumb, KnobLayout.THUMB);
addFigureListener(new FigureListener() {
public void figureMoved(IFigure source) {
ramp.setDirty(true);
revalidate();
}
});
}
/**
* Add a knob listener.
*
* @param listener
* The knob listener to add.
*/
public void addManualValueChangeListener(final IManualValueChangeListener listener) {
knobListeners.add(listener);
}
/**Convert the difference of two points to the corresponding value to be changed.
* @param difference the different theta value in degrees between two polar points.
* @param oldValue the old value before this change
* @return the value to be changed
*/
private double calcValueChange(double difference, double oldValue) {
double change;
double dragRange = ((RoundScale)scale).getLengthInDegrees();
if(scale.isLogScaleEnabled()) {
double c = dragRange/(
Math.log10(scale.getRange().getUpper()) -
Math.log10(scale.getRange().getLower()));
change = oldValue * (Math.pow(10, -difference/c) - 1);
} else {
change = -(scale.getRange().getUpper() - scale.getRange().getLower())
* difference / dragRange;
}
return change;
}
/**
* Inform all knob listeners, that the manual value has changed.
*
* @param newManualValue
* the new manual value
*/
private void fireManualValueChange(final double newManualValue) {
for (IManualValueChangeListener l : knobListeners) {
l.manualValueChanged(newManualValue);
}
}
/**
* @return the increment
*/
public double getIncrement() {
return increment;
}
/**
* @return the thumbColor
*/
public Color getThumbColor() {
return thumbColor;
}
/**
* @return the effect3D
*/
public boolean isEffect3D() {
return effect3D;
}
/**Set Value from manual control of the widget. Value will be coerced in range.
* @param value
*/
public void manualSetValue(double value){
setValue(getCoercedValue(value));
}
@Override
protected void paintClientArea(Graphics graphics) {
super.paintClientArea(graphics);
if(!isEnabled()) {
graphics.setAlpha(DISABLED_ALPHA);
graphics.setBackgroundColor(GRAY_COLOR);
graphics.fillRectangle(bounds);
}
}
public void removeManualValueChangeListener(final IManualValueChangeListener listener){
if(knobListeners.contains(listener))
knobListeners.remove(listener);
}
@Override
public void setBounds(Rectangle rect) {
super.setBounds(rect);
}
/**
* @param color the bulb color to set
*/
public void setBulbColor(Color color) {
bulb.setBulbColor(color);
}
@Override
public void setCursor(Cursor cursor) {
super.setCursor(cursor);
thumb.setCursor(cursor);
}
/**
* @param effect3D the effect3D to set
*/
public void setEffect3D(boolean effect3D) {
this.effect3D = effect3D;
bulb.setEffect3D(effect3D);
repaint();
}
@Override
public void setEnabled(boolean value) {
super.setEnabled(value);
if(value)
thumb.setCursor(Cursors.HAND);
//the disabled cursor should be controlled by widget controller.
repaint();
}
@Override
public void setFont(Font f) {
scale.setFont(f);
super.setFont(f);
}
/**
* @param increment the increment to set
*/
public void setIncrement(double increment) {
this.increment = increment;
}
/**
* @param thumbColor the thumbColor to set
*/
public void setThumbColor(Color thumbColor) {
if(this.thumbColor != null && this.thumbColor.equals(thumbColor))
return;
this.thumbColor = thumbColor;
repaint();
}
@Override
public void setValue(double value) {
super.setValue(value);
valueLabel.setText(getValueText());
// manualSetValue = false;
}
public void setValueLabelVisibility(boolean visible) {
valueLabel.setVisible(visible);
}
}