//=====================================================================
// File:    FragmentMap.java
// Class:   FragmentMap
// Package: AFLPgui
//
// Author:  James J. Benham
// Date:    December 5, 2000
// Contact: james_benham@hmc.edu
//
// Genographer v1.4 - Computer assisted scoring of gels.
// Copyright (C) 1998  Montana State University
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; version 2
// of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// The GNU General Public License is distributed in the file GPL
//=====================================================================

package AFLPgui;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Label;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.Panel;
import java.awt.PrintJob;
import java.awt.ScrollPane;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import AFLPcore.Bin;
import AFLPcore.BinOperation;
import AFLPcore.Cutoff;
import AFLPcore.CutoffFunction;
import AFLPcore.DataList;
import AFLPcore.FeatureList;
import AFLPcore.ImportFilter;
import AFLPcore.Gel;
import AFLPcore.GelOperation;
import AFLPcore.Lane;
import AFLPcore.LaneOperation;
import AFLPcore.Manager;
import AFLPcore.Option;
import AFLPcore.ProgOptions;

/**
 * This is the main GenoGrapher program window. It displays all of the
 * data and manages the classes. It takes care of all of the menu items
 * and switches between different data view. It can display the data as
 * a standard gel image, as a set of thumbnails for the current bin, 
 * a graph of the bin, a trace, or as text analysis. These view are 
 * actually created in seperate classes. Each of these views has it's own
 * button bar and info bar, but this class picks which one to display.
 *
 * <p>It also handles the printing of the views as well as saving, opening,
 * importing, and exporting files. Most of the views do a lot of work in
 * their respective classes, but this one manages them all.
 *
 * @author James J. Benham
 * @version 1.6.0
 * @date September 3, 2001
 */

