/*
* PngWalkTool.java
*
* Created on Apr 29, 2009, 3:17:56 AM
*/
package org.autoplot.pngwalk;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPHeaderCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import external.AnimatedGifDemo;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.Clipboard;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.das2.components.DasProgressPanel;
import org.das2.components.DataPointRecorder;
import org.das2.components.TearoffTabbedPane;
import org.das2.dataset.DataSetUpdateEvent;
import org.das2.dataset.DataSetUpdateListener;
import org.das2.datum.Datum;
import org.das2.datum.DatumRange;
import org.das2.datum.DatumRangeUtil;
import org.das2.datum.TimeParser;
import org.das2.datum.TimeUtil;
import org.das2.datum.Units;
import org.das2.datum.UnitsUtil;
import org.das2.datum.format.TimeDatumFormatter;
import org.das2.event.DataPointSelectionEvent;
import org.das2.event.DataPointSelectionListener;
import org.das2.util.ArgumentList;
import org.das2.util.FileUtil;
import org.das2.util.ImageUtil;
import org.das2.util.LoggerManager;
import org.das2.util.filesystem.FileSystem.FileSystemOfflineException;
import org.das2.util.monitor.AlertNullProgressMonitor;
import org.das2.util.monitor.NullProgressMonitor;
import org.das2.util.monitor.ProgressMonitor;
import org.jdesktop.beansbinding.AutoBinding.UpdateStrategy;
import org.jdesktop.beansbinding.BeanProperty;
import org.jdesktop.beansbinding.Binding;
import org.jdesktop.beansbinding.BindingGroup;
import org.jdesktop.beansbinding.Bindings;
import org.autoplot.AppManager;
import org.autoplot.AutoplotUI;
import org.autoplot.AutoplotUtil;
import org.autoplot.GuiSupport;
import org.autoplot.JythonUtil;
import org.autoplot.ScriptContext;
import org.autoplot.bookmarks.Bookmark;
import org.autoplot.bookmarks.BookmarksException;
import org.autoplot.bookmarks.BookmarksManager;
import org.autoplot.bookmarks.BookmarksManagerModel;
import org.autoplot.bookmarks.Util;
import org.autoplot.datasource.AutoplotSettings;
import org.autoplot.transferrable.ImageSelection;
import org.das2.qds.QDataSet;
import org.autoplot.datasource.DataSetSelector;
import org.autoplot.datasource.DataSetURI;
import org.autoplot.datasource.FileSystemUtil;
import org.autoplot.datasource.TimeRangeTool;
import org.autoplot.datasource.URISplit;
import org.autoplot.dom.Application;
import org.autoplot.dom.Plot;
import org.das2.graph.Painter;
import org.xml.sax.SAXException;
/**
* GUI for browsing PNGWalks, or sets of PNG images. These typically contain files named to make a time series, such as
* product_$Y$m$d.png, but this can browse any set of images using wildcards. This provides a number of views of the
* images, such as a grid of thumbnails and the coverflow view which shows an image and the preceding and succeeding images.
* This also contains a hook to get back into Autoplot, if product.vap (or vap named like the images) is found.
*
* @author jbf
*/
public final class PngWalkTool extends javax.swing.JPanel {
private static boolean ENABLE_QUALITY_CONTROL;
private QualityControlPanel qcPanel=null;
public static final String PREF_RECENT = "pngWalkRecent";
/**
* last location where image was exported
*/
public static final String PREF_LAST_EXPORT= "pngWalkLastExport";
private static final String DEFAULT_BOOKMARKS = " " +
"" +
"Demos" +
"" +
" " +
" POLAR/VIS Images" +
" pngwalk:http://vis.physics.uiowa.edu/survey/1996/04-apr/03/images/VIS_$Y_$m_$d_$H_$M_$S_EC.PNG" +
" Images from the POLAR/VIS instrument" +
" " +
" " +
" RBSP Emfisis HFR-WFR Orbits" +
" pngwalk:https://emfisis.physics.uiowa.edu/pngwalk/RBSP-A/HFR-WFR_orbit/product_$(o,id=rbspa-pp).png" +
" " +
" " +
" RBSP-A MagEIS Combined Spectra" +
" pngwalk:https://www.rbsp-ect.lanl.gov/data_pub/rbspa/ect/level2/combined-elec/rbspa_ect_L2-elec_$Y$m$d_v.1.0.0.png" +
" " +
"" +
"" +
"";
public PngWalkView[] views;
TearoffTabbedPane tabs;
WalkImageSequence seq;
JMenu navMenu;
Pattern actionMatch=null;
String actionCommand=null;
static final Logger logger= org.das2.util.LoggerManager.getLogger("autoplot.pngwalk");
private static final String RESOURCES= "/org/autoplot/resources/";
private static final Icon WARNING_ICON= new ImageIcon( AutoplotUI.class.getResource(RESOURCES+"warning-icon.png") );
private static final Icon ERROR_ICON= new ImageIcon( AutoplotUI.class.getResource(RESOURCES+"error-icon.png") );
private static final Icon BUSY_ICON= new ImageIcon( AutoplotUI.class.getResource(RESOURCES+"spinner.gif") );
private static final Icon READY_ICON= new ImageIcon( AutoplotUI.class.getResource(RESOURCES+"indProgress0.png") );
private static final Icon IDLE_ICON= new ImageIcon( AutoplotUI.class.getResource(RESOURCES+"idle-icon.png") );
int returnTabIndex=0; // index of the tab we left to look at the single panel view. TODO: account for tear off.
transient DatumRange pendingGoto= null; // after password is entered, then go to this range.
private String product; // the product
private String baseurl; // the base url
private String version; // the version, if used.
private String qcturl; // the url for quality control data.
private String pwd=null; // the location of the .pngwalk file, if used, or null.
private String vapfile=null;
public static void main(String[] args) {
DataSetURI.init(); // for FtpFileSystem implementation
System.err.println("autoplot pngwalk 20141111");
final ArgumentList alm = new ArgumentList("PngWalkTool");
alm.addOptionalSwitchArgument("nativeLAF", "n", "nativeLAF", alm.TRUE, "use the system look and feel (T or F)");
alm.addOptionalSwitchArgument( "mode", "m", "mode", "filmStrip", "initial display mode: grid, filmStrip, covers, contextFlow, etc");
alm.addOptionalSwitchArgument( "goto", "g", "goto", "", "start display at the beginning of this range, e.g. 2010-01-01" );
alm.addBooleanSwitchArgument("qualityControl", "q", "qualityControl", "enable quality control review mode");
String home= java.lang.System.getProperty( "user.home" ) + java.lang.System.getProperty( "file.separator" );
String output= "file:" + home + "pngwalk" + java.lang.System.getProperty( "file.separator" )
+ "product_$Y$m$d.png";
alm.addOptionalPositionArgument(0, "template", output, "initial template to use.");
alm.addOptionalSwitchArgument( "template", "t", "template", output, "initial template to use." );
if ( !alm.process(args) ) {
System.exit( alm.getExitCode() );
}
if (alm.getBooleanValue("nativeLAF")) {
logger.fine("nativeLAF");
try {
javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
logger.log( Level.WARNING, e.getMessage(), e );
}
}
ENABLE_QUALITY_CONTROL = alm.getBooleanValue("qualityControl");
String template = alm.getValue("template"); // One Slash!!
//final String template= "file:/home/jbf/temp/product_$Y$m$d.png" ; // One Slash!!
//final String template= "file:/net/spot3/home/jbf/fun/pics/2001minnesota/.*JPG" ;
//final String template= "file:/home/jbf/public_html/voyager/VGPW_0201/BROWSE/V1/.*.PNG";
//final String template= "file:///net/spot3/home/jbf/fun/pics/20080315_tenerife_masca_hike/IMG_.*.JPG";
//final String template= "http://www.swpc.noaa.gov/ftpdir/lists/hpi/plots/pmap_$Y_$m_$d_...._S_.*_.*_.*_.*.gif";
PngWalkTool pngWalkTool= start( template, null );
pngWalkTool.processArguments(alm);
}
/**
* returns a map containing data from the .pngwalk file.
*
* - baseurl - the location of the png images
*
- product - the base used to create file names <product>_$Y$m$d.png
*
- template - the template for files, like product_$Y$m$d.png
*
- pwd - the location of the .pngwalk file.
*
- qcturl - optional location of the quality control files, "" if not specified.
*
* @param template
* @return the map
*/
private static Map readPngwalkFile( String template ) {
URISplit split= URISplit.parse(template);
InputStream in=null;
String product= "";
String baseurl= "";
String pwd="";
String qcturl= "";
String vapfile= "";
String version= "";
try {
Properties p= new Properties();
if ( split.file==null ) {
throw new IllegalArgumentException("template does not appear to be files: "+template);
}
final File local = DataSetURI.getFile( split.resourceUri, new NullProgressMonitor() );
in= new FileInputStream( local );
p.load( in );
String vers= p.getProperty("version");
if ( vers==null || vers.trim().length()==0 ) vers=""; else vers="_"+vers;
pwd= split.path; // pwd is the location of the pngwalk file, which could be different than the template.
product= p.getProperty("product");
pwd= p.getProperty("pwd",pwd); // so that the .pngwalk file can be used out-of-context.
baseurl= p.getProperty("baseurl","."); // baseurl is needed so that pngwalks can be used out-of-context, for example when a browser downloads the file and hands it off to Autoplot.
if ( !baseurl.startsWith(".") ) {
if ( !baseurl.endsWith("/") ) {
baseurl= baseurl + "/";
}
split.path= baseurl;
} else {
split.path= checkRelativeBaseurl( baseurl, pwd, product );
}
qcturl= p.getProperty("qcturl",""); // allow the qc data to come from a different place
String t;
if ( !p.getProperty("filePattern","").equals("") ) {
// names were specified in the batch file.
t= split.path + p.getProperty("filePattern","");
} else {
t= split.path + p.getProperty("product") + "_" + p.getProperty("timeFormat") +vers + ".png";
}
template= t;
vapfile= p.getProperty("vapfile","");
version= vers;
} catch (FileSystemOfflineException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
} catch (FileNotFoundException ex) {
throw new IllegalArgumentException("File does not exist: "+template);
} catch (IOException ex) {
logger.log( Level.WARNING, ex.getMessage(), ex );
throw new RuntimeException(ex);
} finally {
try {
if ( in!=null ) in.close();
} catch ( IOException ex ) {
logger.log( Level.WARNING, ex.getMessage(), ex );
}
}
Map result= new HashMap();
result.put( "template", template );
result.put( "product", product );
result.put( "baseurl", baseurl );
result.put( "qcturl", qcturl );
result.put( "pwd", pwd );
result.put( "vapfile", vapfile );
result.put( "version", version );
return result;
}
private static void raiseApWindowSoon( final Window apWindow ) {
Runnable run= () -> {
GuiSupport.raiseApplicationWindow((JFrame)apWindow);
apWindow.toFront();
apWindow.repaint();
};
SwingUtilities.invokeLater(run);
}
/**
*
* @param baseurl -- possibly relative location, only "./" and ../dir/" supported.
* @param template
* @param product
* @return
*/
private static String checkRelativeBaseurl( String baseurl, String template, String product ) {
if ( baseurl.equals(".") ) {
URISplit split= URISplit.parse(template);
String f= split.path;
int i= f.indexOf( "/"+product+".pngwalk" );
if ( i==-1 ) {
i= f.indexOf('*');
if ( i>-1 ) i= f.lastIndexOf('/',i);
}
if ( i==-1 ) {
if ( f.endsWith("/") ) {
baseurl= f;
}
}
if ( i>-1 ) {
baseurl= f.substring(0,i+1);
}
} else if ( baseurl.startsWith("../") ) { // ../fgmjuno/
URISplit split= URISplit.parse(template);
String f= split.path;
int i= f.lastIndexOf("/",f.length()-2);
f= f.substring(0,i) + baseurl.substring(2);
i= f.indexOf( "/"+product+".pngwalk" );
if ( i==-1 ) {
i= f.indexOf('*');
if ( i>-1 ) i= f.lastIndexOf('/',i);
}
if ( i==-1 ) {
if ( f.endsWith("/") ) {
baseurl= f;
}
}
if ( i>-1 ) {
baseurl= f.substring(0,i+1);
}
}
return baseurl;
}
private void loadPngwalkFile( String file ) {
Map map= readPngwalkFile(file);
product= map.get("product");
baseurl= map.get("baseurl");
qcturl= map.get("qcturl");
pwd= map.get("pwd");
vapfile= map.get("vapfile");
version= map.get("version");
baseurl= checkRelativeBaseurl( baseurl, file, product );
boolean doStartQC=false;
if ( !"".equals(map.get("qcturl")) ) {
qcturl= checkRelativeBaseurl( qcturl, pwd, product );
doStartQC= true;
} else {
qcturl= pwd;
}
vapfile= checkRelativeBaseurl( vapfile, pwd, product );
String template= map.get("template");
this.setTemplate(template);
if ( doStartQC ) {
this.startQC();
}
boolean addToRecent=true;
if ( addToRecent ) {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
dataSetSelector1.addToRecent(file);
}
});
}
}
/**
* initialize a new PNGWalkTool with the given template.
* @param template the template, such as http://autoplot.org/data/pngwalk/product_$Y$m$d.vap
* @param parent null or a parent component to own this application.
* @return a PngWalkTool, which is visible and packed.
*/
public static PngWalkTool start( String template, final Window parent ) {
final PngWalkTool tool = new PngWalkTool();
tool.parentWindow= parent;
String sdeft= DEFAULT_BOOKMARKS;
List deft=null;
try {
deft = Bookmark.parseBookmarks(sdeft);
} catch (BookmarksException | SAXException | IOException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
}
Util.loadRecent( "pngwalkRecent", tool.dataSetSelector1, deft );
if ( template!=null ) {
URISplit split= URISplit.parse(template);
if ( split.file.endsWith(".pngwalk") ) {
tool.loadPngwalkFile( template );
} else {
tool.product= "";
tool.baseurl= "";
tool.pwd= split.path;
tool.setTemplate(template);
}
} else {
tool.product= "";
tool.baseurl= "";
}
Runnable run= () -> {
addFileEnabler(tool,parent);
};
new Thread(run).start();
JFrame frame = new JFrame("PNG Walk Tool");
frame.setIconImage( AutoplotUtil.getAutoplotIcon() );
frame.setJMenuBar( createMenuBar(tool,frame) );
AppManager.getInstance().addApplication(tool);
frame.getContentPane().add(tool);
frame.addWindowListener( AppManager.getInstance().getWindowListener(tool) );
frame.pack();
frame.setLocationRelativeTo(parent);
frame.setVisible(true);
return tool;
}
private static void addFileEnabler( final PngWalkTool tool, final Window parent ) {
PngWalkTool.ActionEnabler enabler= (String filename) -> {
String template = tool.getTemplate();
int i0 = -1;
if ( i0==-1 ) i0= template.indexOf("_$Y");
if ( i0==-1 ) i0= template.indexOf("_$o");
if ( i0==-1 ) i0= template.indexOf("_$(o,");
String productFile=null;
if ( i0==-1 ) {
try {
File file = DataSetURI.getFile( filename, new AlertNullProgressMonitor("get image file") ); // assume it's local.
String json= ImageUtil.getJSONMetadata( file );
if ( json!=null ) {
if ( i0==-1 ) i0= template.indexOf('*');
if ( i0==-1 ) i0= template.indexOf("$x");
}
productFile= tool.baseurl + tool.product + ".vap";
} catch ( IOException ex ) {
logger.log( Level.WARNING, null, ex );
}
}
try {
File file = DataSetURI.getFile( filename, new AlertNullProgressMonitor("get image file") ); // assume it's local.
String scriptURI= ImageUtil.getScriptURI(file);
if ( scriptURI!=null ) {
return true;
}
} catch (FileSystemOfflineException ex) {
Logger.getLogger(PngWalkTool.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(PngWalkTool.class.getName()).log(Level.SEVERE, null, ex);
}
if ( productFile==null && i0>-1 ) {
productFile = template.substring(0, i0) + ".vap";
}
try {
if ( productFile!=null && !WalkUtil.fileExists(productFile) ) {
if ( template.startsWith(tool.baseurl) ) {
String vv= tool.pwd + tool.product + ".vap";
if ( vv!=null ) {
if ( WalkUtil.fileExists(vv) ) {
return true;
} else {
if ( tool.version!=null && tool.version.length()>0 ) {
vv= tool.pwd + tool.product + tool.version + ".vap";
return WalkUtil.fileExists(vv);
} else {
return false;
}
}
}
}
}
return WalkUtil.fileExists(productFile);
} catch (FileSystemOfflineException | URISyntaxException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
return false;
}
};
final String lap= "View in Autoplot";
tool.addFileAction( enabler, new AbstractAction(lap) {
@Override
public void actionPerformed(ActionEvent e) {
LoggerManager.logGuiEvent(e);
String suri=null;
if ( tool.seq==null ) {
suri=null;
} else {
String s = tool.getSelectedFile();
String template = tool.getTemplate();
if ( s.startsWith("file:/") && !s.startsWith("file:///") && template.startsWith("file:///") ) {
s= "file:///"+s.substring(6);
}
DatumRange jsonTimeRange=null;
try {
File file = DataSetURI.getFile( s, new AlertNullProgressMonitor("get image file") ); // assume it's local.
String json= ImageUtil.getJSONMetadata( file );
if ( json!=null ) {
jsonTimeRange= RichPngUtil.getXRange(json);
}
} catch ( IOException ex ) {
logger.log( Level.WARNING, null, ex );
}
// look in script and offer to run the script.
try {
File file = DataSetURI.getFile( s, new AlertNullProgressMonitor("get image file") ); // assume it's local.
String scriptURI= ImageUtil.getScriptURI( file );
if ( scriptURI!=null ) {
suri= scriptURI;
}
} catch ( IOException ex ) {
logger.log( Level.WARNING, null, ex );
}
int i= template.indexOf('$');
if ( i!=-1 ) { // do a little testing
int i2= i+1;
if ( i2==template.length() ) {
throw new IllegalArgumentException("template must start with $Y, $y or $(o,...)");
}
char c= template.charAt(i2);
while ( i20 && tool.baseurl.length()>1 ) {
productFile = tool.baseurl + tool.product + ".vap"; //HERE IT IS
} else {
productFile = template.substring(0, i0) + ".vap";
}
try {
if ( !WalkUtil.fileExists(productFile) ) {
String productFile2 = tool.pwd + tool.product + ".vap";
if ( WalkUtil.fileExists(productFile2) ) {
productFile= productFile2;
} else {
productFile2 = tool.pwd + tool.product + tool.version + ".vap";
if ( WalkUtil.fileExists(productFile2) ) {
productFile= productFile2;
}
}
}
} catch (FileSystemOfflineException | URISyntaxException ex) {
logger.log(Level.SEVERE, null, ex);
}
if ( timeRange!=null ) {
suri = productFile + "?timeRange=" + timeRange;
} else {
JOptionPane.showMessageDialog(ScriptContext.getViewWindow(), "unable to resolve time range from image metadata or filename.");
return;
}
}
}
final String fsuri= suri;
Runnable run = () -> {
ScriptContext.createGui();
Window apWindow= ScriptContext.getViewWindow();
if ( fsuri!=null ) {
raiseApWindowSoon(apWindow);
if ( fsuri.startsWith("script:") ) {
ScriptContext.getApplication().runScriptTools(fsuri);
return;
} else {
ScriptContext.plot(fsuri);
}
}
// go through and check for the axis autorange flag, and autorange if necessary.
Application dom= ScriptContext.getDocumentModel();
for ( int i=0; iUnexpected error when downloading file
" + ssrc+"
"+ex.toString() );
return;
}
try {
ImageSelection iss= new ImageSelection();
iss.setImage( ImageIO.read( src ) );
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents( iss, ImageSelection.getNullClipboardOwner() );
} catch (IOException ex) {
JOptionPane.showMessageDialog( parent, "Unable to read image
"+ex.getMessage() );
}
}
/**
* Write a pngwalk file describing the current pngwalk.
* http://autoplot.org/PNG_Walks
* @param parent
* @param ssrc the focus file
* @throws java.io.IOException
*/
protected static void savePngwalkFile( PngWalkTool parent, String ssrc ) throws IOException {
Preferences prefs = AutoplotSettings.settings().getPreferences(PngWalkTool.class);
String srecent = prefs.get( PngWalkTool.PREF_RECENT, System.getProperty("user.home") );
JFileChooser chooser= new JFileChooser( srecent );
chooser.setFileFilter( new FileNameExtensionFilter( "pngwalk files", "pngwalk" ) );
chooser.setMultiSelectionEnabled(false);
if ( JFileChooser.APPROVE_OPTION==chooser.showSaveDialog(parent) ) {
File f= chooser.getSelectedFile();
if ( !f.getName().endsWith(".pngwalk") ) {
f= new File( f.getAbsolutePath() + ".pngwalk" );
}
prefs.put( PngWalkTool.PREF_RECENT, f.getAbsolutePath() );
try ( PrintWriter w= new PrintWriter(f) ) {
if ( parent.baseurl.length()==0 ) {
String t= parent.getTemplate();
int i= WalkUtil.splitIndex( t );
w.println( "baseurl="+t.substring(0,i+1));
String filePattern= t.substring(i+1);
w.println( "filePattern="+filePattern);
} else {
w.println( "baseurl="+parent.baseurl);
if ( parent.product!=null ) {
w.println( "product="+parent.product);
}
String s= parent.getTemplate();
if ( parent.product!=null && s.endsWith(".png") ) {
s= s.substring(0,s.length()-4);
}
w.println( "timeFormat="+s );
}
String pwd= chooser.getSelectedFile().getParent();
w.println( "pwd="+ pwd );
if ( isQualityControlEnabled() ) {
if ( parent.getQCTUrl()!=null ) {
w.println( "qcturl="+parent.getQCTUrl() );
} else {
w.println( "qcturl="+"file://"+pwd );
}
}
}
parent.setStatus(".pngwalk file saved: "+f);
}
}
/**
* save a copy of the current selection to a local disk.
* @param parent dialog parent
* @param ssrc the file
*/
protected static void saveLocalCopy( Component parent, String ssrc ) {
Preferences prefs = AutoplotSettings.settings().getPreferences(PngWalkTool.class);
String srecent = prefs.get( PngWalkTool.PREF_RECENT, System.getProperty("user.home") );
if ( ssrc==null ) {
JOptionPane.showMessageDialog( parent, "No image is selected." );
return;
}
File src;
try {
src = FileSystemUtil.doDownload(ssrc, new NullProgressMonitor()); // should be local
} catch (IOException | URISyntaxException ex) {
JOptionPane.showMessageDialog( parent, "Unexpected error when downloading file
" + ssrc+"
"+ex.toString() );
return;
}
JFileChooser chooser= new JFileChooser( srecent );
JPanel accessoryPanel= new JPanel();
accessoryPanel.setLayout( new BoxLayout(accessoryPanel,BoxLayout.Y_AXIS) );
JCheckBox r60= new JCheckBox( "Reduce to 60%" );
accessoryPanel.add(r60);
chooser.setMultiSelectionEnabled(false);
chooser.setAccessory(accessoryPanel);
chooser.setSelectedFile( new File( chooser.getCurrentDirectory(), src.getName() ) );
int r= chooser.showSaveDialog(parent);
if ( r==JFileChooser.APPROVE_OPTION ) {
prefs.put( PngWalkTool.PREF_RECENT, chooser.getSelectedFile().getParent() );
try {
if ( !src.exists() ) throw new IllegalArgumentException("Image file no longer exists: "+src);
if ( r60.isSelected() ) {
BufferedImage im= ImageIO.read(src);
int size= (int)Math.sqrt( im.getWidth()*im.getWidth() + im.getHeight()*im.getHeight() );
im= ImageResize.getScaledInstance( im, size*60/100 );
String ext= chooser.getSelectedFile().toString();
int i= ext.lastIndexOf('.');
ext= ext.substring(i+1);
ImageIO.write( im, ext, chooser.getSelectedFile() );
} else {
if ( ! org.autoplot.Util.copyFile( src, chooser.getSelectedFile()) ) {
JOptionPane.showMessageDialog( parent, "Unable to save image to:
" + chooser.getSelectedFile() );
}
}
} catch (IOException ex) {
JOptionPane.showMessageDialog( parent, "Unable to save image to:
" + chooser.getSelectedFile()+"
"+ex.toString() );
}
}
}
private Window parentWindow;
private List qcFilterMenuItems= new ArrayList<>();
/**
* return the interval size (up/down)
* @return
*/
int nextInterval( int index ) {
Component c= tabs.getSelectedComponent();
if ( c instanceof PngWalkView ) {
PngWalkView v= (PngWalkView)c;
return v.getNextInterval( index );
} else if ( c instanceof JSplitPane ) {
c= ((JSplitPane)c).getTopComponent();
if ( c instanceof PngWalkView ) {
PngWalkView v= (PngWalkView)c;
return v.getNextInterval( index );
} else {
return index+7;
}
} else {
return index+7;
}
}
/**
* return the page size (page up/down)
* @return
*/
int nextPage( int index) {
Component c= tabs.getSelectedComponent();
if ( c instanceof PngWalkView ) {
PngWalkView v= (PngWalkView)c;
return v.getNextPage( index );
} else if ( c instanceof JSplitPane ) {
c= ((JSplitPane)c).getTopComponent();
if ( c instanceof PngWalkView ) {
PngWalkView v= (PngWalkView)c;
return v.getNextPage( index );
} else {
return index+7;
}
} else {
return index+28;
}
}
/**
* return the interval size (up/down)
* @return
*/
int prevInterval( int index ) {
Component c= tabs.getSelectedComponent();
if ( c instanceof PngWalkView ) {
PngWalkView v= (PngWalkView)c;
return v.getPrevInterval( index );
} else if ( c instanceof JSplitPane ) {
c= ((JSplitPane)c).getTopComponent();
if ( c instanceof PngWalkView ) {
PngWalkView v= (PngWalkView)c;
return v.getPrevInterval( index );
} else {
return index+7;
}
} else {
return index-7;
}
}
/**
* return the page size (page up/down)
* @return
*/
int prevPage( int index) {
Component c= tabs.getSelectedComponent();
if ( c instanceof PngWalkView ) {
PngWalkView v= (PngWalkView)c;
return v.getPrevPage( index );
} else if ( c instanceof JSplitPane ) {
c= ((JSplitPane)c).getTopComponent();
if ( c instanceof PngWalkView ) {
PngWalkView v= (PngWalkView)c;
return v.getPrevPage( index );
} else {
return index+7;
}
} else {
return index-28;
}
}
private static JMenuBar createMenuBar( final PngWalkTool tool, final JFrame frame ) {
JMenuBar result= new JMenuBar();
JMenu fileMenu= new JMenu("File");
fileMenu.add( new AbstractAction( "Save Local Copy of Image..." ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
saveLocalCopy(tool,tool.getSelectedFile());
}
} );
fileMenu.add( new AbstractAction( "Save .pngwalk File..." ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
try {
savePngwalkFile(tool,tool.getSelectedFile());
} catch ( IOException ex ) {
throw new RuntimeException(ex);
}
}
} );
fileMenu.add( new AbstractAction( "Show Autoplot" ) {
@Override
public void actionPerformed(ActionEvent ae) {
LoggerManager.logGuiEvent(ae);
AppManager appman= AppManager.getInstance();
for ( int i=0; i< appman.getApplicationCount(); i++ ) {
if ( appman.getApplication(i) instanceof AutoplotUI ) {
AutoplotUI.raiseApplicationWindow((AutoplotUI)appman.getApplication(i));
return;
}
}
if ( AppManager.getInstance().getApplicationCount()==1 ) {
ScriptContext.createGui();
Window apWindow= ScriptContext.getViewWindow();
raiseApWindowSoon(apWindow);
}
}
} );
fileMenu.add( new AbstractAction( "Close" ) {
@Override
public void actionPerformed(ActionEvent e) {
LoggerManager.logGuiEvent(e);
if ( AppManager.getInstance().getApplicationCount()==1 ) {
if ( JOptionPane.OK_OPTION==
JOptionPane.showConfirmDialog( tool,
"Quit application?", "Quit PNG Walk", JOptionPane.OK_CANCEL_OPTION ) ) {
if ( AppManager.getInstance().closeApplication(tool) ) {
frame.dispose();
}
}
} else {
if ( AppManager.getInstance().closeApplication(tool) ) {
frame.dispose();
}
}
}
} );
fileMenu.add( new AbstractAction( "Quit" ) {
@Override
public void actionPerformed(ActionEvent e) {
LoggerManager.logGuiEvent(e);
if ( AppManager.getInstance().requestQuit() ) {
frame.dispose();
AppManager.getInstance().quit();
}
}
} );
result.add(fileMenu);
JMenu navMenu= new JMenu("Navigate");
navMenu.add( new AbstractAction( "Go To Date..." ) {
@Override
public void actionPerformed(ActionEvent e) {
LoggerManager.logGuiEvent(e);
DatumRange dr= tool.seq.getTimeSpan();
String str;
if ( dr!=null ) {
str= JOptionPane.showInputDialog(tool,"Select date to display", TimeDatumFormatter.DAYS.format( TimeUtil.prevMidnight( dr.min() ) ) );
} else {
JOptionPane.showMessageDialog( tool, "File times are not available" );
return;
}
if ( str!=null ) {
try {
DatumRange ds = DatumRangeUtil.parseTimeRange(str);
tool.seq.gotoSubrange(ds);
} catch (ParseException ex) {
try {
double d= Units.us2000.parse(str).doubleValue(Units.us2000);
tool.seq.gotoSubrange( DatumRange.newDatumRange( d, d, Units.us2000 ));
} catch (ParseException ex2) {
JOptionPane.showMessageDialog( tool, "parse error: "+ex2 );
}
} catch (RuntimeException ex ) {
tool.setStatus( "warning: "+ex.toString() );
}
}
}
} );
navMenu.add( new AbstractAction( "First" ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
tool.seq.setIndex( 0 );
}
} ).setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_HOME, 0 ));
navMenu.add( new AbstractAction( "Previous Page" ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
tool.seq.setIndex( tool.prevPage(tool.seq.getIndex()) );
}
} ).setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_PAGE_UP, 0 ));
navMenu.add( new AbstractAction( "Previous Interval" ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
tool.seq.setIndex( tool.prevInterval(tool.seq.getIndex()) );
}
} ).setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_UP, 0 ));
navMenu.add( new AbstractAction( "Previous Item" ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
tool.seq.skipBy( -1 );
}
} ).setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_LEFT, 0 ));
navMenu.add( new AbstractAction( "Next Item" ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
tool.seq.skipBy( 1 );
}
} ).setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_RIGHT, 0 ));
navMenu.add( new AbstractAction( "Next Interval" ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
tool.seq.setIndex(tool.nextInterval(tool.seq.getIndex()) );
}
} ).setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, 0 ));
navMenu.add( new AbstractAction( "Next Page" ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
tool.seq.setIndex( tool.nextPage(tool.seq.getIndex()) );
}
} ).setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_PAGE_DOWN, 0 ));
navMenu.add( new AbstractAction( "Last" ) {
@Override
public void actionPerformed( ActionEvent e ) {
LoggerManager.logGuiEvent(e);
tool.seq.setIndex( tool.seq.size()-1 );
}
} ).setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_END, 0 ));
result.add( navMenu );
tool.navMenu= navMenu;
navMenu.setEnabled(tool.seq!=null);
final JMenu optionsMenu= new JMenu( "Options" );
final JCheckBoxMenuItem persMi= new JCheckBoxMenuItem("Use Perspective",true);
persMi.addActionListener((ActionEvent e) -> {
((CoversWalkView)tool.views[4]).setPerspective(persMi.isSelected());
});
optionsMenu.add(persMi);
ButtonGroup buttonGroup1 = new javax.swing.ButtonGroup();
final JMenu thumbsizeMenu= new JMenu("Thumbnail Size" );
final int[] sizes= new int[] { 50, 100, 200, 400 };
for ( int i=0; i {
man.updateBookmarks( bookmarksMenu, tool.getSelector() );
});
man.setVisible(false);
man.setPrefNode("pngwalk","autoplot.default.pngwalk.bookmarks", "http://autoplot.org/data/pngwalk.demos.xml");
man.updateBookmarks( bookmarksMenu, tool.getSelector() );
result.add( bookmarksMenu );
final JMenu helpMenu= new JMenu( "Help" );
final JMenuItem helpContentsMI= new JMenuItem( new AbstractAction( "Help Contents..." ) {
@Override
public void actionPerformed(ActionEvent e) {
LoggerManager.logGuiEvent(e);
String surl = "http://autoplot.org/PNGWalks";
AutoplotUtil.openBrowser(surl);
}
});
helpContentsMI.setToolTipText("Help page for the PNG Walk Tool.");
helpMenu.add( helpContentsMI );
result.add( helpMenu );
return result;
}
/** Creates new form PngWalkTool */
public PngWalkTool() {
initComponents();
setNavButtonsEnabled(false);
dataSetSelector1.setEnableDataSource(false);
dataSetSelector1.setAcceptPattern("(?i).*(\\.gif|\\.png|\\.jpg|\\.pngwalk)");
dataSetSelector1.setSuggestFiles(false); // only aggs.
dataSetSelector1.addSuggestFile(".*\\.pngwalk");
dataSetSelector1.registerActionTrigger( ".*\\.pngwalk", new AbstractAction("pngwalk") {
@Override
public void actionPerformed( ActionEvent ev ) {
String template= dataSetSelector1.getValue();
if ( template.endsWith(".pngwalk") ) {
loadPngwalkFile( template );
} else {
setTemplate(template);
}
}
});
views= new PngWalkView[7];
views[0]= new GridPngWalkView( null );
views[1]= new RowPngWalkView( null );
views[2]= new SinglePngWalkView( null, this );
views[3]= new SinglePngWalkView( null, this );
views[4]= new CoversWalkView( null );
views[5]= new SinglePngWalkView( null, this );
views[6]= new ContextFlowView(null);
final int SCROLLBAR_HEIGHT = (int) Math.round( new JScrollPane().getHorizontalScrollBar().getPreferredSize().getHeight() );
final JSplitPane filmStripSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, views[1], views[2] );
//p.setEnabled(false); //prevents user manipulation
filmStripSplitPane.setDividerLocation(getThumbnailSize()+ (int)(1.2 *SCROLLBAR_HEIGHT));
views[1].addPropertyChangeListener(PngWalkView.PROP_THUMBNAILSIZE, (PropertyChangeEvent evt) -> {
filmStripSplitPane.setDividerLocation( (Integer)evt.getNewValue() + SCROLLBAR_HEIGHT );
});
filmStripSplitPane.setMinimumSize( new Dimension(640,480) );
filmStripSplitPane.setPreferredSize( new Dimension(640,480) );
final JSplitPane coversSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, views[4], views[5] );
coversSplitPane.setDividerLocation(getThumbnailSize()+ (int)(1.2 *SCROLLBAR_HEIGHT));
views[4].addPropertyChangeListener(PngWalkView.PROP_THUMBNAILSIZE, (PropertyChangeEvent evt) -> {
coversSplitPane.setDividerLocation( (Integer)evt.getNewValue() + SCROLLBAR_HEIGHT );
});
coversSplitPane.setMinimumSize( new Dimension(640,480) );
coversSplitPane.setPreferredSize( new Dimension(640,480) );
tabs= new TearoffTabbedPane();
tabs.addTab( "Single", new JScrollPane( views[3] ) );
tabs.addTab( "ContextFlow", views[6] );
tabs.addTab( "Grid", views[0] );
tabs.addTab( "Film Strip", filmStripSplitPane );
tabs.addTab( "Covers", coversSplitPane );
tabs.setSelectedIndex(3);
// add listener to jump to and from the single image view.
for (PngWalkView view : views) {
view.getMouseTarget().addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if ( e.getClickCount()==2 && digitizer==null ) {
int oldIndex= tabs.getSelectedIndex();
if ( oldIndex==0 ) {
tabs.setSelectedIndex( returnTabIndex );
} else {
tabs.setSelectedIndex(0);
returnTabIndex= oldIndex;
}
}
}
});
}
tabs.setFocusable(false);
nextButton.requestFocus();
if (isQualityControlEnabled()) {
qcPanel = new QualityControlPanel(this);
JSplitPane qcPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, tabs, qcPanel);
qcPane.setResizeWeight(1.0);
pngsPanel.add(qcPane);
qcPanel.setWalkImageSequence(seq);
} else {
pngsPanel.add( tabs );
}
pngsPanel.revalidate();
BindingGroup bc= new BindingGroup();
for (PngWalkView view : views) {
Binding b = Bindings.createAutoBinding(UpdateStrategy.READ_WRITE, view, BeanProperty.create("thumbnailSize"), this, BeanProperty.create("thumbnailSize"));
bc.addBinding( b );
}
bc.bind();
addMouseWheelListener( (MouseWheelEvent e) -> {
if ( seq!=null && seq.size()!=0 ) seq.skipBy(e.getWheelRotation());
});
setStatus("ready");
}
private void processArguments( ArgumentList alm ) {
String tab= alm.getValue("mode");
if ( tab.equalsIgnoreCase("filmStrip" ) ) {
tabs.setSelectedIndex(3);
} else if ( tab.equalsIgnoreCase("single" ) ) {
tabs.setSelectedIndex(0);
} else if ( tab.equalsIgnoreCase("contextFlow") ) {
tabs.setSelectedIndex(1);
} else if ( tab.equalsIgnoreCase("grid" ) ) {
tabs.setSelectedIndex(2);
} else if ( tab.equalsIgnoreCase("film strip" ) ) {
tabs.setSelectedIndex(3);
} else if ( tab.equalsIgnoreCase("covers" ) ) {
tabs.setSelectedIndex(4);
}
String show= alm.getValue("goto");
if ( !show.equals("") && seq!=null ) {
try {
if ( seq.getTimeSpan()!=null ) {
seq.gotoSubrange(DatumRangeUtil.parseTimeRange(show));
} else {
this.pendingGoto= DatumRangeUtil.parseTimeRange(show);
}
} catch ( ParseException ex ) {
throw new RuntimeException(ex);
}
} else {
logger.fine("show was empty or seq was null");
}
}
/**
* respond to changes of the current index.
*/
private transient PropertyChangeListener indexListener= new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ( seq==null ) {
logger.fine("seq was null");
return;
}
String item= DataSetURI.fromUri(seq.currentImage().getUri());
for ( int i=0; i {
setStatus((String)evt.getNewValue());
};
/**
*
*/
private final transient PropertyChangeListener qcStatusListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ( seq==null ) {
logger.fine("seq was null");
return;
}
if ( seq.getQualityControlSequence()!=null ) {
int n[] = seq.getQualityControlSequence().getQCTotals();
qcPanel.setStatus(n[0], n[1], n[2], n[3]);
}
}
};
private void setNavButtonsEnabled( boolean enabled ) {
jumpToFirstButton.setEnabled(enabled);
jumpToLastButton.setEnabled(enabled);
prevButton.setEnabled(enabled);
nextButton.setEnabled(enabled);
nextSetButton.setEnabled(enabled);
prevSetButton.setEnabled(enabled);
}
/**
* set the template which the PNGWalk Tool will display.
* @param template file template, like /tmp/$Y$m$d.png
*/
public void setTemplate( String template ) {
if ( template.contains("%") && !template.contains("$") ) {
template= template.replaceAll("\\%","\\$");
if ( template.contains("{") && !template.contains("(") ) {
template= template.replaceAll("\\{","(");
template= template.replaceAll("\\}",")");
}
}
URISplit split= URISplit.parse(template);
Map params= URISplit.parseParams(split.params);
dataSetSelector1.setValue(template);
WalkImageSequence oldseq= this.seq;
URI uri= DataSetURI.getResourceURI(template);
if ( uri==null ) {
throw new IllegalArgumentException("Unable to parse: "+template);
}
String surl= DataSetURI.fromUri( uri );
try {
seq= new WalkImageSequence( surl );
String tr= params.get("timerange");
if ( tr!=null ) {
try {
DatumRange trdr;
trdr= DatumRangeUtil.parseTimeRange(tr);
seq.setTimerange( trdr );
} catch ( ParseException ex ) {
setMessage( ERROR_ICON, "unable to parse timerange" );
}
}
setNavButtonsEnabled(true);
if ( navMenu!=null ) navMenu.setEnabled(true);
seq.setQCFilter("");
if ( qcFilterMenuItems!=null ) {
for ( AbstractButton b: qcFilterMenuItems ) {
b.setEnabled( qcPanel!=null );
}
}
if ( qcPanel!=null ) {
qcPanel.setWalkImageSequence(seq);
}
} catch ( Exception ex ) {
seq= null;
setNavButtonsEnabled(false);
if ( navMenu!=null ) navMenu.setEnabled(false);
logger.log( Level.WARNING, ex.getMessage(), ex );
}
if ( oldseq!=null ) {
oldseq.removePropertyChangeListener(WalkImageSequence.PROP_INDEX, indexListener );
oldseq.removePropertyChangeListener(WalkImageSequence.PROP_STATUS, statusListener);
if (ENABLE_QUALITY_CONTROL) oldseq.removePropertyChangeListener(WalkImageSequence.PROP_BADGE_CHANGE, qcStatusListener);
oldseq.removePropertyChangeListener( PROP_TIMERANGE, seqTimeRangeListener );
oldseq.removePropertyChangeListener( WalkImageSequence.PROP_INDEX, seqIndexListener );
}
if ( seq!=null ) {
seq.addPropertyChangeListener( WalkImageSequence.PROP_INDEX, indexListener );
seq.addPropertyChangeListener( WalkImageSequence.PROP_STATUS, statusListener );
if (ENABLE_QUALITY_CONTROL) seq.addPropertyChangeListener(WalkImageSequence.PROP_BADGE_CHANGE, qcStatusListener);
seq.addPropertyChangeListener( PROP_TIMERANGE, seqTimeRangeListener );
seq.addPropertyChangeListener( WalkImageSequence.PROP_INDEX, seqIndexListener );
}
if ( template.length()==0 ) {
setStatus("Enter the location of a pngwalk file by providing a template for the files, such as /tmp/$Y$m$d.png");
return;
}
Runnable run= () -> {
try {
seq.initialLoad();
if ( pendingGoto!=null ) {
seq.gotoSubrange(pendingGoto);
pendingGoto= null;
}
} catch (java.io.IOException e) {
// This probably means the template was invalid. Don't set new sequence.
if ( !getStatus().startsWith("error") ) setStatus("error:"+e.getMessage());
Container p= SwingUtilities.getWindowAncestor(this);
if ( p==null ) p= parentWindow;
if ( this.getX()!=0 ) p= this; // for Linux, where the component isn't initialized yet.
JOptionPane.showMessageDialog( p, "Unable to find directory for:
"+ seq.getTemplate() );
return;
}
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
updateInitialGui();
}
});
};
new Thread(run).start();
}
/**
* initial settings to be performed on the event thread.
*/
private void updateInitialGui() {
dataSetSelector1.addToRecent( seq.getTemplate() );
useRangeCheckBox.setEnabled(seq.getTimeSpan() != null);
// always clear subrange on new sequence
useRangeCheckBox.setSelected(false);
editRangeButton.setEnabled(false);
timeFilterTextField.setEnabled(false);
timeFilterTextField.setText("");
if ( seq.size()==0 ) {
Container p= SwingUtilities.getWindowAncestor(this);
if ( p==null ) p= parentWindow;
if ( this.getX()!=0 ) p= this; // for Linux, where the component isn't initialized yet.
JOptionPane.showMessageDialog( p, "Unable to find any images in sequence:
"+ seq.getTemplate() );
return;
}
DatumRange tr=seq.currentImage().getDatumRange();
if ( tr!=null ) setTimeRange( tr );
showMissingCheckBox.setEnabled(seq.getTimeSpan() != null);
if (seq.getTimeSpan() == null) {
//Can't identify missing images if there's no date info in template
showMissingCheckBox.setEnabled(false);
showMissingCheckBox.setSelected(false);
} else {
seq.setShowMissing(showMissingCheckBox.isSelected());
}
for (PngWalkView v : views) {
v.setSequence(seq);
}
if ( seq.size()==0 ) {
setStatus("warning: no files found in "+seq.getTemplate() );
} else {
indexListener.propertyChange( null );
if (qcPanel != null ) {
qcPanel.setWalkImageSequence(seq);
if ( seq.getIndex() {
setTimeRange((DatumRange)evt.getNewValue());
};
boolean setting= false;
transient PropertyChangeListener seqIndexListener= new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
boolean setting0= setting;
setting= true;
DatumRange dr= seq.currentImage().getDatumRange();
if ( setting0 && dr!=null ) setTimeRange( dr );
setting= false;
}
};
transient protected String status = "initializing...";
public static final String PROP_STATUS = "status";
public String getStatus() {
return status;
}
public void setStatus(String message) {
String oldStatus = this.status;
this.status = message;
if ( message.startsWith("busy:" ) ) {
setMessage( BUSY_ICON, message.substring(5).trim() );
logger.finer(message);
} else if ( message.startsWith("warning:" ) ) {
setMessage( WARNING_ICON, message.substring(8).trim() );
logger.warning(message);
} else if ( message.startsWith("error:" ) ) {
setMessage( ERROR_ICON, message.substring(6).trim() );
logger.severe(message);
} else {
logger.fine(message);
setMessage(message);
}
firePropertyChange(PROP_STATUS, oldStatus, message);
}
public void setMessage(String message) {
setMessage( IDLE_ICON, message );
}
public void setMessage( Icon icon, String message ) {
if ( message==null ) message= ""; // TODO: fix this later
String myMess= message;
if ( myMess.length()>100 ) myMess= myMess.substring(0,100)+"...";
String fMyMessag= myMess;
String fMessage= message;
Runnable run= () -> {
statusLabel.setIcon( icon );
statusLabel.setText(fMyMessag);
statusLabel.setToolTipText(fMessage);
};
SwingUtilities.invokeLater(run);
}
/**
* start the quality control if it is not started already.
*/
public void startQC() {
if ( !isQualityControlEnabled() ) {
qcPanel= new QualityControlPanel(this);
tabs.add( "Quality Control", qcPanel );
if ( seq!=null ) {
qcPanel.setWalkImageSequence(seq);
seq.addPropertyChangeListener(WalkImageSequence.PROP_BADGE_CHANGE, qcStatusListener);
}
ENABLE_QUALITY_CONTROL= true;
}
for ( AbstractButton b: qcFilterMenuItems ) {
b.setEnabled(true);
}
}
protected DataPointRecorder digitizer= null;
protected boolean digitizerRecording= true;
protected char annoTypeChar= '|';
/**
* start the digitizer if it is not started already.
*/
public void startDigitizer() {
if ( digitizer==null ) {
digitizer= new DataPointRecorder();
digitizer.addDataSetUpdateListener((DataSetUpdateEvent e) -> {
for (PngWalkView v : views) {
if ( v instanceof SinglePngWalkView ) {
v.repaint();
}
}
});
digitizer.addDataPointSelectionListener((DataPointSelectionEvent e) -> {
String image= (e.getPlane("image").toString());
int i= seq.findIndex(image);
if ( i>-1 ) {
seq.setIndex(i);
}
});
tabs.add( "Digitizer" , digitizer );
for (PngWalkView v : views) {
if ( v instanceof SinglePngWalkView ) {
((SinglePngWalkView)v).clickDigitizer.setViewer(this);
}
}
JComboBox annoType= new JComboBox( new String[] { "| vertical line", "+ cross hairs", ". dots" } );
digitizer.addAccessory( annoType );
annoType.addItemListener((ItemEvent e) -> {
annoTypeChar= e.getItem().toString().charAt(0);
for (PngWalkView v : views) {
if ( v instanceof SinglePngWalkView ) {
v.repaint();
}
}
});
digitizerRecording= true;
}
}
private boolean isDigitizerEnabled() {
return digitizer!=null;
}
/**
* provide access to the digitizer DataPointRecorder, so that points
* can be deleted programmatically.
* @return the DataPointRecorder.
*/
public DataPointRecorder getDigitizerDataPointRecorder() {
return digitizer;
}
/**
* this can be used to disable recording of the points.
* @param enable true means record points, false means don't record.
*/
public void setDigitizerRecording( boolean enable ) {
this.digitizerRecording= enable;
}
private void writeContactSheet() {
Component ttt= tabs.getTabByTitle("Grid");
if ( ttt instanceof GridPngWalkView ) {
try {
Preferences prefs= Preferences.userNodeForPackage(PngWalkTool.class);
String fname= prefs.get( "writeToContactSheet", "/tmp/contactSheet.png" );
JFileChooser chooser= new JFileChooser(fname);
if ( !fname.equals("/tmp/contactSheet.png") ) {
chooser.setSelectedFile( new File(fname) );
}
chooser.setFileFilter( new FileNameExtensionFilter( "PNG Files", "png") );
if ( chooser.showSaveDialog(this)==JFileChooser.APPROVE_OPTION ) {
File f= chooser.getSelectedFile();
if ( !f.getName().endsWith(".png") ) {
f= new File( f.getParentFile(), f.getName()+".png" );
}
writeContactSheet( f );
prefs.put( "writeToContactSheet", f.toString() );
}
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
JOptionPane.showMessageDialog( parentWindow, "Error while creating contact sheet");
}
}
}
/**
* write the current Grid view to a single PNG file.
* @param f
* @throws IOException
*/
public void writeContactSheet( File f ) throws IOException {
Component ttt= tabs.getTabByTitle("Grid");
if ( ttt instanceof GridPngWalkView ) {
BufferedImage im= ((GridPngWalkView)ttt).paintContactSheet();
ImageIO.write( im, "png", f );
setMessage("Wrote to "+f);
}
}
public static interface ActionEnabler {
boolean isActionEnabled( String filename );
}
/**
* Enabler that returns true for local files.
*/
public static final ActionEnabler LOCAL_FILE_ENABLER =
(String filename) -> DataSetURI.getResourceURI(filename).toString().startsWith("file:" );
transient List actionEnablers= new ArrayList<>();
List actionButtons= new ArrayList<>();
/**
* Add a file action button to the GUI.
* @param match returns true when the action can be applied to the current image.
* @param abstractAction the action.
* @see #LOCAL_FILE_ENABLER which returns true for local files.
*/
public void addFileAction( ActionEnabler match, Action abstractAction ) {
this.actionEnablers.add( match );
JButton b= new JButton( abstractAction );
this.actionButtons.add( b );
actionButtonsPanel.add( b );
this.revalidate();
}
/**
* add a component that will get property change events and should respond
* to property changes. This allows scientists a way to connect actions to
* the PNGWalk tool.
* @param c null or a smallish JComponent that should be about the size of a button.
* @param p null or the listener for the selected file and timerange.
*/
public void addActionComponent( JComponent c, PropertyChangeListener p ) {
if ( c!=null ) actionButtonsPanel.add(c);
if ( p!=null ) {
this.addPropertyChangeListener( PROP_SELECTED_NAME, p );
this.addPropertyChangeListener( PROP_TIMERANGE, p );
this.addPropertyChangeListener( PROP_MOUSEPRESSLOCATION, p );
this.addPropertyChangeListener( PROP_MOUSERELEASELOCATION, p );
}
this.revalidate();
}
public void removeActionComponent( JComponent c, PropertyChangeListener p ) {
if ( c!=null ) actionButtonsPanel.remove(c);
if ( p!=null ) {
this.removePropertyChangeListener( PROP_SELECTED_NAME, p );
this.removePropertyChangeListener( PROP_TIMERANGE, p );
this.removePropertyChangeListener( PROP_MOUSEPRESSLOCATION, p );
this.removePropertyChangeListener( PROP_MOUSERELEASELOCATION, p );
}
this.revalidate();
}
List decorators= new LinkedList<>();
/**
* add a decorator to the PngWalkTool, which is drawn on single-image
* views. Note this is draw in the coordinate system of the image, pixel
* coordinates with the origin (0,0) at the top left.
* @param p
*/
public void addTopDecorator( Painter p ) {
if ( !decorators.contains(p) ) {
decorators.add( p );
}
repaint();
}
/**
* remove a decorator to the PngWalkTool, which is drawn on single-image
* views. If the decorator is not found, no error is thrown.
* @param p
*/
public void removeTopDecorator( Painter p ) {
decorators.remove( p );
repaint();
}
/**
* remove all decorators from the PngWalkTool.
*/
public void removeTopDecorators() {
decorators.clear( );
repaint();
}
/**
* returns true if there are any top decorators.
* @return true if there are any decorators.
*/
public boolean hasTopDecorators() {
return ! decorators.isEmpty();
}
/**
* set a new component for the bottom left panel, where by default the
* navigation panel resides.
* @param c
*/
public void setBottomLeftPanel( JComponent c ) {
bottomLeftPanel.removeAll();
if ( c!=null ) bottomLeftPanel.add( c, BorderLayout.CENTER );
revalidate();
}
/**
* remove all components from the bottom left panel.
*/
public void clearBottomLeftPanel() {
bottomLeftPanel.removeAll();
}
/**
* get a reference to the navigation panel. To restore the normal layout,
* use setBottomLeftPanel( getNavigationPanel() ).
* @return the navigation panel.
*/
public JPanel getNavigationPanel() {
return navigationPanel;
}
/**
* returns the current selection, which may be a URL on a remote site, or null if no sequence has been selected.
* @return the current selection or null if the sequence is not loaded or empty.
*/
public String getSelectedFile() {
if ( seq==null ) return null;
if ( seq.size()==0 ) return null;
return DataSetURI.fromUri( seq.currentImage().getUri() );
}
public static final String PROP_SELECTED_NAME = "selectedName";
/**
* return the name of the current selection, which is just the globbed or aggregated part of the names.
* This is introduced to support tying two pngwalks together.
* @return the name of the currently selected file.
*/
public String getSelectedName() {
if ( seq.size()>0 ) {
return seq.getSelectedName();
} else {
return "";
}
}
/**
* set the name of the file to select, which is just the globber or aggregated part of the name. For example,
* if getTemplate is file:/tmp/$Y$m$d.gif, then the setSelectedName might be 20141111.gif. If the name is not found in the
* pngwalk, then this has no effect.
* @param name the new name
*/
public void setSelectedName(String name) {
String oldName= getSelectedName();
int i= seq.findIndex( name );
if ( i!=-1 ) {
seq.setIndex(i);
}
firePropertyChange( PROP_SELECTED_NAME, oldName, name );
}
/**
* return the currently selected image.
* @return the currently selected image
*/
public BufferedImage getSelectedImage() {
return seq.currentImage().getImage();
}
DataSetSelector getSelector() {
return this.dataSetSelector1;
}
/**
* return true of the quality control panel is enabled.
* @return return true of the quality control panel is enabled.
*/
public static boolean isQualityControlEnabled() {
return ENABLE_QUALITY_CONTROL;
}
/**
* provide a method for setting the QCStatus externally.
* @param text message annotating the status change or commenting on status.
* @param status the status
*/
public void setQCStatus( String text, QualityControlRecord.Status status ) {
if ( this.qcPanel==null ) {
throw new IllegalArgumentException("QC Panel must be started");
}
this.qcPanel.setStatus(text, status);
this.repaint();
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// //GEN-BEGIN:initComponents
private void initComponents() {
pngsPanel = new javax.swing.JPanel();
actionButtonsPanel = new javax.swing.JPanel();
dataSetSelector1 = new org.autoplot.datasource.DataSetSelector();
statusLabel = new javax.swing.JLabel();
bottomLeftPanel = new javax.swing.JPanel();
navigationPanel = new javax.swing.JPanel();
timeFilterTextField = new javax.swing.JTextField();
showMissingCheckBox = new javax.swing.JCheckBox();
useRangeCheckBox = new javax.swing.JCheckBox();
jPanel1 = new javax.swing.JPanel();
prevSetButton = new javax.swing.JButton();
prevButton = new javax.swing.JButton();
nextButton = new javax.swing.JButton();
nextSetButton = new javax.swing.JButton();
jumpToFirstButton = new javax.swing.JButton();
jumpToLastButton = new javax.swing.JButton();
editRangeButton = new javax.swing.JButton();
pngsPanel.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
pngsPanel.setLayout(new java.awt.BorderLayout());
actionButtonsPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT));
dataSetSelector1.setToolTipText("Enter the location of the images as a wildcard (/tmp/*.png) or template (/tmp/$Y$m$d.png). .png, .gif, and .jpg files are supported.");
dataSetSelector1.setPromptText("Enter images filename template");
dataSetSelector1.setValue("");
dataSetSelector1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
dataSetSelector1ActionPerformed(evt);
}
});
statusLabel.setText("starting application...");
bottomLeftPanel.setLayout(new java.awt.BorderLayout());
timeFilterTextField.setToolTipText("Enter a time range, for example a year like \"2009\", or month \"2009 may\", or \"2009-01-01 to 2009-03-10\"\n");
timeFilterTextField.setEnabled(false);
timeFilterTextField.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
timeFilterTextFieldFocusLost(evt);
}
});
timeFilterTextField.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
timeFilterTextFieldActionPerformed(evt);
}
});
showMissingCheckBox.setText("Show Missing");
showMissingCheckBox.setToolTipText("Insert placeholder images where there are gaps detected in the sequence");
showMissingCheckBox.setEnabled(false);
showMissingCheckBox.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
showMissingCheckBoxItemStateChanged(evt);
}
});
showMissingCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
showMissingCheckBoxActionPerformed(evt);
}
});
useRangeCheckBox.setText("Limit range to:");
useRangeCheckBox.setToolTipText("Limit the time range of the images in the sequence.");
useRangeCheckBox.setEnabled(false);
useRangeCheckBox.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
useRangeCheckBoxItemStateChanged(evt);
}
});
prevSetButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/prevPrevPrev.png"))); // NOI18N
prevSetButton.setToolTipText("Skip to previous interval");
prevSetButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
prevSetButtonActionPerformed(evt);
}
});
prevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/prevPrev.png"))); // NOI18N
prevButton.setToolTipText("previous");
prevButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
prevButtonActionPerformed(evt);
}
});
nextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/nextNext.png"))); // NOI18N
nextButton.setToolTipText("next");
nextButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
nextButtonActionPerformed(evt);
}
});
nextSetButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/nextNextNext.png"))); // NOI18N
nextSetButton.setToolTipText("Skip to next interval");
nextSetButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
nextSetButtonActionPerformed(evt);
}
});
jumpToFirstButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/prevPrevPrevStop.png"))); // NOI18N
jumpToFirstButton.setToolTipText("jump to first");
jumpToFirstButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jumpToFirstButtonActionPerformed(evt);
}
});
jumpToLastButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/nextNextNextStop.png"))); // NOI18N
jumpToLastButton.setToolTipText("jump to last");
jumpToLastButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jumpToLastButtonActionPerformed(evt);
}
});
org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(jPanel1Layout.createSequentialGroup()
.addContainerGap(112, Short.MAX_VALUE)
.add(jumpToFirstButton)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(prevSetButton)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(prevButton)
.add(39, 39, 39)
.add(nextButton)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(nextSetButton)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(jumpToLastButton))
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(prevButton)
.add(prevSetButton)
.add(jumpToFirstButton)
.add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
.add(nextButton)
.add(nextSetButton)
.add(jumpToLastButton))
);
jPanel1Layout.linkSize(new java.awt.Component[] {nextButton, nextSetButton, prevButton, prevSetButton}, org.jdesktop.layout.GroupLayout.VERTICAL);
editRangeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/autoplot/resources/calendar.png"))); // NOI18N
editRangeButton.setToolTipText("Time Range Tool");
editRangeButton.setEnabled(false);
editRangeButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
editRangeButtonActionPerformed(evt);
}
});
org.jdesktop.layout.GroupLayout navigationPanelLayout = new org.jdesktop.layout.GroupLayout(navigationPanel);
navigationPanel.setLayout(navigationPanelLayout);
navigationPanelLayout.setHorizontalGroup(
navigationPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(org.jdesktop.layout.GroupLayout.TRAILING, navigationPanelLayout.createSequentialGroup()
.addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(navigationPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(navigationPanelLayout.createSequentialGroup()
.add(18, 18, 18)
.add(useRangeCheckBox)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(timeFilterTextField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 236, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(12, 12, 12)
.add(editRangeButton)
.add(18, 18, 18)
.add(showMissingCheckBox))
.add(jPanel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
navigationPanelLayout.setVerticalGroup(
navigationPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(org.jdesktop.layout.GroupLayout.TRAILING, navigationPanelLayout.createSequentialGroup()
.add(navigationPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
.add(timeFilterTextField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(useRangeCheckBox)
.add(editRangeButton)
.add(showMissingCheckBox))
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(jPanel1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
bottomLeftPanel.add(navigationPanel, java.awt.BorderLayout.CENTER);
org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(org.jdesktop.layout.GroupLayout.TRAILING, pngsPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 932, Short.MAX_VALUE)
.add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(dataSetSelector1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(statusLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(layout.createSequentialGroup()
.add(bottomLeftPanel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(actionButtonsPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
.add(pngsPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 639, Short.MAX_VALUE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(dataSetSelector1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 27, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false)
.add(bottomLeftPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.add(actionButtonsPanel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 57, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(statusLabel))
);
}// //GEN-END:initComponents
private void nextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextButtonActionPerformed
LoggerManager.logGuiEvent(evt);
seq.skipBy( 1 );
}//GEN-LAST:event_nextButtonActionPerformed
private void prevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevButtonActionPerformed
LoggerManager.logGuiEvent(evt);
seq.skipBy( -1 );
}//GEN-LAST:event_prevButtonActionPerformed
private void nextSetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextSetButtonActionPerformed
LoggerManager.logGuiEvent(evt);
seq.setIndex( nextInterval( seq.getIndex() ) );
}//GEN-LAST:event_nextSetButtonActionPerformed
private void prevSetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevSetButtonActionPerformed
LoggerManager.logGuiEvent(evt);
seq.setIndex( prevInterval( seq.getIndex() ) );
}//GEN-LAST:event_prevSetButtonActionPerformed
private void timeFilterTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_timeFilterTextFieldActionPerformed
LoggerManager.logGuiEvent(evt);
updateTimeRangeFilter( );
}//GEN-LAST:event_timeFilterTextFieldActionPerformed
public void updateTimeRangeFilter() {
try {
timeFilterTextField.setBackground( dataSetSelector1.getBackground() );
DatumRange range= DatumRangeUtil.parseTimeRange(timeFilterTextField.getText());
seq.setActiveSubrange( range );
} catch ( ParseException ex ) {
timeFilterTextField.setBackground( Color.PINK );
}
}
private void timeFilterTextFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_timeFilterTextFieldFocusLost
// canvas.setTimeRange( timeFilterTextField.getText() );
// if ( !canvas.getTimeRange().equals(timeFilterTextField.getText() ) ) {
// timeFilterTextField.setBackground( Color.PINK );
// } else {
// timeFilterTextField.setBackground( dataSetSelector1.getBackground() );
// }
}//GEN-LAST:event_timeFilterTextFieldFocusLost
private void jumpToLastButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jumpToLastButtonActionPerformed
LoggerManager.logGuiEvent(evt);
seq.last();
}//GEN-LAST:event_jumpToLastButtonActionPerformed
private void jumpToFirstButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jumpToFirstButtonActionPerformed
LoggerManager.logGuiEvent(evt);
seq.first();
}//GEN-LAST:event_jumpToFirstButtonActionPerformed
private void dataSetSelector1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSetSelector1ActionPerformed
LoggerManager.logGuiEvent(evt);
String t= dataSetSelector1.getValue();
if ( t.endsWith(".pngwalk") ) {
loadPngwalkFile(t);
} else {
setTemplate( t );
}
nextButton.requestFocus();
}//GEN-LAST:event_dataSetSelector1ActionPerformed
private void showMissingCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_showMissingCheckBoxItemStateChanged
seq.setShowMissing(evt.getStateChange()==java.awt.event.ItemEvent.SELECTED);
}//GEN-LAST:event_showMissingCheckBoxItemStateChanged
private void editRangeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editRangeButtonActionPerformed
LoggerManager.logGuiEvent(evt);
TimeRangeTool t= new TimeRangeTool();
List times;
if ( seq.isUseSubRange() ) {
t.setSelectedRange( timeFilterTextField.getText() );
} else {
times = seq.getAllTimes();
DatumRange tr= times.get(0);
for ( DatumRange tr1: times ) {
tr= tr.union(tr1);
}
t.setSelectedRange( timeFilterTextField.getText() );
}
if ( JOptionPane.OK_OPTION==JOptionPane.showConfirmDialog( parentWindow, t, "Subrange", JOptionPane.OK_CANCEL_OPTION ) ) {
try {
DatumRange range= DatumRangeUtil.parseDatumRange( t.getSelectedRange() );
timeFilterTextField.setText( range.toString() );
updateTimeRangeFilter();
} catch (ParseException ex) {
Logger.getLogger(PngWalkTool.class.getName()).log(Level.SEVERE, null, ex);
}
}
}//GEN-LAST:event_editRangeButtonActionPerformed
private void useRangeCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_useRangeCheckBoxItemStateChanged
boolean enable= evt.getStateChange()==java.awt.event.ItemEvent.SELECTED;
seq.setUseSubRange(enable);
timeFilterTextField.setEnabled(enable);
editRangeButton.setEnabled(enable);
if (!enable) return;
List current = seq.getActiveSubrange();
DatumRange range= DatumRangeUtil.union(current.get(0), current.get(current.size()-1));
timeFilterTextField.setText( range.toString() );
}//GEN-LAST:event_useRangeCheckBoxItemStateChanged
private void showMissingCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showMissingCheckBoxActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_showMissingCheckBoxActionPerformed
/**
* we need to make this public.
* @param propertyName
* @param oldValue
* @param newValue
*/
@Override
public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
super.firePropertyChange(propertyName, oldValue, newValue); //To change body of generated methods, choose Tools | Templates.
}
/**
* provide means for scripts to add component to develop new applications.
* @return the TearoffTabbedPane used.
*/
public TearoffTabbedPane getTabs() {
return tabs;
}
/**
* return the container for the sequence of images, which contains the
* current index and provides a method for jumping to other images.
* @return the sequence.
*/
public WalkImageSequence getSequence() {
return this.seq;
}
/**
*
* @param monitor
* @param f the output folder.
* @param summary summary title for each slide.
* @throws FileNotFoundException
*/
private void writeToHtmlImmediately( ProgressMonitor monitor, File f, String summary ) throws FileNotFoundException {
monitor.setTaskSize( this.seq.size() );
monitor.started();
URI base;
if ( this.seq.getQCFolder()!=null ) {
base= this.seq.getQCFolder();
} else {
int splitIndex=-1;
if ( splitIndex==-1 ) splitIndex= WalkUtil.splitIndex( seq.getTemplate() );
try {
base= new URI( this.seq.getTemplate().substring(0,splitIndex) );
} catch (URISyntaxException ex) {
throw new RuntimeException(ex);
}
}
if ( !f.exists() ) {
if ( !f.mkdirs() ) {
logger.log(Level.WARNING, "unable to create folder: {0}", f);
}
}
boolean writeInSitu= base.relativize(f.toURI() ).toString().trim().length()==0;
try {
if ( !writeInSitu ) {
for ( int i= 0; i params= new HashMap<>();
params.put("dir",base.toString()+"/");
params.put("qconly", this.seq.getQCFilter().equals("") ? "F" : "T" );
String sd= f.toString();
if ( !sd.endsWith("/") && !sd.endsWith("\\") ) {
sd= sd+"/";
}
params.put("outdir",sd);
params.put("name",""); //TODO: what should this be?
params.put("summary",summary);
try {
JythonUtil.invokeScriptSoon(nf.toURI(),null,params,false,false,mon);
} catch (IOException ex) {
Logger.getLogger(PngWalkTool.class.getName()).log(Level.SEVERE, null, ex);
}
} catch ( MalformedURLException ex ) {
throw new IllegalArgumentException(ex);
} catch (IOException ex) {
Logger.getLogger(PngWalkTool.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* write the sequence to a HTML file, so that this can be used to produce
* worksheets.
*
*/
public void writeHtml() {
JFileChooser choose= new JFileChooser();
Preferences prefs= Preferences.userNodeForPackage(PngWalkTool.class);
String fname= prefs.get( "writeToHtml", "/tmp/pngwalk/" );
choose.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
choose.setSelectedFile( new File(fname) );
final HtmlOutputOptions hoo= new HtmlOutputOptions();
choose.setAccessory(hoo);
if ( choose.showSaveDialog(PngWalkTool.this)==JFileChooser.APPROVE_OPTION ) {
final File f= choose.getSelectedFile();
prefs.put( "writeToHtml", f.toString() );
final ProgressMonitor mon= DasProgressPanel.createFramed(SwingUtilities.getWindowAncestor(this),"write html");
Runnable run= () -> {
try {
writeToHtmlImmediately( mon , f, hoo.getTitle() );
} catch (FileNotFoundException ex) {
throw new RuntimeException(ex);
}
};
new Thread(run).start();
}
}
private void writeToPdfImmediately( ProgressMonitor monitor, File f ) throws FileNotFoundException {
try {
monitor.setTaskSize(this.seq.size());
monitor.started();
int imageNumber= 1;
FileOutputStream out= new FileOutputStream( f );
Rectangle rect = new Rectangle( (int)(8.5*72), (int)11*72 );
Document doc = new Document(rect, 0, 0, 0, 0 );
doc.addCreator("autoplotPngwalkTool");
doc.addCreationDate();
PdfWriter writer = PdfWriter.getInstance(doc, out);
doc.open();
QualityControlSequence qcseq= this.seq.getQualityControlSequence();
Font lightGreyFont= new Font();
lightGreyFont.setColor( BaseColor.LIGHT_GRAY );
Chunk lineChunk= new Chunk("________________________________"+
"_________________________________", lightGreyFont );
logger.log(Level.FINE, "writeToPdf {0} {1} pages", new Object[]{f.getName(), this.seq.size()});
for ( int i= 0; i800 ) {
// im= ImageUtil.getScaledInstance( im, 800, 800 * 600, true );
//}
ImageIO.write(im, "png", baos);
Image pdfImage= com.itextpdf.text.Image.getInstance(baos.toByteArray() );
int w= (int)(7.5*72);
int h= w * im.getHeight() / im.getWidth();
pdfImage.setAbsolutePosition(36,11*72-36-h);
pdfImage.scaleToFit(w,h);
PdfPTable table= new PdfPTable(1);
table.getDefaultCell().setBorder( Rectangle.NO_BORDER );
table.getDefaultCell().setPaddingLeft( 48 );
table.getDefaultCell().setPaddingRight( 24 );
table.setWidthPercentage(100);
PdfPCell cell;
cell= new PdfPHeaderCell();
cell.setFixedHeight(72);
cell.setPaddingLeft( 48 );
cell.setPaddingRight( 24 );
cell.setHorizontalAlignment( Element.ALIGN_RIGHT );
cell.setVerticalAlignment( Element.ALIGN_BOTTOM );
Paragraph p;
p= new Paragraph();
p.setAlignment( Element.ALIGN_RIGHT );
p.add( String.format("%d of %d", imageNumber, this.seq.size() ) );
cell.addElement( p );
cell.setBorder( Rectangle.NO_BORDER );
cell.setPaddingLeft( 48 );
cell.setPaddingRight( 48 );
table.addCell( cell );
cell = new PdfPCell( pdfImage );
cell.setBorder( Rectangle.NO_BORDER );
cell.setPaddingLeft( 48 );
cell.setPaddingRight( 24 );
table.addCell( cell );
String caption;
if ( qcseq!=null ) {
QualityControlRecord r= qcseq.getQualityControlRecord(i);
if ( r!=null ) {
caption= r.getLastComment();
} else {
caption= "";
}
} else {
caption= "";
}
logger.log(Level.FINER, "caption: {0}", caption);
p= new Paragraph();
p.add( caption );
cell = new PdfPCell( p );
cell.setBorder( Rectangle.NO_BORDER );
cell.setPaddingLeft( 48 );
cell.setPaddingRight( 48 );
table.addCell(cell);
cell = new PdfPCell( p );
cell.addElement( new Paragraph(" ") );
for ( int j=0;j<10; j++ ) {
p= new Paragraph(lineChunk);
cell.addElement( p );
}
cell.setBorder( Rectangle.NO_BORDER );
cell.setPaddingLeft( 48 );
cell.setPaddingRight( 48 );
table.addCell(cell);
Chunk nameChunk= new Chunk( this.seq.imageAt(i).uriString, lightGreyFont );
cell= new PdfPCell( new Paragraph(nameChunk) );
cell.setBorder( Rectangle.NO_BORDER );
cell.setPaddingLeft( 48 );
cell.setPaddingRight( 48 );
table.addCell( cell );
doc.add( table );
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
cb.restoreState();
doc.newPage();
imageNumber++;
}
doc.close();
} catch (DocumentException ex) {
logger.log(Level.SEVERE, null, ex);
} finally {
monitor.finished();
}
}
/**
* write the sequence to a PDF file, so that this can be used to produce
* worksheets.
*
*/
public void writePdf() {
JFileChooser choose= new JFileChooser();
Preferences prefs= Preferences.userNodeForPackage(PngWalkTool.class);
String fname= prefs.get( "writeToPdf", "/tmp/pngwalk.pdf" );
choose.setSelectedFile( new File(fname) );
choose.setFileFilter( new FileNameExtensionFilter("pdf files", "pdf" ));
if ( choose.showSaveDialog(PngWalkTool.this)==JFileChooser.APPROVE_OPTION ) {
final File f= choose.getSelectedFile();
prefs.put( "writeToPdf", f.toString() );
final ProgressMonitor mon= DasProgressPanel.createFramed(SwingUtilities.getWindowAncestor(this),"write pdf");
Runnable run= () -> {
try {
writeToPdfImmediately( mon , f );
final JPanel panel= new javax.swing.JPanel();
panel.setLayout( new BoxLayout(panel,BoxLayout.Y_AXIS ));
panel.add( new javax.swing.JLabel("wrote file "+f) );
JButton b= new JButton("Open in Browser");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
AutoplotUtil.openBrowser(f.toURI().toString());
}
} );
panel.add( b );
JButton b2= new JButton("Copy filename to clipboard");
b2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
GuiSupport.setClipboard( f.toURI().toString() );
}
});
panel.add( b2 );
JOptionPane.showMessageDialog( PngWalkTool.this,panel );
} catch (FileNotFoundException ex) {
logger.log(Level.SEVERE, null, ex);
}
};
new Thread(run).start();
}
}
/**
* write the sequence to a HTML file, so that this can be used to produce
* worksheets.
*
*/
public void writeCsv() {
JFileChooser choose= new JFileChooser();
Preferences prefs= Preferences.userNodeForPackage(PngWalkTool.class);
String fname= prefs.get( "writeToCsv", "/tmp/pngwalk.csv" );
choose.setFileSelectionMode(JFileChooser.FILES_ONLY);
choose.setSelectedFile( new File(fname) );
choose.setFileFilter( new FileNameExtensionFilter("csv files", "csv" ));
if ( choose.showSaveDialog(PngWalkTool.this)==JFileChooser.APPROVE_OPTION ) {
final File f= choose.getSelectedFile();
prefs.put( "writeToCsv", f.toString() );
final ProgressMonitor mon= DasProgressPanel.createFramed(SwingUtilities.getWindowAncestor(this),"write csv");
Runnable run= () -> {
try {
writeToCsvImmediately( mon , f );
} catch (FileNotFoundException ex) {
logger.log(Level.SEVERE, null, ex);
}
final JPanel panel= new javax.swing.JPanel();
panel.setLayout( new BoxLayout(panel,BoxLayout.Y_AXIS ));
panel.add( new javax.swing.JLabel("wrote file "+f) );
JButton b= new JButton("Open in Browser");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
AutoplotUtil.openBrowser(f.toURI().toString());
}
} );
panel.add( b );
JButton b2= new JButton("Copy filename to clipboard");
b2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
GuiSupport.setClipboard( f.toURI().toString() );
}
});
panel.add( b2 );
JOptionPane.showMessageDialog( PngWalkTool.this,panel );
};
new Thread(run).start();
}
}
private void writeToCsvImmediately( ProgressMonitor monitor, File f ) throws FileNotFoundException {
try ( PrintWriter pout= new PrintWriter(f) ) {
monitor.setTaskSize(this.seq.size());
monitor.started();
QualityControlSequence qcseq= this.seq.getQualityControlSequence();
logger.log(Level.FINE, "writeToCsv {1}", new Object[]{f.getName()});
pout.println( "start,stop,label,filename,lastQCMessage,QCStatus");
for ( int i= 0; i0 ) {
int nl= lastComment.indexOf("\n");
if ( nl>-1 ) lastComment= lastComment.substring(0,nl);
lastComment= "\""+lastComment+"\"";
}
String status = qcr==null ? "" : qcr.getStatus().toString();
if ( status.equals("Unknown") ) status="";
String line= String.format("%s,%s,%s,%s,%s,%s",smin,smax,scaption,filename,lastComment,status);
pout.println(line);
}
} finally {
monitor.finished();
}
}
/**
*
* @param monitor
* @param f
* @param overrideDelays if null, then just use 100ms between frames, otherwise use this delay. "realTime", "10ms", "secondPerDay"
* @param r60, if true, then reduce the image to 60% of its original size.
* @throws FileNotFoundException
*/
private void writeToAnimatedGifImmediately( final ProgressMonitor monitor, File f, final String overrideDelays, final boolean r60 ) throws FileNotFoundException {
try {
monitor.setTaskSize(this.seq.size());
monitor.started();
final DatumRange baseRange= this.seq.imageAt(0).getDatumRange();
final Datum baset;
if ( baseRange!=null ) {
baset= this.seq.imageAt(0).getDatumRange().min();
} else {
if ( overrideDelays!=null && !overrideDelays.endsWith("ms")) {
throw new IllegalArgumentException("template does not imply timeranges");
}
baset= null;
}
Iterator images= new Iterator() {
int i=0;
@Override
public boolean hasNext() {
return i delays= new Iterator() {
int i=0;
Datum lastTime;
@Override
public boolean hasNext() {
throw new IllegalArgumentException("use images.next");
}
@Override
public String next() {
if ( i==0 ) {
lastTime= baset;
}
i=i+1;
if ( i==seq.size() ) i--;
String result;
if ( overrideDelays!=null ) {
switch (overrideDelays) {
case "realTime":
result= String.valueOf((int) Math.ceil( seq.imageAt(i).getDatumRange().min().subtract(lastTime).convertTo(Units.milliseconds).value()/10. ) );
lastTime= seq.imageAt(i).getDatumRange().min();
break;
case "secondPerDay":
result= String.valueOf((int) Math.ceil( seq.imageAt(i).getDatumRange().min().subtract(lastTime).convertTo(Units.milliseconds).value()/864000) );
lastTime= seq.imageAt(i).getDatumRange().min();
break;
default:
try {
result= String.valueOf((int) Math.ceil( Units.milliseconds.parse(overrideDelays).value()/10 ) );
} catch (ParseException ex) {
throw new IllegalArgumentException( ex );
}
break;
}
} else {
result= "1";
}
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove is not supported");
}
};
logger.log(Level.FINE, "writing to {0}", f);
AnimatedGifDemo.saveAnimate( f, images, delays );
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
} finally {
monitor.finished();
}
}
/**
* Write the displayed images to an animated gif.
*/
public void writeAnimatedGif( ) {
JFileChooser choose= new JFileChooser();
Preferences prefs= Preferences.userNodeForPackage(PngWalkTool.class);
String fname= prefs.get( "writeToGif", "/tmp/pngwalk.gif" );
choose.setSelectedFile( new File(fname) );
final String[] opts= new String[] { "10ms", "50ms", "200ms", "400ms", "800ms", "1000ms", "1200ms", "realTime", "secondPerDay" };
JPanel p= new JPanel();
p.setLayout( new BoxLayout(p,BoxLayout.Y_AXIS) );
JComboBox jo= new JComboBox(opts);
jo.setSelectedIndex(1);
jo.setMaximumSize( new Dimension( 1000, 30 ) );
jo.setEditable(true);
p.add(new JLabel("Interslide-Delay:"));
p.add(jo);
final JCheckBox r60= new JCheckBox( "Reduce to 60%" );
p.add(r60);
p.add(Box.createGlue());
choose.setAccessory(p);
if ( choose.showSaveDialog(PngWalkTool.this)==JFileChooser.APPROVE_OPTION ) {
final File f= choose.getSelectedFile();
prefs.put( "writeToGif", f.toString() );
final ProgressMonitor mon= DasProgressPanel.createFramed(SwingUtilities.getWindowAncestor(this),"write animated gif");
final String fdelay= (String)jo.getSelectedItem();
Runnable run= () -> {
try {
writeToAnimatedGifImmediately( mon, f, fdelay, r60.isSelected() );
JPanel panel= new javax.swing.JPanel();
panel.setLayout( new BoxLayout(panel,BoxLayout.Y_AXIS ));
panel.add( new javax.swing.JLabel("wrote file "+f) );
JButton b= new JButton("Open in Browser");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
AutoplotUtil.openBrowser(f.toURI().toString());
}
});
panel.add( b );
JButton b2= new JButton("Copy filename to clipboard");
b2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
GuiSupport.setClipboard( f.toURI().toString() );
}
});
panel.add( b2 );
JOptionPane.showMessageDialog( PngWalkTool.this,panel );
} catch (FileNotFoundException ex) {
Logger.getLogger(PngWalkTool.class.getName()).log(Level.SEVERE, null, ex);
}
};
new Thread(run).start();
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel actionButtonsPanel;
private javax.swing.JPanel bottomLeftPanel;
private org.autoplot.datasource.DataSetSelector dataSetSelector1;
private javax.swing.JButton editRangeButton;
private javax.swing.JPanel jPanel1;
private javax.swing.JButton jumpToFirstButton;
private javax.swing.JButton jumpToLastButton;
private javax.swing.JPanel navigationPanel;
private javax.swing.JButton nextButton;
private javax.swing.JButton nextSetButton;
private javax.swing.JPanel pngsPanel;
private javax.swing.JButton prevButton;
private javax.swing.JButton prevSetButton;
private javax.swing.JCheckBox showMissingCheckBox;
private javax.swing.JLabel statusLabel;
private javax.swing.JTextField timeFilterTextField;
private javax.swing.JCheckBox useRangeCheckBox;
// End of variables declaration//GEN-END:variables
}