Commit 93d33909 authored by arbennett's avatar arbennett
Browse files

Merge remote-tracking branch 'origin/master' into andrew/mars

parents 3edac629 760f3263
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
output.. = bin/
bin.includes = META-INF/,\
.,\
plugin.xml,\
icons/,\
OSGI-INF/,\
OSGI-INF/vizService.xml,\
plugin.xml
OSGI-INF/vizService.xml
source.. = src/
......@@ -8,24 +8,31 @@
* Contributors:
* Jordan Deyton (UT-Battelle, LLC.) - Initial API and implementation and/or
* initial documentation
* Jordan Deyton - bug 471166
*******************************************************************************/
package org.eclipse.ice.viz.service.visit.widgets;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Scale;
import org.eclipse.swt.widgets.Text;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
/**
* This class provides a widget with three separate means of selecting times
......@@ -56,17 +63,36 @@ public class TimeSliderComposite extends Composite {
/**
* The text widget used for exact timestep control.
*/
private final Text spinnerText;
private final Text text;
/**
* Sets the timestep to the next available timestep. This is a fine-grained
* control.
*/
private final Button spinnerNext;
private final Button nextButton;
/**
* Sets the timestep to the previous available timestep. This is a
* fine-grained control.
*/
private final Button spinnerPrev;
private final Button prevButton;
/**
* Starts or pauses playback of the timesteps.
*/
private final Button playButton;
/**
* The playback interval or framerate is 1 second.
*/
private static final int playbackInterval = 1;
/**
* Whether or not the playback operation is currently running.
*/
private boolean isPlaying = false;
/**
* The runnable operation used for "playback". It increments the timestep
* and schedules itself to execute later based on the value of
* {@link #playbackInterval}.
*/
private Runnable playbackRunnable;
/**
* The current timestep, or -1 if there are no times available.
......@@ -77,6 +103,23 @@ public class TimeSliderComposite extends Composite {
*/
private BinarySearchTree times;
/**
* The image used for the "next" button.
*/
private static Image nextImage;
/**
* The image used for the "pause" button.
*/
private static Image pauseImage;
/**
* The image used for the "play" button.
*/
private static Image playImage;
/**
* The image used for the "previous" button.
*/
private static Image prevImage;
/**
* The string to use in the text box when there are no times configured.
*/
......@@ -100,79 +143,192 @@ public class TimeSliderComposite extends Composite {
public TimeSliderComposite(Composite parent, int style) {
super(parent, style);
// Set the initial timestep and tree of times.
timestep = -1;
times = null;
// Iniitalize the list of selection listeners.
listeners = new ArrayList<SelectionListener>();
// Create the scale widget.
scale = new Scale(this, SWT.HORIZONTAL);
scale.setMinimum(0);
scale.setIncrement(1);
scale.setToolTipText("Traverses the timesteps");
// Create the text widget.
spinnerText = new Text(this, SWT.SINGLE | SWT.BORDER);
spinnerText.setFont(parent.getFont());
spinnerText.setToolTipText("The current time");
// Create a Composite to contain the spinner buttons.
Composite buttonComposite = new Composite(this, SWT.NONE);
// Create the two spinner buttons.
spinnerNext = new Button(buttonComposite, SWT.ARROW | SWT.UP);
spinnerNext.setToolTipText("Next Timestep");
spinnerPrev = new Button(buttonComposite, SWT.ARROW | SWT.DOWN);
spinnerPrev.setToolTipText("Previous Timestep");
// Layout the widgetes. The scale should take up all horizontal space on
// the left. The text widget should grab whatever space remains, while
// the two buttons, one above the other, take up just the space they
// require.
setLayout(new GridLayout(4, false));
// Create the widgets.
prevButton = createPrevButton(this);
playButton = createPlayButton(this);
nextButton = createNextButton(this);
text = createText(this);
scale = createScale(this);
// Layout the widgets. The scale should take up all horizontal space on
// the right. The text widget should grab whatever space remains, while
// the normal buttons take up only the space they require.
setLayout(new GridLayout(5, false));
GridData gridData = new GridData(SWT.CENTER, SWT.CENTER, false, true);
prevButton.setLayoutData(gridData);
playButton.setLayoutData(GridDataFactory.copyData(gridData));
nextButton.setLayoutData(GridDataFactory.copyData(gridData));
text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true));
scale.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
spinnerText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false,
true));
buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false,
true));
buttonComposite.setLayout(new GridLayout(1, false));
spinnerNext
.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
spinnerPrev
.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
// Add selection listeners to each of the widgets. These listeners
// should trigger an update to the timestep and notify listeners if
// necessary.
scale.addSelectionListener(new SelectionAdapter() {
// The default focus should be on the play button.
playButton.setFocus();
// Refresh the widgets. This will enable/disable them as necessary.
setTimes(new ArrayList<Double>());
return;
}
/**
* Creates the "next" button that increments the timestep.
*
* @param parent
* The parent Composite for this widget. Assumed not to be
* {@code null}.
* @return The new widget.
*/
private Button createNextButton(Composite parent) {
Button nextButton = new Button(parent, SWT.PUSH);
// When the button is clicked, playback should be stopped and the
// timestep should be incremented.
nextButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// Get the timestep from the scale widget.
if (setTimestep(scale.getSelection())) {
// Disable playback.
setPlayback(false, e);
// Increment the timestep.
if (incrementTimestep()) {
notifyListeners(e);
}
}
});
spinnerNext.addSelectionListener(new SelectionAdapter() {
// Load the button image as necessary.
if (nextImage == null) {
nextImage = loadImage("nav_forward.gif");
}
// Set the initial tool tip and image.
nextButton.setToolTipText("Next");
nextButton.setImage(nextImage);
return nextButton;
}
/**
* Creates the "play" button that toggles the playback operation.
*
* @param parent
* The parent Composite for this widget. Assumed not to be
* {@code null}.
* @return The new widget.
*/
private Button createPlayButton(Composite parent) {
Button playButton = new Button(parent, SWT.PUSH);
// When the button is clicked, playback should be toggled.
playButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// Increment the timestep.
if (setTimestep(timestep + 1)) {
// Toggle playback.
setPlayback(!isPlaying, e);
}
});
// Load the play and pause button images as necessary.
if (playImage == null) {
playImage = loadImage("nav_go.gif");
}
if (pauseImage == null) {
pauseImage = loadImage("suspend_co.gif");
}
// Set the initial tool tip and image.
playButton.setToolTipText("Play");
playButton.setImage(playImage);
return playButton;
}
/**
* Creates the "previous" button that deccrements the timestep.
*
* @param parent
* The parent Composite. Assumed not to be {@code null}.
* @return The new button.
*/
private Button createPrevButton(Composite parent) {
Button prevButton = new Button(parent, SWT.PUSH);
// When the button is clicked, playback should be stopped and the
// timestep should be decremented.
prevButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// Disable playback.
setPlayback(false, e);
// Decrement the timestep.
if (decrementTimestep()) {
notifyListeners(e);
}
}
});
spinnerPrev.addSelectionListener(new SelectionAdapter() {
// Load the button image as necessary.
if (prevImage == null) {
prevImage = loadImage("nav_backward.gif");
}
// Set the initial tool tip and image.
prevButton.setToolTipText("Previous");
prevButton.setImage(prevImage);
return prevButton;
}
/**
* Creates the scale or slider widget that can be used to quickly traverse
* the timesteps.
*
* @param parent
* The parent Composite for this widget. Assumed not to be
* {@code null}.
* @return The new widget.
*/
private Scale createScale(Composite parent) {
final Scale scale = new Scale(this, SWT.HORIZONTAL);
scale.setMinimum(0);
scale.setIncrement(1);
scale.setMaximum(0);
scale.setToolTipText("Traverses the timesteps");
scale.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// Decrement teh timestep.
if (setTimestep(timestep - 1)) {
// Disable playback.
setPlayback(false, e);
// Get the timestep from the scale widget.
if (setTimestep(scale.getSelection())) {
notifyListeners(e);
}
}
});
spinnerText.addSelectionListener(new SelectionAdapter() {
return scale;
}
/**
* Creates the text widget for setting this time widget to a specific time.
*
* @param parent
* The parent Composite for this widget. Assumed not to be
* {@code null}.
* @return The new widget.
*/
private Text createText(Composite parent) {
final Text text = new Text(this, SWT.SINGLE | SWT.BORDER);
text.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
widgetSelected(e);
......@@ -180,9 +336,12 @@ public class TimeSliderComposite extends Composite {
@Override
public void widgetSelected(SelectionEvent e) {
// Disable playback.
setPlayback(false, e);
// Try to find the value from the text widget's new text.
try {
double value = Double.parseDouble(spinnerText.getText());
double value = Double.parseDouble(text.getText());
// Set the value to the nearest allowed time.
if (setTimestep(times.findNearestIndex(value))) {
notifyListeners(e);
......@@ -190,21 +349,39 @@ public class TimeSliderComposite extends Composite {
// Update the text to the set time regardless of whether
// it changed. This lets the user know their input was
// accepted.
spinnerText.setText(Double.toString(getTime()));
text.setText(Double.toString(getTime()));
}
} catch (NumberFormatException exception) {
// If the number was invalid, revert to the previous text.
spinnerText.setText(Double.toString(getTime()));
text.setText(Double.toString(getTime()));
}
return;
}
});
// Refresh the widgets. This will enable/disable them as necessary.
syncWidgets();
// Tweak the default appearance.
text.setFont(parent.getFont());
return;
// Set the initial tool tip.
text.setToolTipText("The current time");
return text;
}
/**
* A convenient operation for loading a custom image resource from this
* widget's path.
*
* @param name
* The name of (path to) the image to load, like "/image1.gif".
* @return The loaded image, or {@code null} if the image could not be
* loaded.
*/
private Image loadImage(String name) {
Bundle bundle = FrameworkUtil.getBundle(TimeSliderComposite.class);
URL url = bundle.getEntry("icons/" + name);
return ImageDescriptor.createFromURL(url).createImage();
}
/*
......@@ -216,9 +393,9 @@ public class TimeSliderComposite extends Composite {
// Update the background colors for all child widgets.
scale.setBackground(color);
spinnerNext.getParent().setBackground(color);
spinnerNext.setBackground(color);
spinnerPrev.setBackground(color);
playButton.setBackground(color);
nextButton.setBackground(color);
prevButton.setBackground(color);
return;
}
......@@ -228,54 +405,43 @@ public class TimeSliderComposite extends Composite {
*
* @param newValue
* The new value. Assumed to be valid.
* @return True if the value changed, false otherwise.
* @return True if the timestep changed, false otherwise.
*/
private boolean setTimestep(int newValue) {
boolean changed = false;
if (newValue != timestep) {
changed = true;
// Update the timestep.
timestep = newValue;
// The "next" button should be disabled if the timestep is the max.
spinnerNext.setEnabled(timestep < times.size() - 1);
// The "prev" button should be disabled if the timestep is the min.
spinnerPrev.setEnabled(timestep > 0);
// Update the selections on the other widgets.
// Update the selections on the widgets.
scale.setSelection(timestep);
spinnerText.setText(Double.toString(getTime()));
text.setText(Double.toString(times.get(timestep)));
}
return changed;
}
/**
* Refreshes the widgets based on the current timesteps. This will
* enable/disable as is appropriate.
* Increments the timestep. Updates all widgets as necessary. This will loop
* back around to the first timestep if necessary.
*
* @return True if the timestep changed, false otherwise.
*/
private void syncWidgets() {
// The widgets should only be enabled if there is more than 1 timestep.
int size = times != null ? times.size() : 0;
boolean widgetsEnabled = size > 1;
// Refresh the scale widget's max value.
scale.setMaximum(widgetsEnabled ? size - 1 : 0);
// Enable/disable the widgets as necessary.
scale.setEnabled(widgetsEnabled);
spinnerText.setEnabled(widgetsEnabled);
spinnerNext.setEnabled(widgetsEnabled & timestep < size - 1);
spinnerPrev.setEnabled(widgetsEnabled & timestep > 0);
// Update the selections on the widgets.
scale.setSelection(0);
spinnerText.setText(size > 0 ? Double.toString(getTime()) : NO_TIMES);
private boolean incrementTimestep() {
return setTimestep((timestep + 1) % times.size());
}
return;
/**
* Decrements the timestep. Updates all widgets as necessary. This will loop
* back around to the last timestep if necessary.
*
* @return True if the timestep changed, false otherwise.
*/
private boolean decrementTimestep() {
int size = times.size();
return setTimestep((timestep - 1 + size) % size);
}
/**
......@@ -316,7 +482,22 @@ public class TimeSliderComposite extends Composite {
}
// Refresh the widgets.
syncWidgets();
final int size = this.times != null ? this.times.size() : 0;
boolean widgetsEnabled = size > 1;
// Enable/disable the widgets as necessary.
scale.setEnabled(widgetsEnabled);
text.setEnabled(widgetsEnabled);
nextButton.setEnabled(widgetsEnabled);
prevButton.setEnabled(widgetsEnabled);
playButton.setEnabled(widgetsEnabled);
// Refresh the scale widget's max value.
scale.setMaximum(widgetsEnabled ? size - 1 : 0);
// Reset the selection of the widgets.
scale.setSelection(0);
text.setText(size > 0 ? Double.toString(getTime()) : NO_TIMES);
return;
}
......@@ -357,6 +538,61 @@ public class TimeSliderComposite extends Composite {
return timestep >= 0 ? times.get(timestep) : 0.0;
}
/**
* Starts or stops the playback operation.
*
* @param play
* If true, playback will be started. If false, playback will be
* stopped.
* @param e
* The selection event that triggered the playback operation.
* This is only used if play is true. Otherwise, you may use
* {@code null}.
*/
private void setPlayback(boolean play, final SelectionEvent e) {
if (play != this.isPlaying) {
this.isPlaying = play;
// Determine the text and image for the play/pause button as well as
// whether the playback event should be scheduled or cancelled.
final String text;
final Image image;