/*******************************************************************************
* Copyright (c) 2010, 2012 Weltevree Beheer BV, Remain Software & Industrial-TSI
*
* 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
*
* Contributors:
* Wim S. Jongman - initial API and implementation
* Jantje- split oscilloscope in plotter and oscilloscope
******************************************************************************/
package org.eclipse.nebula.widgets.oscilloscope.multichannel;
import java.util.ArrayList;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
/**
* Animated widget that tries to mimic an Oscilloscope.
*
* <i>An oscilloscope (also known as a scope, CRO or, an O-scope) is a type of
* electronic test instrument that allows observation of constantly varying
* signal voltages, usually as a two-dimensional graph of one or more electrical
* potential differences using the vertical or 'Y' axis, plotted as a function
* of time, (horizontal or 'x' axis).</i>
* <p/>
* <a href="http://en.wikipedia.org/wiki/Oscilloscope">http://en.wikipedia.org/
* wiki/Oscilloscope<a/>
*
* @author Wim.Jongman (@remainsoftware.com)
*
*/
public class Oscilloscope extends Plotter {
private class Data {
private OscilloscopeDispatcher dispatcher;
private ArrayList<OscilloscopeStackAdapter> stackListeners;
private IntegerFiFoCircularStack stack;
private int progression = PROGRESSION_DEFAULT;
}
/**
* This method can be called outside of the UI thread.
*
* @return the number of internal calculation steps at each draw request.
* @see #setProgression(int)
*/
public int getProgression(int channel) {
return chan[channel].progression;
}
/**
* The number of internal steps that must be made before drawing. Normally
* this will slide the graph one pixel. Setting this to a higher value will
* speed up the animation at the cost of a more jerky motion.
* <p/>
* This method can be called outside of the UI thread.
*
* @param progression
*/
public void setProgression(int channel, int progression) {
if (progression > 0) {
chan[channel].progression = progression;
}
}
/**
* The stack can hold a limited number of values but will never overflow.
* Think of the stack as a tube with a given length. If you push too many
* values in, the oldest will be pushed out at the end.
*
*/
public class IntegerFiFoCircularStack {
private int bottom;
private final int capacity;
private final int[] stack;
private int storedValues;
private int top;
/**
* Creates a stack with the indicated capacity.
*
* @param capacity
* must be greater than 1
*/
public IntegerFiFoCircularStack(int capacity) {
if (capacity <= 1) {
throw new RuntimeException("Stack capacity must be > 1");
}
this.capacity = capacity;
stack = new int[capacity];
top = 0;
bottom = 0;
}
/**
* Creates stack with the indicated capacity and copies the old stack
* into the new stack and the old stack will be empty after this action.
*
* @param capacity
* must be greater than 1
* @param oldStack
*/
public IntegerFiFoCircularStack(int capacity, IntegerFiFoCircularStack oldStack) {
this(capacity);
while (!oldStack.isEmpty()) {
push(oldStack.pop(0));
}
}
/**
* Clears the stack.
*/
public void clear() {
synchronized (stack) {
for (int i = 0; i < stack.length; i++) {
stack[i] = 0;
}
top = 0;
bottom = 0;
}
}
public int getCapacity() {
return capacity;
}
/**
*
* @return boolean
*/
public int getLoad() {
return storedValues;
}
/**
*
* @return boolean
*/
public boolean isEmpty() {
return storedValues == 0;
}
/**
*
* @return boolean
*/
public boolean isFull() {
return storedValues == capacity;
}
/**
* Returns the oldest value from the stack without removing the value
* from the stack. Returns the supplied entry if the stack is empty.
*
* @param valueIfEmpty
* @return int
*/
public int peek(int valueIfEmpty) {
if (storedValues > 0) {
return stack[bottom];
}
return valueIfEmpty;
}
/**
* Returns the oldest value from the stack. Returns the supplied entry
* if the stack is empty.
*
* @param valueIfEmpty
* @return int
*/
public int pop(int valueIfEmpty) {
if (isEmpty()) {
return valueIfEmpty;
}
storedValues--;
int result = stack[bottom++];
if (bottom == capacity) {
bottom = 0;
}
return result;
}
/**
* Returns the oldest value from the stack and negates the value.
* Returns the supplied entry if the stack is empty.
*
* @param valueIfEmpty
* @return int
*/
public int popNegate(int valueIfEmpty) {
return pop(valueIfEmpty) * -1;
}
/**
* Puts a value on the stack.
*
* @param value
*/
public void push(int value) {
if (storedValues == capacity) {
top = bottom;
bottom++;
if (bottom == capacity) {
bottom = 0;
}
} else {
storedValues++;
}
if (top == capacity) {
top = 0;
}
stack[top++] = value;
}
}
/**
* This set of values will draw a figure that is similar to the heart beat
* that you see on hospital monitors.
*/
public static final int[] HEARTBEAT = new int[] { 2, 10, 2, -16, 16, 44, 49, 44, 32, 14, -16, -38, -49, -47, -32,
-10, 8, 6, 6, -2, 6, 4, 2, 0, 0, 6, 8, 6 };
private Data[] chan;
private int width;
/**
* Creates a scope with one channel.
*
* @param parent
* @param style
*/
public Oscilloscope(Composite parent, int style) {
this(1, null, parent, style);
}
/**
* Creates a scope with <code>channels</code> channels.
*
* @param channels
* @param parent
* @param style
*/
public Oscilloscope(int channels, Composite parent, int style) {
this(channels, null, parent, style);
}
/**
* Creates a new scope with <code>channels</code> channels and adds attaches
* it to the supplied <code>dispatcher</code>.
*
* @param channels
* @param dispatcher
* may be null
* @param parent
* @param style
*/
public Oscilloscope(int channels, OscilloscopeDispatcher dispatcher, Composite parent, int style) {
super(channels, parent, style);
chan = new Data[channels];
for (int i = 0; i < chan.length; i++) {
chan[i] = new Data();
if (dispatcher == null) {
chan[i].dispatcher = new OscilloscopeDispatcher(i, this);
} else {
chan[i].dispatcher = dispatcher;
dispatcher.setOscilloscope(this);
}
}
}
/**
* Adds a new stack listener to the collection of stack listeners. Adding
* the same listener twice will have no effect.
* <p/>
* This method can be called outside of the UI thread.
*
* @param listener
*/
public synchronized void addStackListener(int channel, OscilloscopeStackAdapter listener) {
if (chan[channel].stackListeners == null) {
chan[channel].stackListeners = new ArrayList<OscilloscopeStackAdapter>();
}
if (!chan[channel].stackListeners.contains(listener)) {
chan[channel].stackListeners.add(listener);
}
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
checkWidget();
int width;
int height;
if (wHint != SWT.DEFAULT) {
width = wHint;
} else {
width = DEFAULT_WIDTH;
}
if (hHint != SWT.DEFAULT) {
height = hHint;
} else {
height = DEFAULT_HEIGHT;
}
return new Point(width + 2, height + 2);
}
/**
* This method can be called outside of the UI thread.
*
* @param channel
* @return the dispatcher associated with this channel.
*/
public OscilloscopeDispatcher getDispatcher(int channel) {
return chan[channel].dispatcher;
}
public boolean needsRedraw() {
checkWidget();
return isDisposed() ? false : true;
}
private void notifyListeners(int channel) {
if (chan[channel].stackListeners == null || chan[channel].stackListeners.size() == 0) {
return;
}
for (int i = 0; i < chan[channel].stackListeners.size(); i++) {
chan[channel].stackListeners.get(i).stackEmpty(this, channel);
}
}
protected void paintControl(PaintEvent e) {
super.paintControl(e);
for (int c = 0; c < chan.length; c++) {
for (int progress = 0; progress < getProgression(c); progress++) {
if (chan[c].stack.isEmpty() && chan[c].stackListeners != null) {
notifyListeners(c);
}
setValue(c, chan[c].stack.popNegate(0));
}
// I think the next code can be removed
// if (getTailSize(c) <= 0) {
// chan[c].stack.popNegate(0);
// continue;
// }
}
}
/**
* Removes a stack listener from the collection of stack listeners. This
* method can be called outside of the UI thread.
*
* @param listener
*/
public void removeStackListener(int channel, OscilloscopeStackAdapter listener) {
if (chan[channel].stackListeners != null) {
chan[channel].stackListeners.remove(listener);
if (chan[channel].stackListeners.size() == 0) {
synchronized (chan[channel].stackListeners) {
chan[channel].stackListeners = null;
}
}
}
}
/**
* Sets the dispatcher that is associated with the supplied channel. This
* method can be called outside of the UI thread.
*
* @param channel
* @param dispatcher
*/
public void setDispatcher(int channel, OscilloscopeDispatcher dispatcher) {
chan[channel].dispatcher = dispatcher;
}
/**
* Sets a bunch of values that will be drawn. See
* {@link #setValue(int, int)} for details.
* <p/>
* This method can be called outside of the UI thread.
*
* @param channel
* @param values
*
* @see #setValue(int, int)
*/
public synchronized void setValues(int channel, int[] values) {
checkWidget();
if (getBounds().width <= 0)
return;
if (!super.isVisible())
return;
if (this.chan[channel].stack == null)
this.chan[channel].stack = new IntegerFiFoCircularStack(width);
for (int i = 0; i < values.length; i++) {
this.chan[channel].stack.push(values[i]);
}
}
protected void controlResized(ControlEvent e) {
super.controlResized(e);
width = getSize().x;
if (width > 1) {
for (int c = 0; c < this.chan.length; c++) {
if (this.chan[c].stack == null) {
this.chan[c].stack = new IntegerFiFoCircularStack(width);
} else {
this.chan[c].stack = new IntegerFiFoCircularStack(width, this.chan[c].stack);
}
}
}
}
}