public class FragmentMap extends Frame implements ActionListener,
                                                  WindowListener
{
  private static final String HELP_FILE = "help.html";
  private final String ICON = "geno.gif";

  public static final int GEL = 0;
  public static final int THUMBNAIL = 1;
  public static final int GRAPH = 2;
  public static final int TRACE = 3;
  public static final int ANALYSIS = 4;

  // Constants for the printer
  private static final int PAGE_H_ADJUST = 36;
  private static final int PAGE_V_ADJUST = 33;
  private static final int PAGE_HEADER_SPACE = 20;

  protected static int BAR_HEIGHT = 22;

  // Menu Items
  protected MenuItem newItem;
  protected MenuItem openItem;
  protected MenuItem saveItem;
  protected MenuItem saveAsItem;
  protected MenuItem importItem;
  protected MenuItem exportItem;
  protected MenuItem printItem;
  protected MenuItem exitItem;

  protected MenuItem laneDeleteItem;
  protected MenuItem laneOpItem[];

  protected MenuItem binRedrawItem;
  protected MenuItem binOpItem[];

  protected MenuItem gelRedrawItem;
  protected MenuItem gelOpItem[];

  protected MenuItem viewGelItem;
  protected MenuItem viewThumbnailItem;
  protected MenuItem viewGraphItem;
  protected MenuItem viewTraceItem;
  protected MenuItem viewAnalysisItem;

  protected MenuItem helpItem;
  protected MenuItem aboutItem;

  // Buttons on button bar
  protected Button newButton;
  protected Button openButton;
  protected Button saveButton;
  protected Button printButton;

  // Program variables
  protected Gel gel;

  protected GelView gelView;
  protected Thumbnail thumbnail;
  protected GraphView graph;
  protected TraceView trace; 
  protected AnalysisView analysis;

  protected ScrollPane mainPanel;
  protected ButtonBar buttonBar;
  protected Bar infoBar;
  protected Bar statusBar;
  protected Panel bottomP;
  protected Label statusLabel;
  protected double defaultCutoff;

  protected int currentView;  // the type of thing currently displayed.
  protected Bin currentBin;   // The bin that is selected or viewed

  // Dialogs
  protected MultiFileDialog importDialog;
  protected FileDialog fileDialog;
  protected ErrorDialog errorDialog;

  // File management variables
  protected String currentFile;
  protected String currentDir;
  protected boolean modified;

  /**
   * Create a new FragmentMap. This is the main program window.
   */
  public FragmentMap()
  {
    this(null);
  }

  /**
   * Create a new FragmentMap. This is the main program window.
   *
   * @param command line parameters. The only value accepted is
   *                the path for the program files. It should be
   *                the same as the startup directory.
   */
  public FragmentMap(String argv[])
  {
    setTitle("GenoGrapher");

    gel = new Gel();

    setLayout(new BorderLayout(0, 0));
    setBackground(Color.white);
    setSize(638, 440);
    setIconImage(getToolkit().getImage(ICON));

    File homePath;
    boolean goodValue = false;;
    if((argv != null) && (argv.length >= 1))
      {
	homePath = new File(argv[0]);
	goodValue = homePath.exists();
	if(!goodValue) {
	  System.err.println("Bad command line parameter for home path.");
	} else {
	  // Added by B. Master 10/25/2000
	  ProgOptions.homePath = homePath.getAbsolutePath();
	  System.out.println("Genographer path set to: " 
			     + ProgOptions.homePath);
	}
      }

    // Path wasn't an argument, so try looking at the current directory.
    // Also, path could have been bad.
    if(!goodValue)
      {
	// Set some of the options.
	homePath = new File(".");
	String tempSt = homePath.getAbsolutePath();
	if(tempSt.endsWith("."))
	    tempSt = tempSt.substring(0, tempSt.length() - 1);
	ProgOptions.homePath = tempSt;
      }

    // Make sure we have a trailing path seperator
    if(! ProgOptions.homePath.endsWith(File.separator))
	ProgOptions.homePath += File.separator;

    ProgOptions.readOptions();

    // Create Components
    gelView = new GelView(gel, this);
    thumbnail = new Thumbnail(gel.getLanes(), gel.getBins(), this);
    graph = new GraphView(gel.getLanes(), gel.getBins(), this);
    trace = new TraceView(this);
    analysis = new AnalysisView(this);
    buttonBar = gelView.getButtonBar();
    infoBar = gelView.getInfoBar();
    statusBar = new Bar();

    defaultCutoff = 0;

    // Make a panel for the bottom.
    bottomP = new Panel();
    programInitialize();

    currentBin = null;
    switchTo("Gel");

    // Create the file import dialog
    importDialog = new MultiFileDialog(this, "Import");
    fileDialog  = new FileDialog(this);
    errorDialog = new ErrorDialog(this);

    // create the help file.
    try{
      FeatureList.generateFileList();
    } catch(IOException e) {
      errorDialog.showError(e);
    }
    
    currentFile = null;

    addWindowListener(this);
  }

  /**
   * Change the program display so that it displays the type specified.
   * Possible values are: "Gel" "Thumbnail" "Graph" "Trace" and "Analysis".
   * This will handle switching the button bars as well as the main window.
   *
   * @param value  the view to switch the display to.
   */
  public void switchTo(String value)
  {
    // Retrieve the currently selected bin before we switch
    switch(currentView)
      {
      case GEL:
	currentBin = gelView.getCurrentBin();
	break;
      case THUMBNAIL:
	currentBin = thumbnail.getBin();
	break;
      case GRAPH:
	currentBin = graph.getBin();
	break;
      }

    if(value.equals("Gel"))
      showGelImage();
    else if(value.equals("Thumbnail"))
      showThumbnail();
    else if(value.equals("Graph"))
      showGraph();
    else if(value.equals("Trace"))
      showTrace();
    else if(value.equals("Analysis"))
      showAnalysis();

    // get the buttons on the button bar that we need to handle
    // button bar is set appropriately by the above
    newButton = buttonBar.getNewButton();
    openButton = buttonBar.getOpenButton();
    saveButton = buttonBar.getSaveButton();
    printButton = buttonBar.getPrintButton();
  }

  /**
   * Display the gel image. This method will set the main display as
   * well as the button bar and info bar to the correct objects.
   */
  private void showGelImage()
  {
    currentView = GEL;

    gelView.setCurrentBin(currentBin);

    mainPanel.removeAll();
    mainPanel.add(gelView);
    
    remove(buttonBar);
    buttonBar = gelView.getButtonBar();
    add(buttonBar, "North");
    
    bottomP.remove(infoBar);
    infoBar = gelView.getInfoBar();
    bottomP.add(infoBar, "North");

    gelView.syncDisplay();
    
    validate();    
  }

  /**
   * Displays the thumbnails for the currently selected bin. It also
   * sets the bars to the appropriate values and initializes the 
   * thumbnail class by giveing it either the selected lanes, or if there
   * are no selected lanes, all of the lanes. It also sets the range
   * to match that of the current bin and tells the thumbnail which bin
   * to use.
   */
  private void showThumbnail()
  {
    currentView = THUMBNAIL;

    // set the bin
    thumbnail.setBin(currentBin);
    
    // and a list of all the bins so it can switch if neccessary
    thumbnail.setBinList(gel.getBins());
    
    if(gel.getSelectedLanes().isEmpty())
      thumbnail.setLanes(gel.getLanes());
    else
      thumbnail.setLanes(gel.getSelectedLanes());

    thumbnail.init();
    
    mainPanel.removeAll();
    mainPanel.add(thumbnail);
    
    remove(buttonBar);
    buttonBar = thumbnail.getButtonBar();
    add(buttonBar, "North");
    
    bottomP.remove(infoBar);
    infoBar = thumbnail.getInfoBar();
    bottomP.add(infoBar, "North");

    validate();
  }

  /**
   * Displays the graph of the current bin. It is initialized using 
   * the current bin and either the selected lanes or all the lanes if
   * none are selected. The correct bars are selected also.
   */
  private void showGraph()
  {
    currentView = GRAPH;

    // initialize it with the current bin, and all the bins and lanes
    if(gel.getSelectedLanes().isEmpty())
      graph.init(currentBin, gel.getLanes(), gel.getBins());
    else
      graph.init(currentBin, gel.getSelectedLanes(), gel.getBins());

    mainPanel.removeAll();
    mainPanel.add(graph);
    
    remove(buttonBar);
    buttonBar = graph.getButtonBar();
    add(buttonBar, "North");
    
    bottomP.remove(infoBar);
    infoBar = graph.getInfoBar();
    bottomP.add(infoBar, "North");

    validate();
  }

  /**
   * Displays the lane trace, setting the bars to the appropriate value.
   * The trace displays either the first selected lane, or if there are
   * no selected lanes, the first lane in the gel.
   */
  private void showTrace()
  {
    currentView = TRACE;

    // Pick a lane. Try the first one from the selected lanes first,
    // then take the first lane, null if no lanes.
    if(gel.getSelectedLanes().isEmpty())
      {
	//try all lanes.
	if(gel.getLanes().isEmpty())
	  trace.init(null, gel.getLanes());
	else
	  trace.init((Lane) gel.getLanes().dataAt(0), gel.getLanes());
      }
    else
      trace.init((Lane) gel.getSelectedLanes().dataAt(0), 
		 gel.getSelectedLanes());

    mainPanel.removeAll();
    mainPanel.add(trace);
    
    remove(buttonBar);
    buttonBar = trace.getButtonBar();
    add(buttonBar, "North");
    
    bottomP.remove(infoBar);
    infoBar = trace.getInfoBar();
    bottomP.add(infoBar, "North");

    validate();
  }

  /**
   * Displays the analysis output, with the correct bars. The analysis
   * view is initialized with the current gel
   */
  private void showAnalysis()
  {
    currentView = ANALYSIS;

    analysis.init(gel);

    mainPanel.removeAll();
    mainPanel.add(analysis);
    mainPanel.setScrollPosition(0, 0);
    
    remove(buttonBar);
    buttonBar = analysis.getButtonBar();
    add(buttonBar, "North");
    
    bottomP.remove(infoBar);
    infoBar = analysis.getInfoBar();
    bottomP.add(infoBar, "North");

    validate();
  }

  /**
   * Reads the specified files in to the gel. The import filter is
   * retrieved from the dialog used to select the files. Any options
   * for the filter are presented in an options dialog.
   *
   * @param files  the list of files to import.
   */
  private void importFiles(File files[])
  {
    ImportFilter filter = importDialog.getFilter();
    statusLabel.setText("Importing using " + filter.getName());

    // Show the options
    OptionDialog optDialog = new OptionDialog(filter.getOptions(),
					      this,
					      filter.getName()+" Parameters");
    optDialog.setVisible(true);
    if(!optDialog.isCanceled())
      {
	Option[] opts = optDialog.getOptions();

	filter.setOptions(opts);

	// Conveted from lane to lane[] by Philip DeCamp 6/25/2001
	Lane [] ln = null;
	for(int i=0; i < files.length; i++)
	  {
	    try{
		// read in the lane
		ln = filter.readLane(files[i]);
		
		// add it to the gel
		// Begin --Added 6/25/2001 by Philip DeCamp
		for(int j = 0; j < ln.length; j++) 
		    if(ln[j] != null){
			gel.addLane(ln[j]);
			setCutoff(ln[j]);
		    }
		// End --Added 6/25/2001 by Philip DeCamp

		statusLabel.setText(filter.getName() + ": Imported " + 
				    ln[0].getName() + " from "	+
				    files[i].getAbsolutePath() + " (" +
				    (i+1)  + " of " + files.length + 
				    " files)");
	    }
	    catch(IOException ioE){
	      if(ioE instanceof FileNotFoundException)
		errorDialog.showError(ioE);
	      else
		{
		  errorDialog.showError(new IOException("Error on import. " +
							ioE.getMessage()));
		}
	    }
	  }
	
	// adjust the intensity
	gel.setIntensity(gel.getGlobalMaxIntensity());

	// see if the size has been set, if not base the sizes off lane 0
	if(!gelView.isSizeSet())
	  gelView.setGelSizeToMax(0);
	
	statusLabel.setText("Import Complete. Rebuilding gel image...");
	
	gelView.refresh(true);
      } // if(!optDialog.isCanceled())

    statusLabel.setText("Ready.");
  }

  /**
   * Reads the specified file in to the program. The file should be the
   * result of writing out a gel. This is the format used by the program.
   * This is the result of an open operation.
   *
   * @param fileName  the name of a gel file written by the program.
   *
   * @see AFLPcore.Gel#read
   * @see FragmentMap#writeFile
   */
  public void readFile(String fileName)
  {
    try
      {
	statusLabel.setText("Reading " + fileName);

	FileInputStream fs = new FileInputStream(fileName);
	BufferedInputStream bs = new BufferedInputStream(fs);
	DataInputStream inStream = new DataInputStream(bs);

	gel.read(inStream);

	inStream.close();
	fs.close();

	statusLabel.setText("Read completed. Building gel image...");

	// rebuild the gel image
	gelView.refresh(true);
	showGelImage();
	statusLabel.setText("Ready.");
      }
    catch(IOException e)
      {
	statusLabel.setText("Read failed. Ready");
	errorDialog.showError(new IOException("Error while reading in file " +
					      fileName + "." +
					      e.getMessage()));
      }
  }

  /**
   * Writes the gel out to the specified file name. This will write all of
   * the information in the program session. In other words, it saves the
   * session. The file can be restored with the <code>readFile</code>
   * method.
   *
   * @param fileName  the name of the file to write to.
   *
   * @see AFLPcore.Gel#write
   * @see FragmentMap#readFile
   */
  public void writeFile(String fileName)
  {
    try
      {
	statusLabel.setText("Writing " + fileName);

	FileOutputStream fs = new FileOutputStream(fileName);
	BufferedOutputStream bs = new BufferedOutputStream(fs);
	DataOutputStream outStream = new DataOutputStream(bs);
	
	gel.write(outStream);

	outStream.close();
	fs.close();

	statusLabel.setText("Gel saved as " + fileName + ". Ready.");
      }
    catch (IOException e)
      {
	statusLabel.setText("Write failed! Ready.");
	errorDialog.showError(new IOException("Couldn't write file." +
					      e.getMessage()));
      }
  }

  /**
   * Sets the inital cutoff level for the specified lane. The inital cutoff
   * is set to 50% of the max value in the lane, and has only one level.
   * The default cutoff function, defined by <code>FeatureList</code> is
   * used.
   *
   * @param ln  the lane to set the cutoff for.
   */
  private void setCutoff(Lane ln)
  {
    // Set some cutoffs initially: 1 level, start at beginning, 50% of max
    // in lane.
    Cutoff ct = new Cutoff(ln.getMinSize(), 1);
    // Retrieve the default and then clone it so this lane gets it's own
    // cutoff function.
    CutoffFunction ctfn;
    ctfn = (CutoffFunction) FeatureList.getCutoffMgr().getDefault();
    ctfn = (CutoffFunction) ctfn.clone();
    
    if(defaultCutoff == 0)
      defaultCutoff = 0.5*ln.getMaxHeight(ln.getMinSize(), ln.getMaxSize());

    Option[] opts = ctfn.getOptions();
    opts[0].setValue(defaultCutoff);
    ctfn.setOptions(opts);

    ct.setCutoffFunction(ctfn, 0);
    ln.addCutoff(ct);
  }

  /**
   * Prompts the user for a name for a file to be saved, by presenting
   * a file dialog box.
   */
  private void saveFileAs()
  {
    String oldFile = currentFile;
    String oldDir = currentDir;
    {
      fileDialog.setTitle("Save file...");
      fileDialog.setMode(FileDialog.SAVE);

      if(currentFile == null)
	fileDialog.setFile("");
      else
	fileDialog.setFile(currentFile);

      fileDialog.show();
      currentFile = fileDialog.getFile();
      currentDir = fileDialog.getDirectory();
    }
    
    if(currentFile != null)
      {
	writeFile(currentDir + currentFile);
	statusLabel.setText("Saved " + currentDir + currentFile);
      }
    else
      {
	// Canceled, so set things back to the way they were
	currentFile = oldFile;
	currentDir = oldDir;
      }
  }

  /**
   * Prints the specified data to the printer. The user selects
   * the printer using the system dependent print dialog, or equivalent.
   * If the gel is specified, then it will be printed. Or the thumbnails
   * could be printed. The data is specified by comparing the specified value
   * to constants in this class. <b>Note:</b> The gel does not seem to print
   * well at all. It seems to print in black and white, instead of grey
   * scale. The others seem to print fine.
   *
   * @param displayType  corresponds to one of the constants declared in this
   *                     class: <code>GEL</code>, <code>THUMBNAIL</code>, 
   *                     <code>GRAPH</code>, <code>TRACE</code>, or
   *                     <code>ANALYSIS</code>.
   */
  private void print(int displayType)
  {
    // Get the neccessary system resources
    Toolkit tk = Toolkit.getDefaultToolkit();
    PrintJob pJob = tk.getPrintJob(this, "AFLP print stuff", null);

    if(pJob != null)
      {
	Graphics g = pJob.getGraphics();
	Dimension d = pJob.getPageDimension();
	int width = d.width - PAGE_H_ADJUST;
	int height = d.height - PAGE_V_ADJUST - PAGE_HEADER_SPACE;
	
	// Set the font for the graphics
	g.setFont(new Font("SansSerif", Font.PLAIN, 10));
	
	int oldWidth;
	int oldHeight;
	int gWidth;
	int oldBorder;
	int oldSpace;
	String name;
	Bin bin;
	
	switch(displayType)
	  {
	  case GEL:
	    oldBorder = gel.getGelTopBorder();
	    oldWidth = gel.getLaneWidth();
	    oldSpace = gel.getLaneBorder();
	    
	    // create the percent of the gel that the lane width occupies,
	    // as compared to that occupied by the lane borders.
	    double lanePercentage = 3.0/4.0;
	    
	    gel.setLaneWidth((int)(width/gel.getNumLanes()*lanePercentage));
	    gel.setLaneBorder((int) (width/gel.getNumLanes() *
				     (1 - lanePercentage)));
	    
	    GelView pageView = new GelView(gel, this);
	    pageView.setGelLength(height);
	    
	    // create a little frame for the view, just to make it work.
	    Frame tempFrame = new Frame("Print renderer");
	    tempFrame.setLayout(null);
	    tempFrame.setBounds(0, 0, 1, 1);
	    tempFrame.add(pageView);
	    
	    tempFrame.setVisible(true);
	    pageView.paint(g);
	    tempFrame.setVisible(false);
	    
	    gel.setLaneWidth(oldWidth);
	    gel.setLaneBorder(oldSpace);
	    break;
	  case THUMBNAIL:
	    // figure out how many pages we need.
	    DataList lanes = thumbnail.getLanes();
	    int thumbWidth = thumbnail.getThumbnailSize().width;
	    int thumbHeight = thumbnail.getThumbnailSize().height;
	    
	    int numThumbnails = lanes.size();
	    int numPerRow = width/thumbWidth;
	    int numPerColumn = height/thumbHeight;
	    int numPerPage = numPerRow * numPerColumn;
	    
	    if(numPerPage < 1)
	      {
		throw new IllegalArgumentException("Thumbnail won't fit on" +
						   " a page!");
		// return; not reached
	      }
	    
	    // draw some header info
	    bin = thumbnail.getBin();
	    name = bin.getName();
	    if(!name.equals(""))
	      name = name + ": ";
	    
	    g.drawString(name + bin.getLocation() + "+/-" + bin.getRange() + 
			 "  " + bin.getScoreInfo()[0],
			 0, height + PAGE_HEADER_SPACE);
	    
	    thumbnail.setViewWidth(width);
	    
	    if(numPerPage >= numThumbnails)
	      {
		thumbnail.print(g);
	      }
	    else
	      {
		int firstLane = 0;
		DataList tempLanes;
		while(firstLane < numThumbnails)
		  {
		    tempLanes = new DataList();
		    for(int i=firstLane; i < (firstLane + numPerPage); i++)
		      tempLanes.addData(lanes.dataAt(i));
		    
		    thumbnail.setLanes(tempLanes);
		    thumbnail.print(g);
		    
		    // get the next page
		    g.dispose();
		    g = pJob.getGraphics();
		    firstLane += numPerPage;
		  }
		
		thumbnail.setLanes(lanes);
	      }
	    break;
	  case GRAPH:
	    bin = graph.getBin();
	    
	    oldWidth = graph.getWidth();
	    oldHeight= graph.getHeight();
	    gWidth = graph.getGraphWidth();
	    
	    graph.setWidth(width);
	    graph.setHeight(height);
	    graph.setGraphWidth(width - 20);
	    
	    graph.paint(g);
	    
	    graph.setWidth(oldWidth);
	    graph.setHeight(oldHeight);
	    graph.setGraphWidth(gWidth);
	    
	    name = bin.getName();
	    if(!name.equals(""))
	      name = name + ": ";
	    
	    g.drawString(name + bin.getLocation() + "+/-" + bin.getRange() + 
			 "  " + bin.getScoreInfo()[0],
			 0, height + PAGE_HEADER_SPACE);
	    break;
	  case TRACE:
	    oldWidth = trace.getWidth();
	    oldHeight = trace.getHeight();

	    trace.setWidth(width);
	    trace.setHeight(height);

	    trace.print(g);

	    trace.setWidth(oldWidth);
	    trace.setHeight(oldHeight);
	    break;
	  case ANALYSIS:
	    TextPrinter textP = new TextPrinter(pJob);
	    textP.printString(analysis.getText(), g);
	    break;
	  }
	
	g.dispose();
	pJob.end();

	statusLabel.setText("Done Printing. Ready.");
      }
  }

  /**
   * This will export the specified data view out to a file. A dialog
   * box will be presented to allow the user to select a file name. 
   * Only the gel and analysis currently support exporting. The gel is
   * exported as a PNG file, which can be read by several graphics programs.
   * The analysis is exported as a simple text file.
   *
   * @param view   corresponds to one of the constants declared in this
   *               class: <code>GEL</code>, <code>THUMBNAIL</code>, 
   *               <code>GRAPH</code>, <code>TRACE</code>, or
   *               <code>ANALYSIS</code>. Only <code>GEL</code> and
   *               <code>ANALYSIS</code> are currently supported.
   */
  public void exportView(int view)
  {
    String exportFile;
    String exportDir;
    String oldDir = currentDir;
    {
      fileDialog.setTitle("Export file...");
      fileDialog.setMode(FileDialog.SAVE);
      
      fileDialog.setFile("");
      
      fileDialog.show();
      exportFile = fileDialog.getFile();
      exportDir = fileDialog.getDirectory();
    }
    
    if(exportFile == null)
      {
	// Canceled, so set things back to the way they were
	currentDir = oldDir;
      }
    else
      {
	try
	  {
	    FileOutputStream     fs;
	    BufferedOutputStream bs;

	    switch(view) {
	    	case GEL:
		    // Check the file extension.		      
		    int     nameLength   = exportFile.length();

		    if(nameLength < 4) {
			exportFile += ".png";
		    } else {
			String temp = exportFile.substring(nameLength - 4);
			if(!temp.equalsIgnoreCase(".png")) {
			    exportFile += ".png";
			}
		    }
		    
		    fs = new FileOutputStream(exportDir + exportFile);
		    bs = new BufferedOutputStream(fs);

		    DataOutputStream outStream = new DataOutputStream(bs);
		
		    gel.writePNG(outStream);
	    
		    outStream.close();
		    statusLabel.setText("Exported gel to " + exportDir + 
					exportFile + ". Ready.");
		    bs.close();
		    fs.close();
		    break;
	    	case ANALYSIS:
		    fs = new FileOutputStream(exportDir + exportFile);
		    bs = new BufferedOutputStream(fs);

		    byte output[] = analysis.getText().getBytes();
		    bs.write(output, 0, output.length);
		    bs.flush();
		    statusLabel.setText("Exported analysis to " + exportDir + 
					exportFile + ". Ready.");
		    bs.close();
		    fs.close();
		    break;
	    	default:
		    statusLabel.setText("Exporting this type is not " +
					"supported. Ready.");
	      }
	  }
	catch (IOException except)
	  {
	    errorDialog.showError(new IOException("Couldn't write file." +
						  except.getMessage()));
	  }
      }
  }	    

  /**
   * Displays an about dialog box for the program.
   */
  protected void showAbout()
  {
    AboutDialog aboutD = new AboutDialog(this, "About Genographer");
    aboutD.show();
  }

  /**
   * Handles the events from buttons and menu items. 
   */
  public void actionPerformed(ActionEvent e)
  {
    try{
      if((e.getSource() == openItem) || 
	 (e.getSource() == openButton))
	{	
	  fileDialog.setTitle("Open file...");
	  fileDialog.setMode(FileDialog.LOAD);
	  fileDialog.setDirectory(currentDir);
	  fileDialog.setFile("");
	  fileDialog.show();
	  
	  currentFile = fileDialog.getFile();
	  currentDir = fileDialog.getDirectory();
	  
	  if(currentFile != null)
	    {
	      readFile(currentDir + currentFile);
	    }
	}
      else if((e.getSource() == saveItem) ||
	      (e.getSource() == saveButton))
	{
	  if(currentFile == null)
	    saveFileAs();
	  else
	    {
	      writeFile(currentDir + currentFile);
	    }
	}
      else if(e.getSource() == saveAsItem)
	{
	  saveFileAs();
	}
      else if(e.getSource() == importItem)
	{
	  importDialog.setVisible(true);
	  File files[] = importDialog.getFiles();
	  if(files != null)
	    importFiles(files);
	}
      else if((e.getSource() == newItem) ||
	      (e.getSource() == newButton))
	{
	  gel = new Gel();
	  gelView = new GelView(gel, this);
	  switchTo("Gel");
	}
      else if((e.getSource() == printItem) ||
	      (e.getSource() == printButton))
	{
	  print(currentView);
	}
      else if(e.getSource() == exportItem)
	{
	  exportView(currentView);
	}
      else if(e.getSource() == exitItem)
	{
	  endProgram();
	}
      else if(e.getSource() == viewGelItem)
	{
	  switchTo("Gel");
	}
      else if(e.getSource() == viewThumbnailItem)
	{
	  switchTo("Thumbnail");
	}
      else if(e.getSource() == viewGraphItem)
	{
	  switchTo("Graph");
	}
      else if(e.getSource() == viewTraceItem)
	{
	  switchTo("Trace");
	}
      else if(e.getSource() == viewAnalysisItem)
	{
	  switchTo("Analysis");
	}
      else if(e.getSource() == gelRedrawItem)
	{
	  if(currentView == GEL)
	    gelView.refresh(true);
	}
      else if(e.getSource() == binRedrawItem)
	{
	  switch(currentView)
	    {
	    case THUMBNAIL:
	      thumbnail.refresh();
	      break;
	    case GRAPH:
	      graph.refresh();
	      break;
	    }
	}
      else if(e.getSource() == helpItem)
	{
	  ProgOptions.showHelp(HELP_FILE);
	}
      else if(e.getSource() == aboutItem)
	{
	  showAbout();
	}
      else if(e.getSource() == laneDeleteItem)
	{
	  DataList lanesToDelete = gel.getSelectedLanes();

	  // Change it into an array
	  if(!lanesToDelete.isEmpty())
	    {
	      // this will give us the list, since once we start deleting,
	      // the list pointed to by lanesToDelete will start to change
	      // to.
	      Lane toDelete[] = new Lane[lanesToDelete.size()];
	      for(int i=0; i < toDelete.length; i++)
		toDelete[i] = (Lane) lanesToDelete.dataAt(i);

	      for(int i=0; i < toDelete.length; i++)
		gel.removeLane(toDelete[i]);
	    }

	  // update the display
	  gelView.refresh(true);
	}
      else
	{
	  // Handle events from the operations
	  String opName;
	  opName = getName(laneOpItem, e);
	  if(opName != null)
	    {
	      // lane operation
	      LaneOperation laneOp;
	      laneOp = (LaneOperation) FeatureList.getLaneOpMgr().get(opName);
	      
	      // see if we should use selected lanes or not.
	      if(gel.getSelectedLanes().isEmpty())
		laneOp.doLaneOp(gel.getLanes());
	      else
		laneOp.doLaneOp(gel.getLanes());
	    }

	  opName = getName(binOpItem, e);
	  if(opName != null)
	    {
	      // bin operation
	      BinOperation binOp;
	      binOp = (BinOperation) FeatureList.getBinOpMgr().get(opName);

	      if(binOp.isMultiBin())
		binOp.doBinOp(gel.getBins());
	      else
		{
		  if(gelView.getCurrentBin() != null)
		    binOp.doBinOp(gelView.getCurrentBin());
		}
	    }

	  opName = getName(gelOpItem, e);
	  if(opName != null)
	    {
	      // gel operation
	      GelOperation gelOp;
	      gelOp = (GelOperation) FeatureList.getGelOpMgr().get(opName);
	      if(gel != null)
		gelOp.doGelOp(gel);
	    }
	}

      // Refresh the current display. It may not be neccessary, but 
      // it may also be, so take the conservative approach and do it.
      switch(currentView)
	  {
	  case GEL:
	    gelView.refresh(true);
	  case THUMBNAIL:
	    thumbnail.refresh();
	    break;
	  case GRAPH:
	    graph.refresh();
	    break;
	  }

    }
    catch(Throwable error)
      {
	// Take care of any error that might occur. All the errors come
	// from the event thread, which will go through here some of the
	// time.
	errorDialog.showError(error);
      }
  }

  /**
   * exits the program when the window is closed.
   */
  public void windowClosing(WindowEvent e)
  {
    endProgram();
  }

  /**
   * Clean up before the program quits.
   */
  private void endProgram()
  {
    // What kind of clean up do we need to do
    System.exit(0);
  }

  /**
   * Gives the name associated with a given action event. The 
   * menu items come from the <code>FeatureList</code>. The action
   * event is compared to all of the items, to see if one of them
   * originated it. If so, then the name of that item is returned.
   * 
   * @param items  menu items that the event <code>e</code> should be
   *               compared to.
   * @param e      the event for which we wish to obtain a name.
   *
   * @return  either the name (label) of the item that generated the event
   *          or <code>null</code> if none of the items produced the event.
   */
  private String getName(MenuItem items[], ActionEvent e)
  {
    String name = null;
    for(int i=0; i < items.length; i++)
      if(e.getSource() == items[i])
	name = items[i].getLabel();

    return name;
  }

  /**
   * Does a lot of laying out of the window, and menu construction. 
   */
  private void programInitialize()
  {
    // Make all the other things.

    ControlBar controls = new ControlBar(this);

    //=================== Make the menu ==================
    MenuBar menuBar = new MenuBar();

    // File menu
    Menu fileMenu = new Menu("File");
    newItem = new MenuItem("New");
    openItem = new MenuItem("Open");
    saveItem = new MenuItem("Save");
    saveAsItem = new MenuItem("Save As...");
    importItem = new MenuItem("Import...");
    exportItem = new MenuItem("Export...");
    printItem = new MenuItem("Print...");
    exitItem = new MenuItem("Exit");

    newItem.addActionListener(this);
    openItem.addActionListener(this);
    saveAsItem.addActionListener(this);
    saveItem.addActionListener(this);
    importItem.addActionListener(this);
    exportItem.addActionListener(this);
    printItem.addActionListener(this);
    exitItem.addActionListener(this);
    
    fileMenu.add(newItem);
    fileMenu.add(openItem);
    fileMenu.add(saveItem);
    fileMenu.add(saveAsItem);
    fileMenu.add(new MenuItem("-"));
    fileMenu.add(importItem);
    fileMenu.add(exportItem);
    fileMenu.add(new MenuItem("-"));
    fileMenu.add(printItem);
    fileMenu.add(new MenuItem("-"));
    fileMenu.add(exitItem);
    menuBar.add(fileMenu);

    // Lane menu
    Menu laneMenu = new Menu("Lane");
    laneDeleteItem = new MenuItem("Delete Marked Lanes.");
    
    laneDeleteItem.addActionListener(this);

    laneMenu.add(laneDeleteItem);
    menuBar.add(laneMenu);

    laneOpItem = addMenu(laneMenu, FeatureList.getLaneOpMgr());

    // Bin menu
    Menu binMenu = new Menu("Bin");
    binRedrawItem = new MenuItem("Redraw Bin");

    binRedrawItem.addActionListener(this);

    binMenu.add(binRedrawItem);
    menuBar.add(binMenu);

    binOpItem = addMenu(binMenu, FeatureList.getBinOpMgr());
    
    // Gel menu 
    Menu gelMenu = new Menu("Gel");
    gelRedrawItem = new MenuItem("Redraw Gel");
   
    gelRedrawItem.addActionListener(this);

    gelMenu.add(gelRedrawItem);
    menuBar.add(gelMenu);

    gelOpItem = addMenu(gelMenu, FeatureList.getGelOpMgr());

    // View menu
    Menu viewMenu = new Menu("View");
    viewGelItem = new MenuItem("Gel");
    viewThumbnailItem = new MenuItem("Thumbnail");
    viewGraphItem = new MenuItem("Graph");
    viewTraceItem = new MenuItem("Trace");
    viewAnalysisItem = new MenuItem("Analysis");

    viewGelItem.addActionListener(this);
    viewThumbnailItem.addActionListener(this);
    viewGraphItem.addActionListener(this);
    viewTraceItem.addActionListener(this);
    viewAnalysisItem.addActionListener(this);

    viewMenu.add(viewGelItem);
    viewMenu.add(viewThumbnailItem);
    viewMenu.add(viewGraphItem);
    viewMenu.add(viewTraceItem);
    viewMenu.add(viewAnalysisItem);
    menuBar.add(viewMenu);

    // Help menu
    Menu helpMenu = new Menu("Help");
    helpItem = new MenuItem("Contents...");
    aboutItem = new MenuItem("About");

    helpItem.addActionListener(this);
    aboutItem.addActionListener(this);

    helpMenu.add(helpItem);
    helpMenu.add(aboutItem);
    menuBar.add(helpMenu);

    //Add labels to status/info bars
    statusLabel = new Label("Copyright (c) Montana" +
			    " State University 1998. Licensed under GNU" +
			    " General Public License. See help for details.");

    statusBar.setLayout(null);
    statusBar.add(statusLabel);
    statusLabel.setBounds(5, 3, 800, 18);

    mainPanel = new ScrollPane();
    mainPanel.add(gelView);
    add(mainPanel, "Center");
    mainPanel.setVisible(true);

    // Add all of the components
    setMenuBar(menuBar);

    add(controls, "West");
    controls.setBounds(0, 0, 89, 200);

    add(buttonBar, "North");
    buttonBar.setBounds(0, 0, getSize().width, 32);

    bottomP.setLayout(new BorderLayout());
    bottomP.add(infoBar, "North");
    bottomP.add(statusBar, "South");
    // Different width b/c second one has bottom border
    infoBar.setBounds(0, 0, getSize().width, BAR_HEIGHT);
    statusBar.setBounds(0, BAR_HEIGHT + 1, getSize().width, BAR_HEIGHT + 1);
    
    add(bottomP, "South");    
  }

  /**
   * Adds <code>Operation</code>s from the specified <code>Manager</code>
   * to the specified menu and stores the menu items. If the manager
   * contains anything, the mehtod will add a separator to the menu before
   * it adds any of the items.
   *
   * @param menu    the menu to add the items to.
   * @param mgr     the manager to retrieve the operations from
   *
   * @return an array containing all of the menu items added.
   *
   * @see AFLPcore.Manager
   */
  public MenuItem[] addMenu(Menu menu, Manager mgr)
  {
    String names[] = mgr.getNames();
    MenuItem items[] = new MenuItem[names.length];

    if(names.length > 0)
      menu.add(new MenuItem("-"));

    for(int i=0; i < names.length; i++)
    {
      items[i] = new MenuItem(names[i]);
      menu.add(items[i]);
      items[i].addActionListener(this);
    }

    return items;
  }

  // ==================Unused methods required by interfaces=================
  /**Unused*/public void windowOpened(WindowEvent e) {}
  /**Unused*/public void windowClosed(WindowEvent e) {}
  /**Unused*/public void windowIconified(WindowEvent e) {}
  /**Unused*/public void windowDeiconified(WindowEvent e) {}
  /**Unused*/public void windowActivated(WindowEvent e) {}
  /**Unused*/public void windowDeactivated(WindowEvent e) {}
}
