/******************************************************************** * ViewPlots is an Applet that allows a number of graphics to be looped. * * ViewPlots is designed that the user may select critera whereby * a set of graphics in a specific directory are choosen. These * graphics can then be looped through automatically at different * rates, choosen specically, or manualy framed forward and back. * * Change Log: * 03 Mar 2004: Initial re-write * 12 Mar 2004: Various bug-fixes, added support for environment variables *******************************************************************/ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.File; import java.io.FilterReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.StringTokenizer; import java.util.Vector; import javax.swing.AbstractButton; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; import javax.swing.Timer; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; /******************************************************************** * ViewPlots Applet and Application * * This class will display a list of all image files in the current * directory and a list of all the sub-directories in the current * directory. It will then allow you to loop through the images, or * a sub-set of the images. Giving manual control for forward and * back. You also have the option of going to one of the sub- * directories and looping through the images there. * * A coding convention has been followed to establish the scope of * a variable. All variables of the form: its represent * globally scoped variables available in all methods. All variables * of the form: the represent formal arguments of a method. All * variables of the form: a[n] represent locally declared * variables. * * @author Daniel J. Adams * @version 1.0.0 03 Mar 2004 *******************************************************************/ public class ViewPlots extends JApplet implements ActionListener, ListSelectionListener { /** Represents spacing between widgets, can be 0-MAXINT */ private static final int itsGap = 5; /** Utilizes the IconImage as component for the image display */ private JLabel itsImage; /** Trigger for the looping mechanism */ private Timer itsTimer; /** Index in the image list of the currently displayed image */ private int itsIndex = 0; /** Flag to indicate running state as Applet(true) or Application(false) */ private boolean itsAppletFlag = false; /** The widget containing the list of sub-directories of the current dir. */ private JList itsDirList; /** The widget containing the list of images in the current dir. */ private JList itsImageList; /** Holder for the sub-set when looping on a subset of images */ private int[] itsSelectedIndices = new int[0]; /** Flag to indicate doing a menu update, ignore slected events during update */ private boolean itsUpdateFlag = false; /** Current directory, relative to root directory where this was started */ private String itsSubDir = "" + File.separatorChar; /** Root directory, where this was started */ private String itsRootDir; /** Environment, parameters from the command line or from the HTML file */ private Environment itsEnvironment = new Environment(); /******************************************************************** * Empty constructor * * Empty contsructor called when executing as an Applet *******************************************************************/ public ViewPlots() { this(true); // call the normal constructor, setting applet flag } /******************************************************************** * Normal constructor. * * Normal constructor, called directly by main() to indicate * that we are not running as an Applet and need to make sure * we use filesystem, nor URL resources. Also called by the applet * constructor with true to insure that we are running in a browser. * * @param theAppletFlag true if running as an applet, false if an application *******************************************************************/ public ViewPlots(boolean theAppletFlag) { itsAppletFlag = theAppletFlag; if(itsAppletFlag) // This code avoids the system event access message in the browsers getRootPane().putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE); itsTimer = new Timer(400, this); // Initial pause between images is .4 seconds } /******************************************************************** * Application entry point * * This method allows the applet to be run as an application. It sets * up the necessary components for a single display window that acts * like the applet portion of a browser window. When this class is * run as an application, the main window is closed to end it. * * @param theArgs Arguments passed in on the command line. A single * argument is used to determine the root directory, default is '.' * with no argument. *******************************************************************/ public static void main(String[] theArgs) { JFrame aFrame = new JFrame("Application version: ViewPlots Applet"); // // THIS IS THE ANONYMOUS INNER CLASS (ViewPlots$1.class) // aFrame.addWindowListener(new WindowAdapter() // Simple adapter class to exit the application on a window close event { public void windowClosing(WindowEvent anEvent) { System.exit(0); } }); ViewPlots anApplet = new ViewPlots(false); anApplet.itsEnvironment = anApplet.new Environment(theArgs); aFrame.setContentPane(anApplet.makeContentPane()); aFrame.pack(); aFrame.setVisible(true); } /******************************************************************** * Initialization of envirronment variable. * * This method is required to support both applet and applicatoin mode. *******************************************************************/ private void setEnvironment(Environment theEnv) { itsEnvironment = theEnv; } /******************************************************************** * Method called as an Applet, prior to display. * * Required method for all Applets to do setups prior to the display * of the Applet. This init sets the Applets content pane to a * newly constructed pane. * * @see #makeContentPane *******************************************************************/ public void init() { setContentPane(makeContentPane()); } /******************************************************************** * Displays the image * * Displays the image in itsImageList pointed to by itsIndex. * Checking is done prior to display, to insure that itsIndex is * within bounds of itsImageList or itsSelectedIndices, wrapping over * as required. * * Side Effect: itsImage may be changed to either the first legal * element if itsIndex overflows the limits, or to the * last legal element if itsIndex underflows the limits. *******************************************************************/ private void showImage() { int aStartLimit = 0; int anEndLimit = itsImageList.getModel().getSize() - 1; if (anEndLimit == -1) // No items in image list return; if (!(itsSelectedIndices.length == 0)) // restricted to the selected section { aStartLimit = itsSelectedIndices[0]; anEndLimit = itsSelectedIndices[1]; } if (itsIndex > anEndLimit) // check for overflow itsIndex = aStartLimit; else if (itsIndex < aStartLimit) // and for underflow itsIndex = anEndLimit; itsUpdateFlag = true; // don't let this fire a new event itsImageList.setSelectedIndex(itsIndex); // ensure that we are choosing itsImageList.ensureIndexIsVisible(itsIndex); // and displaying the selected image in itsImageList itsUpdateFlag = false; // don't let this fire a new event try { if (itsAppletFlag) itsImage.setIcon(new ImageIcon(new URL(getDocumentBase(),itsSubDir + itsImageList.getSelectedValue()))); else itsImage.setIcon(new ImageIcon(new File(itsRootDir + itsSubDir + itsImageList.getSelectedValue()).toURL())); } catch(MalformedURLException theEx) { System.err.println("Unable to get URL for image:\n"+theEx); theEx.printStackTrace(); } } /******************************************************************** * Responds to Timer events, and Button events * * This method handles events from both the Buttons, and itsTimer * itsTimer events cause itsImage to advance * Button events cause the proscribed action to occur. * * Side Effects: itsImage is adjusted on itsTimer ticks, Forward * button press, and Backward button press. On View button press * itsSubDir is reset, and both menus updated. * * @param theEvent Action event from itsTimer or a JButton *******************************************************************/ public void actionPerformed(ActionEvent theEvent) { String aCommand = theEvent.getActionCommand(); if (aCommand == null) // Then event is from itsTimer { ++itsIndex; showImage(); } else // Only button events left { if (aCommand.equalsIgnoreCase("play") && !itsTimer.isRunning()) // Start itsTimer { itsTimer.start(); } else if (aCommand.equalsIgnoreCase("stop")) // Stop itsTimer itsTimer.stop(); else if (aCommand.equalsIgnoreCase("faster") && itsTimer.getDelay() > 50) // Speed up itsTimer itsTimer.setDelay(itsTimer.getDelay() - 50); else if (aCommand.equalsIgnoreCase("slower") && itsTimer.getDelay() < 1500) // Slow down itsTimer itsTimer.setDelay(itsTimer.getDelay() + 50); else if (aCommand.equalsIgnoreCase("back")) // Go back one image { itsTimer.stop(); --itsIndex; showImage(); } else if (aCommand.equalsIgnoreCase("forward")) // Go forward one image { itsTimer.stop(); ++itsIndex; showImage(); } else if (aCommand.equalsIgnoreCase("View")) // Reset back to root directory { itsUpdateFlag = true; itsTimer.stop(); itsSubDir = "" + File.separatorChar; itsImageList.setListData(getMenu(false)); itsDirList.setListData(getMenu(true)); itsUpdateFlag = false; } } } /******************************************************************** * Respond to select events in the lists. * * Allow the list selection events to occur. This can cause a new * Image to be displayed, or a new set of contraints on the images. * * Side Effects: On selecting a new sub directory, the lists are * regenerated. * * @param theEvent List Selection event from one of the lists. *******************************************************************/ public void valueChanged(ListSelectionEvent theEvent) { JList aList = (JList)theEvent.getSource(); // Can be either Dir or Image itsIndex = aList.getSelectedIndex(); String aSelect = (String)aList.getSelectedValue(); if (!itsTimer.isRunning()) // Then we are allowed to set, or clear, a new set of constraints { if (aList.getSelectedIndices().length > 1) // Multiple selections { itsSelectedIndices = new int[2]; itsSelectedIndices[0] = aList.getMinSelectionIndex(); itsSelectedIndices[1] = aList.getMaxSelectionIndex(); } else // Single selection, clear constraints itsSelectedIndices = new int[0]; } if (!theEvent.getValueIsAdjusting() && !itsUpdateFlag) // Make sure we are not in the middle of a software driven change { if (aList == itsImageList && isImage(aSelect) && !itsTimer.isRunning() && itsSelectedIndices.length == 0) showImage(); // Change to the just selected image else if (aList == itsDirList && isDir(aSelect)) // Selected a new subdirectory { itsUpdateFlag = true; itsSubDir = itsSubDir + aSelect + File.separatorChar; itsDirList.setListData(getMenu(true)); itsImageList.setListData(getMenu(false)); itsUpdateFlag = false; } } } /******************************************************************** * Build the contents of the Applet * * This method builds and initializes all of the widgets for the * display. It is called once at initialization of the applet. * * @return Container (JPanel) for root content pane. *******************************************************************/ private Container makeContentPane() { JPanel aContentPane = new JPanel(); // Global content pane aContentPane.setBackground(Color.blue); BorderLayout aLayout = new BorderLayout(); aLayout.setHgap(itsGap); // astetic for border aLayout.setVgap(itsGap); // astetic for border aContentPane.setLayout(aLayout); itsRootDir = itsEnvironment.getRootDir(); Box aButtonPane = new Box(BoxLayout.X_AXIS); // Container for holding all the buttons aButtonPane.setBackground(Color.blue); aButtonPane.add(Box.createHorizontalGlue()); // Split extra space in front and back of buttons // Fill the top of the content pane with control buttons // First button is pattern for all buttons JButton aButton = new JButton("Play"); // Button display name aButton.setVerticalTextPosition(AbstractButton.CENTER); // Center name on button aButton.setHorizontalTextPosition(AbstractButton.CENTER); aButton.setMnemonic(KeyEvent.VK_P); // Add Mnemonic, allow button press on keyEvent (Alt-P) aButton.setToolTipText("This button begins animating the graph area."); // Tooltext on button hover aButton.setActionCommand("Play"); // Command for event aButton.addActionListener(this); // We listen to it aButtonPane.add(aButton); // add the button aButtonPane.add(Box.createHorizontalStrut(itsGap)); // Space between buttons aButton = new JButton("Stop"); aButton.setVerticalTextPosition(AbstractButton.CENTER); aButton.setHorizontalTextPosition(AbstractButton.CENTER); aButton.setMnemonic(KeyEvent.VK_S); // Alt-S aButton.setToolTipText("This button stops the animation in the graph area."); aButton.setActionCommand("Stop"); aButton.addActionListener(this); aButtonPane.add(aButton); aButtonPane.add(Box.createHorizontalStrut(itsGap)); // Space between buttons aButton = new JButton("Faster"); aButton.setVerticalTextPosition(AbstractButton.CENTER); aButton.setHorizontalTextPosition(AbstractButton.CENTER); aButton.setMnemonic(KeyEvent.VK_EQUALS); // Alt-= aButton.setToolTipText("This button speeds up animation in the graph area."); aButton.setActionCommand("Faster"); aButton.addActionListener(this); aButtonPane.add(aButton); aButtonPane.add(Box.createHorizontalStrut(itsGap)); // Space between buttons aButton = new JButton("Slower"); aButton.setVerticalTextPosition(AbstractButton.CENTER); aButton.setHorizontalTextPosition(AbstractButton.CENTER); aButton.setMnemonic(KeyEvent.VK_MINUS); // Alt-- aButton.setToolTipText("This button slows down animation in the graph area."); aButton.setActionCommand("Slower"); aButton.addActionListener(this); aButtonPane.add(aButton); aButtonPane.add(Box.createHorizontalStrut(itsGap)); // Space between buttons aButton = new JButton("Back One"); aButton.setVerticalTextPosition(AbstractButton.CENTER); aButton.setHorizontalTextPosition(AbstractButton.CENTER); aButton.setMnemonic(KeyEvent.VK_B); // Alt-B aButton.setToolTipText("This button moves the animation in the graph area backward one frame."); aButton.setActionCommand("Back"); aButton.addActionListener(this); aButtonPane.add(aButton); aButtonPane.add(Box.createHorizontalStrut(itsGap)); // Space between buttons aButton = new JButton("Forwad One"); aButton.setVerticalTextPosition(AbstractButton.CENTER); aButton.setHorizontalTextPosition(AbstractButton.CENTER); aButton.setMnemonic(KeyEvent.VK_F); // Alt-F aButton.setToolTipText("This button moves the animation in the graph area forward one frame."); aButton.setActionCommand("Forward"); aButton.addActionListener(this); aButtonPane.add(aButton); if (itsEnvironment.getShowDirs()) { aButtonPane.add(Box.createHorizontalStrut(itsGap)); // Space between buttons aButton = new JButton("View a Different Date"); aButton.setVerticalTextPosition(AbstractButton.CENTER); aButton.setHorizontalTextPosition(AbstractButton.CENTER); aButton.setMnemonic(KeyEvent.VK_V); // Alt-V aButton.setToolTipText("This button resets the lists to allow a different date to be choosen."); aButton.setActionCommand("View"); aButton.addActionListener(this); aButtonPane.add(aButton); } aButtonPane.add(Box.createHorizontalGlue()); // Split extra space in front and back of buttons aContentPane.add(aButtonPane, BorderLayout.NORTH); // Fill the right of the content pane with the menu items Box aListPane = new Box(BoxLayout.Y_AXIS); // Container for holding all the lists aListPane.setBackground(Color.blue); aListPane.setMinimumSize(new Dimension(150, 0)); // Constrain the lists to ALWAYS be this wide aListPane.setPreferredSize(new Dimension(150, 150 * 5)); aListPane.setMaximumSize(new Dimension(150, Integer.MAX_VALUE)); if (itsEnvironment.getShowDirs()) { itsDirList = new JList(getMenu(true)); itsDirList.setVisibleRowCount(25); // Initial visible row count, can change on resize itsDirList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // Only a single dir at a time selected itsDirList.addListSelectionListener(this); aListPane.add(new JScrollPane(itsDirList), BorderLayout.SOUTH); aListPane.add(Box.createRigidArea(new Dimension(150,itsGap)),BorderLayout.CENTER); } itsImageList = new JList(getMenu(false)); itsImageList.setVisibleRowCount(25); // Initial visible row count, can change on resize itsImageList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // Ranges allowed for images itsImageList.addListSelectionListener(this); aListPane.add(new JScrollPane(itsImageList), BorderLayout.NORTH); aContentPane.add(aListPane, BorderLayout.EAST); // The center of the content pane holds the image area itsImage = new JLabel(); itsImage.setMinimumSize(new Dimension(800,800)); // Set dimensions to be the size of an image itsImage.setMaximumSize(new Dimension(800,800)); itsImage.setPreferredSize(new Dimension(800,800)); JPanel imagePanel = new JPanel(); imagePanel.setBackground(Color.blue); imagePanel.add(itsImage); aContentPane.add(imagePanel, BorderLayout.CENTER); return aContentPane; } /******************************************************************** * Get a set of items for a List display * * Gather a list of all sub-directories (on theDirFlag->true) or * all images (theDirFlag->false) in the current directory. * * @param theDirFlag Flag to indicate if collecting directories *******************************************************************/ private Vector getMenu(boolean theDirFlag) { Vector aReturn = new Vector(); String aLine = ""; try { InputStream anInput; if (itsAppletFlag) // Determine Stream source { System.out.println("DB: "+getDocumentBase().getPath()); String aDir = (getDocumentBase().getPath()).substring(0,getDocumentBase().getPath().lastIndexOf("/")); System.out.println("aDir: "+aDir+", DB: "+getDocumentBase().toString()); System.out.println("aHost: "+getDocumentBase().getHost()); anInput = new URL("http",getDocumentBase().getHost(),aDir+"/").openStream(); } else anInput = new File(itsRootDir + itsSubDir).toURL().openStream(); // direct file URL BufferedReader aReader = new BufferedReader(new InputStreamReader(anInput)); // Buffered read on source as URL while ((aLine = aReader.readLine()) != null) // Read a line at a time { System.out.println("Parsing: "+aLine); if (itsAppletFlag) aLine = extractReference(aLine); if (!theDirFlag && aLine != null && aLine.length() > 0 && isImage(aLine)) // Image when looking for image? aReturn.add(aLine); else if (theDirFlag && aLine != null && aLine.length() > 0 && isDir(aLine)) // Directory when looking for directory? aReturn.add(aLine); } } catch (IOException theEx) { if (itsRootDir != null &&!itsRootDir.equals(".")) // Allow fail if a bad argument was sent to main { System.err.println("Unkown root diretory (" + itsRootDir + ") choosen as starting point."); System.exit(-1); } System.err.println("Exception caught in directory read:"); theEx.printStackTrace(); } return aReturn; } /******************************************************************** * Given a file name, return if it looks like an image * * Class method return true if String looks like an image file * * @param theFileName String to test for Image pattern * @return true if theFileName matches the Image pattern *******************************************************************/ private static boolean isImage(String theFileName) { if (((theFileName.indexOf(".jpg") != -1) || // Check for valid image extensions (theFileName.indexOf(".JPG") != -1) || (theFileName.indexOf(".jpeg") != -1) || (theFileName.indexOf(".JPEG") != -1) || (theFileName.indexOf(".gif") != -1) || (theFileName.indexOf(".GIF") != -1)) && theFileName.indexOf("back.gif") == -1) return true; return false; } /******************************************************************** * Given a file name, return if it looks like a directory * * Return true if the String looks like a directory String * * @param theFileName String to test for directory pattern * @return true if theFileName matches the directory pattern *******************************************************************/ private boolean isDir(String theFileName) { System.out.println("isDir FN: " + theFileName + ", " + theFileName.length()); if (itsAppletFlag) // See if it ends in the file seperator return ((theFileName.charAt(theFileName.length() - 1) == File.separatorChar) && theFileName.indexOf("0") != -1); else { File aFile = new File(itsRootDir + itsSubDir + theFileName); return aFile.isDirectory(); } } /******************************************************************** * Given an HTML formatted line, return only the link referenced file name * * Return the reference contents of the anchor tag * so: Some File * returns foobar.txt * * @param theString String to search for reference * @return null, or the reference without HTML tag info. *******************************************************************/ static private String extractReference(String theString) { String aStartTag = " theString.length()) return theString; // not a tagged string String aString = theString.substring(aStartingOffset); int anEndingOffset = aString.indexOf(anEndTag); // int anEndingOffset = theString.substring(aStartingOffset).indexOf(anEndTag); if (aStartingOffset < anEndingOffset) return theString.substring(aStartingOffset,anEndingOffset); else return null; } /******************************************************************** * Environment supporting class for ViewPlots * * This class handles the retrieving of environment settings for both * the applet (passed from html), or from the aplication (command line * arguments). It simplifies the ViewPlots code to reduce the confusion * the mode can add. * * @author Daniel J. Adams * @version 1.0.0 12 Mar 2004 *******************************************************************/ class Environment { HashMap itsHash = null; /******************************************************************** * Default constructor. * * Used as an applet, does not initialize itsHash. *******************************************************************/ public Environment() { } /******************************************************************** * Application constructor * * Used for creating a new Environment from the parameters passed in * on the command line. *******************************************************************/ public Environment(String[] theArgs) { itsHash = new HashMap(); for (int anIndex = 0;anIndex < theArgs.length;++anIndex) { StringTokenizer aTokenizer = new StringTokenizer(theArgs[anIndex].trim(),"="); String aKey = aTokenizer.nextToken().trim(); String aValue = aTokenizer.nextToken().trim(); if (!aKey.equals("start_dir") && !aKey.equals("root_dir") && !aKey.equals("filters")) { System.err.println("Illegal arguments; looking for the following:"); System.err.println("\"show_dirs = [true|false]\""); System.err.println("\"root_dir = \""); System.err.println("\"filters = ,,...\""); System.exit(-1); } else { itsHash.put(aKey, aValue); } } } /******************************************************************** * Returns the root directory. * * From the environment for both applets and applications. *******************************************************************/ public String getRootDir() { String aReturn = "."; if (!itsAppletFlag) { Object anObj = itsHash.get("root_dir"); if (anObj != null) aReturn = (String)anObj; } else { aReturn = getParameter("root_dir"); } return aReturn; } /******************************************************************** * Returns the show directory flag. * * From the environment for both applets and applications. This flag * determines if the directory list will be displayed or not. *******************************************************************/ public boolean getShowDirs() { boolean aReturn = false; if (!itsAppletFlag) { Object anObj = itsHash.get("show_dirs"); if (anObj != null && ((String)anObj).equalsIgnoreCase("true")) aReturn = true; } else { aReturn = (getParameter("show_dirs") != null && getParameter("show_dirs").equalsIgnoreCase("true")); } return aReturn; } /******************************************************************** * Returns the file filters. * * From the environment for both applets and applications. TBD *******************************************************************/ public Vector getFilters() { return null; } } }