/*
* Copyright (c) 2012 European Synchrotron Radiation Facility,
* Diamond Light Source Ltd.
*
* 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 fable.imageviewer.editor;
import java.io.File;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Arrays;
import java.util.TreeSet;
import org.dawb.common.ui.util.GridUtils;
import org.dawb.common.ui.widgets.ActionBarWrapper;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
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.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Slider;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IActionBars2;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IReusableEditor;
import org.eclipse.ui.IShowEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.part.FileEditorInput;
import org.embl.cca.utils.threading.ExecutableManager;
import org.embl.cca.utils.datahandling.file.WildCardFileFilter;
import org.embl.cca.utils.imageviewer.FilenameCaseInsensitiveComparator;
import org.embl.cca.utils.imageviewer.MemoryImageEditorInput;
import org.embl.cca.utils.threading.TrackableJob;
import org.embl.cca.utils.threading.TrackableRunnable;
import fable.framework.logging.FableLogger;
import fable.imageviewer.component.ActionsProvider;
import fable.imageviewer.component.ImageComponent;
import fable.imageviewer.component.ImageComponentImage;
import fable.imageviewer.component.ImagePlay;
import fable.imageviewer.model.ImageModel;
import fable.imageviewer.model.ImageModelFactory;
import fable.imageviewer.rcp.Activator;
/**
* ImageEditor
*
* @author Matthew Gerring
*
*/
public class ImageEditor extends EditorPart implements IReusableEditor, ActionsProvider, IShowEditorInput {
/**
* Plug-in ID.
*/
public static final String ID = "fable.imageviewer.editor.ImageEditor";
/**
* The object which does the work, can be used in different view parts.
*/
private ImageComponent imageComponent; ////earlier AbstractPlottingSystem plottingSystem
/**
* Redirects the action bars so that we can show local actions to the
* user
*/
private ActionBarWrapper actionBarsWrapper;
private Label totalSliderImageLabel;
private Slider imageSlider;
private Text imageFilesWindowWidthText;
private int imageFilesWindowWidth; //aka batchAmount
private File[] allImageFiles;
private TreeSet<File> loadedImageFiles; //Indices in loadedImagesFiles which are loaded
boolean autoFollow;
Button imageFilesAutoLatestButton;
ImageModel resultImageModel = null;
static private NumberFormat decimalFormat = NumberFormat.getNumberInstance();
ExecutableManager imageLoaderManager = null;
Thread imageFilesAutoLatestThread = null;
/*
private void initSlider( int amount ){
//if(!label.isDisposed() && label !=null){
imageSlider.setValues( 1, 1, amount+1, 1, 1, Math.max(1, amount/5) );
totalSliderImageLabel.setText( "1" + "/" + amount );
totalSliderImageLabel.getParent().pack();
//}
}
*/
private void updateSlider( int sel ) {
if( imageSlider == null || imageSlider.isDisposed() )
return;
synchronized (imageSlider) {
final int min = 1;
final int total = allImageFiles.length;
final int selection = Math.max(Math.min(sel,total + 1),min);
try {
// if( imageSlider.getSelection() == selection )
// return;
imageFilesWindowWidth = imageSlider.getThumb();
imageSlider.setValues(selection, min, total+1, imageFilesWindowWidth, 1, Math.max(imageFilesWindowWidth, total/5));
totalSliderImageLabel.setText( "" + selection + "/" + total + " ");
totalSliderImageLabel.getParent().pack();
sliderMoved( selection );
} catch (SWTException e) {
//eat it!
}
}
}
private void updateBatchAmount( int amount ) {
if( imageSlider == null || imageSlider.isDisposed() )
return;
synchronized (imageSlider) {
if( imageFilesWindowWidth == amount )
return;
int oldSel = imageSlider.getSelection();
int newSel = oldSel;
if( amount < 1 )
amount = 1;
else if( amount > imageSlider.getMaximum() - oldSel && oldSel > 1 ) {
newSel = imageSlider.getMaximum() - amount;
if( newSel < 1 ) {
newSel = 1;
amount = imageSlider.getMaximum() - newSel;
}
// amount = imageSlider.getMaximum() - imageSlider.getSelection();
}
imageSlider.setThumb( amount );
if( oldSel != newSel )
imageSlider.setSelection( newSel );
else
updateSlider( newSel );
/*
imageSlider.setSelection(imageSlider.getSelection());
imageFilesWindowWidth = amount;
imageFilesWindowWidthText.setText( "" + amount );
imageFilesWindowWidthText.getParent().pack();
sliderMoved( imageSlider.getSelection() ); //Updates loaded files and draw image
*/
}
}
private void sliderMoved( int pos ) {
File[] toLoadImageFiles = null;
synchronized (allImageFiles) {
int iMax = imageFilesWindowWidth;
toLoadImageFiles = new File[iMax];
for( int i = 0; i < iMax; i++ )
toLoadImageFiles[ i ] = allImageFiles[ pos - 1 + i ];
}
createPlot(toLoadImageFiles);
}
public void onImageFilesAutoLatestButtonSelected() {
if( autoFollow != imageFilesAutoLatestButton.getSelection() ) {
autoFollow = imageFilesAutoLatestButton.getSelection();
if( autoFollow ) {
// imageSlider.setEnabled( false );
imageFilesAutoLatestThread = new Thread() {
ExecutableManager imageFilesAutoLatestManager = null;
protected boolean checkDirectory() {
final IPath imageFilename = getPath( getEditorInput() );
final File[] currentAllImageFiles = listIndexedFilesOf( imageFilename );
TreeSet<File> currentAllImageFilesSet = new TreeSet<File>( Arrays.asList(currentAllImageFiles) );
TreeSet<File> allImageFilesSet = new TreeSet<File>( Arrays.asList(allImageFiles) );
if( currentAllImageFilesSet.containsAll(allImageFilesSet)
&& allImageFilesSet.containsAll(currentAllImageFilesSet) )
return false;
if( imageLoaderManager.isAlive() )
return false;
final TrackableRunnable runnable = new TrackableRunnable(imageFilesAutoLatestManager) {
@Override
public void runThis() {
synchronized (imageSlider) {
allImageFiles = currentAllImageFiles;
updateSlider( allImageFiles.length - imageFilesWindowWidth + 1 );
}
}
};
imageFilesAutoLatestManager = ExecutableManager.addRequest(runnable);
return true;
}
@Override
public void run() {
do {
int sleepTime = 10; //Sleeping some even if directory updated, so user can move slider (and abort this thread)
if( !checkDirectory() )
sleepTime = 100;
try {
sleep(sleepTime);
} catch (InterruptedException e) {
break;
}
} while( true );
}
@Override
public void interrupt() {
if( imageFilesAutoLatestManager != null )
imageFilesAutoLatestManager.interrupt();
super.interrupt();
}
};
imageFilesAutoLatestThread.start();
} else {
imageFilesAutoLatestThread.interrupt();
// imageSlider.setEnabled( true );
}
}
}
private void createImageSelectorUI(Composite parent) {
final Composite sliderMain = new Composite(parent, SWT.NONE);
sliderMain.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
sliderMain.setLayout(new GridLayout(5, false));
GridUtils.removeMargins(sliderMain);
imageSlider = new Slider(sliderMain, SWT.HORIZONTAL);
imageSlider.setThumb(imageFilesWindowWidth);
// imageSlider.setBounds(115, 50, 25, 15);
totalSliderImageLabel = new Label(sliderMain, SWT.NONE);
totalSliderImageLabel.setToolTipText("Selected image/Number of images");
totalSliderImageLabel.setText("0/0");
imageSlider.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
// imageSlider.setSelection( imageSlider.getSelection() );
if( autoFollow ) {
imageFilesAutoLatestButton.setSelection( false );
//setSelection does not trigger the Selection event because we are in Selection event here already,
onImageFilesAutoLatestButtonSelected(); //so we have to call it manually, which is lame.
}
updateSlider( imageSlider.getSelection() );
}
});
final Label imageFilesWindowWidthLabel = new Label(sliderMain, SWT.NONE);
imageFilesWindowWidthLabel.setToolTipText("Number of images to sum up");
imageFilesWindowWidthLabel.setText("Batch Amount");
imageFilesWindowWidthText = new Text(sliderMain, SWT.BORDER | SWT.RIGHT);
imageFilesWindowWidthText.setToolTipText(imageFilesWindowWidthLabel.getToolTipText());
imageFilesWindowWidthText.setText( "" + imageFilesWindowWidth );
imageFilesWindowWidthText.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WHITE));
imageFilesWindowWidthText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
if( imageFilesWindowWidthText == null || imageFilesWindowWidthText.isDisposed() ) return;
if( !imageFilesWindowWidthText.isEnabled() || imageFilesWindowWidthText.getText().isEmpty() )
return;
try {
updateBatchAmount( decimalFormat.parse( imageFilesWindowWidthText.getText() ).intValue() );
} catch (ParseException exc) {
FableLogger.error("Unable to parse batch amount value: " + imageFilesWindowWidthText.getText(), exc);
}
}
});
imageFilesAutoLatestButton = new Button(sliderMain, SWT.CHECK);
imageFilesAutoLatestButton.setText("Auto latest");
imageFilesAutoLatestButton.setToolTipText("Automatically scan directory and display last batch");
autoFollow = false;
imageFilesAutoLatestButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
onImageFilesAutoLatestButtonSelected();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
}
protected IPath getPath( IEditorInput editorInput ) {
final IPath imageFilename;
if( editorInput instanceof FileEditorInput )
imageFilename = new Path( ((FileEditorInput)editorInput).getURI().getPath() );
else if( editorInput instanceof FileStoreEditorInput )
imageFilename = new Path( ((FileStoreEditorInput)editorInput).getURI().getPath() );
else {
IFile iF = (IFile)editorInput.getAdapter(IFile.class);
if( iF != null )
imageFilename = iF.getLocation().makeAbsolute();
else {
FableLogger.error("Cannot determine full path of requested file");
return null;
}
}
return imageFilename;
}
protected File[] listIndexedFilesOf( IPath imageFilename ) {
File[] result = null;
String q = imageFilename.removeFileExtension().lastSegment().toString();
String r = q.replaceAll("[0-9]*$", "");
int len = q.length() - r.length();
for( int i = 0; i < len; i++ )
r += "?";
r += "." + imageFilename.getFileExtension();
result = new File(imageFilename.removeLastSegments(1).toString()).listFiles( new WildCardFileFilter(r, false) );
Arrays.sort( result, new FilenameCaseInsensitiveComparator() );
return result;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets
* .Composite)
*/
@Override
public void createPartControl(Composite parent) {
parent.setLayout(new GridLayout(1,false)); ////earlier main
final Composite top = new Composite(parent, SWT.NONE); ////earlier tools
top.setLayout(new GridLayout(3, false));
top.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
final Text point = new Text(top, SWT.LEFT);
point.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
point.setEditable(false);
GridUtils.setVisible(point, true);
point.setBackground(top.getBackground());
final MenuManager menuMan = new MenuManager();
final ToolBarManager toolMan = new ToolBarManager(SWT.FLAT|SWT.RIGHT);
final ToolBar toolBar = toolMan.createControl(top);
toolBar.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
loadedImageFiles = new TreeSet<File>();
imageFilesWindowWidth = 1;
/* Top line containing image selector sliders */
createImageSelectorUI(top);
Action menuAction = new Action("", Activator.getImageDescriptor("/icons/DropDown.png")) {
@Override
public void run() {
final Menu mbar = menuMan.createContextMenu(toolBar);
mbar.setVisible(true);
}
};
final IActionBars bars = this.getEditorSite().getActionBars();
this.actionBarsWrapper = new ActionBarWrapper(toolMan,menuMan,null,(IActionBars2)bars);
final Composite plotComposite = new Composite(parent, SWT.NONE);
plotComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
imageComponent = new ImageComponent(this);
imageComponent.setStatusLabel(point);
imageComponent.createPartControl(plotComposite);
ImagePlay.setView(this.getImageComponent());
editorInputChanged();
GridUtils.removeMargins(plotComposite);
GridUtils.removeMargins(top);
GridUtils.removeMargins(parent);
toolMan.add(menuAction);
toolMan.update(true);
}
private void editorInputChanged() {
if (getEditorInput() instanceof MemoryImageEditorInput) {
MemoryImageEditorInput miei = (MemoryImageEditorInput)getEditorInput();
ImageModel imageModel = new ImageModel("", miei.getWidth(), miei.getHeight(), miei.getData(), 0);
if ("ExpSimImgInput".equals(getEditorInput().getName())) {
} else {
/*
System.out.println("First block of received image (imageModel):");
for( int j = 0; j < 10; j++ ) {
for( int i = 0; i < 10; i++ ) {
System.out.print( " " + Integer.toHexString( (int)imageModel.getData(i, j) ) );
}
System.out.println();
}
*/
}
createPlot(imageModel);
} else {
final IPath imageFilename = getPath( getEditorInput() );
allImageFiles = listIndexedFilesOf( imageFilename );
String actFname = imageFilename.lastSegment().toString();
int pos;
for (pos = 0; pos < allImageFiles.length; pos++ )
if (allImageFiles[pos].getName().equals(actFname))
break;
updateSlider( pos + 1 ); //it calls (and must call) createPlot()
}
}
private void createPlot(final ImageModel imageModel) {
if( imageComponent != null ) { //async call because we are not in UI thread, and for loadModel must be
getSite().getShell().getDisplay().asyncExec(new Runnable() {
public void run() {
imageComponent.loadModel(imageModel);
}
});
}
}
private void createPlot(final File[] toLoadImageFiles) {
final TrackableJob job = new TrackableJob(imageLoaderManager, "Read image data") {
TreeSet<File> toLoadImageFilesJob = new TreeSet<File>( Arrays.asList(toLoadImageFiles) );
ImageModel imageModel = null;
public IStatus processImage(File imageFile, boolean add) {
if( add || loadedImageFiles.size() > 1 ) {
final String filePath = imageFile.getAbsolutePath();
try {
imageModel = ImageModelFactory.getImageModel(filePath);
} catch (Throwable e) {
FableLogger.error("Cannot load file "+filePath, e);
return Status.CANCEL_STATUS;
}
if (imageModel==null) {
FableLogger.error("Cannot read file "+getEditorInput().getName());
return Status.CANCEL_STATUS;
}
if( isAborting() )
return Status.OK_STATUS;
if( loadedImageFiles.size() == 0 ) {
if( add )
resultImageModel = imageModel;
} else {
if( add )
resultImageModel.addImageModel( imageModel );
else
resultImageModel.subImageModel( imageModel );
}
}
if( add )
loadedImageFiles.add( imageFile );
else {
loadedImageFiles.remove( imageFile );
if( loadedImageFiles.size() == 0 )
resultImageModel = null;
}
return Status.OK_STATUS;
}
public IStatus runThis(IProgressMonitor monitor) {
/* Since running this and others aswell through imageLoaderManager,
* the single access of loading data is guaranteed.
*/
IStatus result = Status.CANCEL_STATUS;
do {
TreeSet<File> adding = new TreeSet<File>( toLoadImageFilesJob );
adding.removeAll( loadedImageFiles );
TreeSet<File> removing = new TreeSet<File>( loadedImageFiles );
removing.removeAll( toLoadImageFilesJob );
if( adding.size() + removing.size() > toLoadImageFilesJob.size() ) {
adding = toLoadImageFilesJob;
removing.clear();
loadedImageFiles.clear();
}
for( File i : adding ) {
if( isAborting() )
break;
result = processImage(i, true);
if( result != Status.OK_STATUS )
break;
}
for( File i : removing ) {
if( isAborting() )
break;
result = processImage(i, false);
if( result != Status.OK_STATUS )
break;
}
if( isAborting() )
break;
ImageModel resultImageModelDivided = resultImageModel;
if( loadedImageFiles.size() > 1 ) {
resultImageModelDivided = resultImageModel.clone();
float[] fsetdata = resultImageModelDivided.getData();
int divider = loadedImageFiles.size();
int jMax = fsetdata.length;
for( int j = 0; j < jMax; j++ )
fsetdata[ j ] /= divider;
}
if( isAborting() )
break;
createPlot(resultImageModelDivided);
if( loadedImageFiles.size() > 0 ) { //Checking for sure
Display.getDefault().syncExec(new Runnable(){
public void run() {
setPartName(loadedImageFiles.first().getName());
}
});
}
result = Status.OK_STATUS;
} while( false );
if( isAborting() ) {
setAborted();
return Status.CANCEL_STATUS;
}
return result;
}
};
job.setUser(false);
job.setPriority(Job.BUILD);
imageLoaderManager = ExecutableManager.setRequest(job);
}
/**
* Override to provide extra content.
* @param toolMan
*/
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.part.WorkbenchPart#setFocus()
*/
@Override
public void setFocus() {
if (imageComponent!=null) {
imageComponent.setFocus();
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.part.WorkbenchPart#dispose()
*/
@Override
public void dispose() {
if (imageComponent!=null) imageComponent.dispose();
}
public ImageComponentImage getImage() {
return imageComponent.getImage();
}
public void setPartName(final String name) {
super.setPartName(name);
}
public ImageComponent getImageComponent() {
return imageComponent;
}
@Override
public void doSave(IProgressMonitor monitor) {
}
@Override
public void doSaveAs() {
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
setSite(site);
setInput(input);
}
public void setInput(IEditorInput input) {
super.setInput(input);
setPartName(input.getName());
editorInputChanged();
}
@Override
public boolean isDirty() {
return false;
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
@Override
public void showEditorInput(IEditorInput editorInput) {
this.setInput(editorInput);
}
public IActionBars getActionBars() {
return actionBarsWrapper;
}
}