/*
* Copyright (c) 2008 Stiftung Deutsches Elektronen-Synchrotron,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
*
* THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
* WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
* NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
* THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
* IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
* CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
* NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
* DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
* OR MODIFICATIONS.
* THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
* USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
* PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
* AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
*/
package org.csstudio.sds.components.ui.internal.figures;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.csstudio.sds.components.ui.internal.utils.TextPainter;
import org.csstudio.sds.ui.CheckedUiRunnable;
import org.csstudio.sds.ui.figures.BorderAdapter;
import org.csstudio.sds.ui.figures.CrossedOutAdapter;
import org.csstudio.sds.ui.figures.IBorderEquippedWidget;
import org.csstudio.sds.ui.figures.ICrossedFigure;
import org.csstudio.sds.ui.figures.IRhombusEquippedWidget;
import org.csstudio.sds.ui.figures.RhombusAdapter;
import org.csstudio.sds.util.ExecutionService;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Display;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An image figure.
*
* @author jbercic, Xihui Chen
*
*/
public final class RefreshableImageFigure extends Figure implements IAdaptable {
private static final Logger LOG = LoggerFactory.getLogger(RefreshableImageFigure.class);
/**
* A border adapter, which covers all border handlings.
*/
private IBorderEquippedWidget _borderAdapter;
/**
* The {@link IPath} to the image.
*/
private IPath _path = new Path("");
/**
* The image itself.
*/
private Image staticImage=null;
/**
* The width of the image.
*/
private int _imgWidth=0;
/**
* The height of the image.
*/
private int _imgHeight=0;
/**
* The amount of pixels, which are cropped from the top.
*/
private int _topCrop=0;
/**
* The amount of pixels, which are cropped from the bottom.
*/
private int _bottomCrop=0;
/**
* The amount of pixels, which are cropped from the left.
*/
private int _leftCrop=0;
/**
* The amount of pixels, which are cropped from the right.
*/
private int _rightCrop=0;
/**
* The stretch state for the image.
*/
private boolean _stretch=true;
/**
* If this is an animated image
*/
private boolean animated = false;
private Image offScreenImage;
private GC offScreenImageGC;
/**
* The imaged data array for animated image
*/
private ImageData[] imageDataArray;
private ImageData[] originalImageDataArray;
/**
* The index in image data array
*/
private int showIndex = 0;
/**
* The animated image is being refreshed by editpart
*/
private boolean refreshing = false;
private boolean stoppedAnimation = false;
private boolean loadingError = false;
private final boolean useGIFBackground = false;
private final ImageLoader loader = new ImageLoader();
private ImageData originalStaticImageData = null;
private int repeatCount;
private int animationIndex;
private long lastUpdateTime;
private long interval_ms;
private ScheduledFuture<?> scheduledFuture;
private CrossedOutAdapter _crossedOutAdapter;
private RhombusAdapter _rhombusAdapter;
public RefreshableImageFigure() {
}
/**
* We want to have local coordinates here.
* @return True if here should used local coordinates
*/
@Override
protected boolean useLocalCoordinates() {
return true;
}
/**
* The main drawing routine.
* @param graphics The {@link Graphics} to use
*/
@SuppressWarnings("unused")
@Override
public void paintFigure(final Graphics graphics) {
Rectangle bound=getBounds().getCopy();
bound.crop(this.getInsets());
if(loadingError) {
if (staticImage!=null) {
staticImage.dispose();
}
staticImage=null;
if (!_path.isEmpty()) {
/*Font f=gfx.getFont();
FontData fd=f.getFontData()[0];
if (bound.width>=20*30) {
fd.setHeight(30);
} else {
if (bound.width/20+1<7) {
fd.setHeight(7);
} else {
fd.setHeight(bound.width/20+1);
}
}
f=new Font(Display.getDefault(),fd);
gfx.setFont(f);*/
graphics.setBackgroundColor(getBackgroundColor());
graphics.setForegroundColor(getForegroundColor());
graphics.fillRectangle(bound);
graphics.translate(bound.getLocation());
TextPainter.drawText(graphics,"ERROR in loading image\n"+_path,bound.width/2,bound.height/2,TextPainter.CENTER);
//f.dispose();
}
return;
}
//create static image
if((staticImage == null) && (originalStaticImageData !=null)){
if (_stretch) {
staticImage=new Image(Display.getDefault(),
originalStaticImageData.scaledTo(bound.width+_leftCrop+_rightCrop,
bound.height+_topCrop+_bottomCrop));
if(animated) {
imageDataArray = new ImageData[originalImageDataArray.length];
double widthScaleRatio = (double)(bound.width+_leftCrop+_rightCrop) / (double)originalStaticImageData.width;
double heightScaleRatio = (double)(bound.height+_topCrop+_bottomCrop) / (double)originalStaticImageData.height;
for (int i = 0; i < originalImageDataArray.length; i++){
int scaleWidth = (int) (originalImageDataArray[i].width * widthScaleRatio);
int scaleHeight = (int) (originalImageDataArray[i].height * heightScaleRatio);
int x = (int) (originalImageDataArray[i].x * widthScaleRatio);
int y = (int) (originalImageDataArray[i].y * heightScaleRatio);
imageDataArray[i] = originalImageDataArray[i].scaledTo(scaleWidth, scaleHeight);
imageDataArray[i].x = x;
imageDataArray[i].y = y;
}
}
} else {
staticImage=new Image(Display.getDefault(),originalStaticImageData);
if(animated) {
imageDataArray = originalImageDataArray;
}
}
_imgWidth=staticImage.getBounds().width;
_imgHeight=staticImage.getBounds().height;
if(animated) {
if ((offScreenImage != null) && !offScreenImage.isDisposed()) {
offScreenImage.dispose();
}
offScreenImage = new Image(Display.getDefault(), _imgWidth,
_imgHeight);
if ((offScreenImageGC != null) && !offScreenImageGC.isDisposed()) {
offScreenImageGC.dispose();
}
offScreenImageGC = new GC(offScreenImage);
}
}
//avoid negative number
_leftCrop = _leftCrop > _imgWidth ? 0 : _leftCrop;
_topCrop = _topCrop > _imgWidth ? 0 : _topCrop;
int cropedWidth = (_imgWidth-_leftCrop-_rightCrop) > 0 ?
(_imgWidth-_leftCrop-_rightCrop) : _imgWidth;
int cropedHeight = (_imgHeight-_topCrop-_bottomCrop) > 0 ?
(_imgHeight-_topCrop-_bottomCrop) : _imgHeight;
if(animated && refreshing) { //draw refreshing image
ImageData imageData = imageDataArray[showIndex];
Image refresh_image = new Image(Display.getDefault(), imageData);
switch (imageData.disposalMethod) {
case SWT.DM_FILL_BACKGROUND:
/* Fill with the background color before drawing. */
Color bgColor = null;
if (useGIFBackground && (loader.backgroundPixel != -1)) {
bgColor = new Color(Display.getDefault(), imageData.palette.getRGB(loader.backgroundPixel));
}
offScreenImageGC.setBackground(bgColor != null ? bgColor : getBackgroundColor());
offScreenImageGC.fillRectangle(
imageData.x, imageData.y, imageData.width, imageData.height);
if (bgColor != null) {
bgColor.dispose();
}
break;
case SWT.DM_FILL_PREVIOUS:
/* Restore the previous image before drawing. */
Image startImage = new Image(Display.getDefault(), imageDataArray[0]);
offScreenImageGC.drawImage(
startImage,
0,
0,
imageData.width,
imageData.height,
imageData.x,
imageData.y,
imageData.width,
imageData.height);
startImage.dispose();
break;
}
offScreenImageGC.drawImage(refresh_image,
0,
0,
imageData.width,
imageData.height,
imageData.x,
imageData.y,
imageData.width,
imageData.height);
graphics.drawImage(offScreenImage, _leftCrop,_topCrop,
cropedWidth,cropedHeight,
bound.x,bound.y,
cropedWidth,cropedHeight);
refresh_image.dispose();
} else { // draw static image
if(animated && stoppedAnimation && (offScreenImage != null) && (showIndex!=0)){
graphics.drawImage(offScreenImage, _leftCrop,_topCrop,
cropedWidth,cropedHeight,
bound.x,bound.y,
cropedWidth,cropedHeight);
} else {
graphics.drawImage(staticImage,
_leftCrop,_topCrop,
cropedWidth,cropedHeight,
bound.x,bound.y,
cropedWidth,cropedHeight);
}
}
_crossedOutAdapter.paint(graphics);
_rhombusAdapter.paint(graphics);
}
/**
* Resizes the image.
*/
public void resizeImage() {
if ((staticImage!=null) && !staticImage.isDisposed()) {
staticImage.dispose();
}
staticImage=null;
if(refreshing && animated){
stopAnimation();
startAnimation();
}
}
/**
* Sets the path to the image.
* @param newval The path to the image
*/
public void setFilePath(final IPath newval) {
loadingError = false;
_path=newval;
if ((staticImage!=null) && !staticImage.isDisposed()) {
staticImage.dispose();
}
staticImage=null;
try {
if ((staticImage==null) && !_path.isEmpty()) {
// _path
String currentPath = _path.toOSString();
IPath fullPath = ResourcesPlugin.getWorkspace().getRoot().getLocation();
try {
loadImage(currentPath);
} catch (Exception e) {
try {
IPath append = fullPath.append(_path);
IFile[] findFilesForLocation = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(append);
currentPath = findFilesForLocation[0].getLocation().toOSString();
loadImage(currentPath);
} catch (Exception ex) {
String[] segments = _path.segments();
String projectName = segments[0];
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
int index = 1;
IFolder folder = null;
currentPath = null;
while (index < segments.length-1) {
folder = project.getFolder(segments[index]);
if (currentPath!=null) {
currentPath = currentPath+ IPath.SEPARATOR +segments[index];
}
if (folder.isLinked()) {
currentPath = folder.getLocation().toString();
}
index++;
}
currentPath = currentPath + IPath.SEPARATOR + segments[segments.length-1];
loadImage(currentPath);
}
}
}
} catch (Exception e) {
loadingError = true;
LOG.error("ERROR in loading image\n"+_path, e);
}
if(animated){
stopAnimation();
startAnimation();
}
}
private void loadImage(final String currentPath) {
Image temp = null;
try {
temp=new Image(Display.getDefault(),currentPath);
originalStaticImageData = temp.getImageData();
}finally {
if ((temp != null) && !temp.isDisposed()) {
temp.dispose();
}
}
originalImageDataArray = loader.load(currentPath);
animated = (originalImageDataArray.length > 1);
}
/**
* Returns the path to the image.
* @return The path to the image
*/
public IPath getFilePath() {
return _path;
}
/**
* Sets the amount of pixels, which are cropped from the top.
* @param newval The amount of pixels
*/
public void setTopCrop(final int newval) {
_topCrop=newval;
resizeImage();
}
/**
* Returns the amount of pixels, which are cropped from the top.
* @return The amount of pixels
*/
public int getTopCrop() {
return _topCrop;
}
/**
* Sets the amount of pixels, which are cropped from the bottom.
* @param newval The amount of pixels
*/
public void setBottomCrop(final int newval) {
_bottomCrop=newval;
resizeImage();
}
/**
* Returns the amount of pixels, which are cropped from the top.
* @return The amount of pixels
*/
public int getBottomCrop() {
return _bottomCrop;
}
/**
* Sets the amount of pixels, which are cropped from the left.
* @param newval The amount of pixels
*/
public void setLeftCrop(final int newval) {
_leftCrop=newval;
resizeImage();
}
/**
* Returns the amount of pixels, which are cropped from the top.
* @return The amount of pixels
*/
public int getLeftCrop() {
return _leftCrop;
}
/**
* Sets the amount of pixels, which are cropped from the right.
* @param newval The amount of pixels
*/
public void setRightCrop(final int newval) {
_rightCrop=newval;
resizeImage();
}
/**
* Returns the amount of pixels, which are cropped from the top.
* @return The amount of pixels
*/
public int getRightCrop() {
return _rightCrop;
}
/**
* Sets the stretch state for the image.
* @param newval The new state (true, if it should be stretched, false otherwise)
*/
public void setStretch(final boolean newval) {
_stretch=newval;
if ((staticImage!=null) && !staticImage.isDisposed()) {
staticImage.dispose();
}
staticImage=null;
if(refreshing && animated){
stopAnimation();
startAnimation();
}
}
@Override
public void setVisible(final boolean visible) {
super.setVisible(visible);
if(visible) {
startAnimation();
} else {
stopAnimation();
}
}
/**
* Returns the stretch state for the image.
* @return True, if it should be stretched, false otherwise
*/
public boolean getStretch() {
return _stretch;
}
/**
* @param showIndex the showIndex to set
*/
public void setShowIndex(final int showIndex) {
this.showIndex = showIndex;
repaint();
}
/**
* Automatically make the widget bounds be adjusted to the size of the static image
* @param autoSize
*/
public void setAutoSize(final boolean autoSize){
if(!_stretch && autoSize) {
resizeImage();
}
}
/**
* @return the auto sized widget dimension according to the static imageSize
*/
public Dimension getAutoSizedDimension() {
if(originalStaticImageData != null) {
return new Dimension(originalStaticImageData.width + getInsets().getWidth() - _leftCrop - _rightCrop,
originalStaticImageData.height + getInsets().getHeight() - _topCrop - _bottomCrop);
} else {
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public Object getAdapter(final Class adapter) {
if (adapter == IBorderEquippedWidget.class) {
if(_borderAdapter==null) {
_borderAdapter = new BorderAdapter(this);
}
return _borderAdapter;
} else if(adapter == ICrossedFigure.class) {
if(_crossedOutAdapter==null) {
_crossedOutAdapter = new CrossedOutAdapter(this);
}
return _crossedOutAdapter;
} else if(adapter == IRhombusEquippedWidget.class) {
if(_rhombusAdapter==null) {
_rhombusAdapter = new RhombusAdapter(this);
}
return _rhombusAdapter;
}
return null;
}
/**
* start the animation if the image is an animated GIF image.
*/
public void startAnimation(){
if(animated && !refreshing && !stoppedAnimation) {
repeatCount = loader.repeatCount;
//animationIndex = 0;
lastUpdateTime=0;
interval_ms =0;
refreshing = true;
Runnable animationTask = new Runnable() {
@Override
public void run() {
new CheckedUiRunnable(){
@Override
protected void doRunInUi() {
if((loader.repeatCount ==0) || (repeatCount >0)) {
long currentTime = System.currentTimeMillis();
//use Math.abs() to ensure that the system time adjust won't cause problem
if(Math.abs(currentTime - lastUpdateTime) >= interval_ms) {
setShowIndex(animationIndex);
lastUpdateTime = currentTime;
int ms = originalImageDataArray[animationIndex].delayTime * 10;
animationIndex = (animationIndex + 1) % originalImageDataArray.length;
if (ms < 20) {
ms += 30;
}
if (ms < 30) {
ms += 10;
}
interval_ms = ms;
/* If we have just drawn the last image, decrement the repeat count and start again. */
if(animationIndex == originalImageDataArray.length -1) {
repeatCount--;
}
}
}
}
};
}
};
if(scheduledFuture !=null){
scheduledFuture.cancel(true);
scheduledFuture = null;
}
scheduledFuture = ExecutionService.getInstance().
getScheduledExecutorService().scheduleAtFixedRate(
animationTask, 100, 10, TimeUnit.MILLISECONDS);
}
}
/**
* stop the animation if the image is an animated GIF image.
*/
public void stopAnimation(){
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
scheduledFuture = null;
}
refreshing = false;
}
public void setStopAnimation(final boolean stop){
stoppedAnimation = stop;
if(stop){
stopAnimation();
}else if(animated){
startAnimation();
}
}
/**
* dispose the resources used by this figure
*/
public void dispose(){
if ((offScreenImage != null) && !offScreenImage.isDisposed()) {
offScreenImage.dispose();
offScreenImage = null;
}
if ((offScreenImageGC != null) && !offScreenImageGC.isDisposed()) {
offScreenImageGC.dispose();
offScreenImage = null;
}
if ((staticImage != null) && !staticImage.isDisposed()) {
staticImage.dispose();
staticImage = null;
}
}
}