//=====================================================================
// File: MultiFileDialog.java
// Class: MultiFileDialog
// Package: AFLPgui
//
// Author: James J. Benham
// Date: January 4, 1999
// Contact: james_benham@hmc.edu
//
// Genographer v1.0 - 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.Button;
import java.awt.Choice;
import java.awt.Dialog;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.Label;
import java.awt.List;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.util.Vector;
import AFLPcore.FeatureList;
import AFLPcore.ImportFilter;
/**
* This class allows the user to select multiple files. It provides
* a directory strucute which can be navigated on one side and a list
* of files to open on the other.
*
* @author James J. Benham
* @version 1.1.0
* @date January 4, 1999
*/
public class MultiFileDialog extends Dialog implements ActionListener,
WindowListener
{
// Layout Constants
private static final int BUTTON_WIDTH = 70;
private static final int BUTTON_HEIGHT = 22;
private static final int LIST_HEIGHT = 250;
private static final int DIRECTORY_WIDTH = 180;
private static final int IMPORT_WIDTH = 180;
private static final int COMPONENT_HSPACE = 10;
private static final int COMPONENT_VSPACE = 5;
private static final int H_INSET = 5;
private static final int V_INSET = 25;
private static final int PATH_HEIGHT = 16;
private static final int TYPE_LABEL_WIDTH = 80;
private static final int FILE_TYPE_WIDTH = 200;
private static final int FILE_TYPE_HEIGHT = 22;
private static final int PATH_WIDTH = (IMPORT_WIDTH + DIRECTORY_WIDTH +
BUTTON_WIDTH);
private static final int WIDTH = (H_INSET + DIRECTORY_WIDTH + BUTTON_WIDTH +
IMPORT_WIDTH + BUTTON_WIDTH +
5*COMPONENT_HSPACE);
private static final int HEIGHT = (V_INSET + 2*PATH_HEIGHT +
3*COMPONENT_VSPACE + LIST_HEIGHT +
FILE_TYPE_HEIGHT + 5);
// GUI components
private Label pathLabel;
private List directoryList;
private List openList;
private Choice fileType;
private Button addButton;
private Button removeButton;
private Button addAllButton;
private Button clearButton;
private Button importButton;
private Button cancelButton;
private String parentItem;
private String directory;
private String file;
private File constantFiles[]; // files to always display, like hard drives
private File currentFile;
private File fileList[];
private Vector importFiles;
private File importList[];
private ImportFilter filter;
/**
* Creates a new dialog with the specified parameters
*
* @param parent the owner of the dialog
*/
public MultiFileDialog(Frame parent)
{
this(parent, "");
}
/**
* Creates a new dialog with the specified parameters
*
* @param parent the owner of the dialog
* @param title the name of the dialog box
*/
public MultiFileDialog(Frame parent, String title)
{
super(parent, title, true);
importFiles = new Vector();
// find the constantFiles. For Windows/DOS drives
try {
File [] fileRoots = File.listRoots();
if(fileRoots.length > 1) {
constantFiles = fileRoots;
} else {
// Don't bother if there is only one root
constantFiles = new File[0];
}
} catch(NoSuchMethodError e) {
// Ok, we're on an older version of Java, the listRoots method
// was added for Java version 1.2. Just go back to the old
// way of doing things. This may cause Windows to bring up annoying
// dialogs, but deal.
System.out.println("Older version of Java detected. Upgrading to" +
" Java 2 or later is recommended.");
Vector tempFileList = new Vector();
File tempFile;
for(char letter = 'A'; letter <= 'Z'; letter++) {
tempFile = new File("" + letter + ":\\");
if(tempFile.exists())
tempFileList.addElement(tempFile);
}
constantFiles = new File[tempFileList.size()];
for(int i=0; i < tempFileList.size(); i++)
constantFiles[i] = (File) tempFileList.elementAt(i);
}
// set the directory by using a normal file dialog and getting
// its directory;
directory = ".";
// Find the real name of "."
File dirFile = new File(".");
directory = dirFile.getAbsolutePath();
if(directory.endsWith("."))
directory = directory.substring(0, directory.length() - 1);
parentItem = ".. / (move up a directory)";
addWindowListener(this);
componentLayout();
}
/**
* Gives a list of files selected by the user.
*
* @return the files to use.
*/
public File[] getFiles()
{
return importList;
}
/**
* Gives the filter selected in the dialog box.
*
* @return the filter to read in the files selected.
*/
public ImportFilter getFilter()
{
return filter;
}
/**
* Controls the visibility of this dialog. If it is set to be visible,
* it will perform some initialization stuff, like clearing the old
* list of files.
*
* @param b true
to display the dialog.
*/
public void setVisible(boolean b)
{
if(b)
{
// initialize some of the stuff
openList.removeAll();
importFiles = new Vector();
currentFile = new File(directory);
String tempSt = currentFile.getAbsolutePath();
if(tempSt.endsWith("."))
tempSt = tempSt.substring(0, tempSt.length() - 1);
directory = tempSt;
currentFile = new File(directory);
displayDirectory(currentFile);
}
// let the superclass do the rest
super.setVisible(b);
}
/**
* Displays the specified directory on the left side of the dialog. All of
* the files and subdirectories in the directory will be entered in the
* list. It will also enter all of the files/subdirectories into an
* array so they can be accessed by looking at the index of a selected
* item in the list.
*
* @param dir the directory to display.
*/
protected void displayDirectory(File dir)
{
File temp;
if(!currentFile.isDirectory())
System.err.println("Invalid directory specification.");
directoryList.removeAll();
directoryList.add(parentItem);
String contents[] = dir.list();
fileList = new File[contents.length + constantFiles.length];
try
{
directory = dir.getCanonicalPath();
}
catch(java.io.IOException e)
{
directory = "IO Error";
}
pathLabel.setText("Current Path: " + directory);
directoryList.setVisible(false);
for(int i=0; i < constantFiles.length; i++)
{
fileList[i] = constantFiles[i];
directoryList.add("[" + constantFiles[i].getPath() + "]");
}
File listing[] = new File[contents.length];
for(int i=0; i < contents.length; i++)
{
listing[i] = new File(directory + File.separator + contents[i]);
if(listing[i].isDirectory())
contents[i] += "/";
}
quicksort(contents, listing, 0, contents.length - 1);
for(int i=0; i < contents.length; i++)
{
fileList[i + constantFiles.length] = listing[i];
directoryList.add(contents[i]);
}
directoryList.setVisible(true);
}
/**
* This will sort the file listing. It uses the compare method to
* compare two entries, taking into account directories, which are
* placed first. It uses the standard quicksort algorithm. The code
* was copied from an example of Robert Keller. The only difference
* is that is sorts two arrays which are kept in parrallel and
* comparisons occur between arrays as well as within the arrays.
*
* @param str the names of all of the files, as they will appear in
* the list
* @param files a list of file objects that represents the names in
* str
Used to see if a name represents
* a directory or not.
* @param low the bottom index for the portion of the array to be sorted.
* @param high the upper index for the portion of the array to be sorted.
*/
private static void quicksort(String str[], File files[], int low, int high)
{
String tempStr;
File tempFile;
if( low >= high )
return; // basis case, <= 1 element
String pivotStr = str[(low + high)/2]; // select pivot
File pivotFile = files[(low + high)/2];
// shift elements <= pivot to bottom
// shift elements >= pivot to top
int i = low-1;
int j = high+1;
for( ; ; )
{ // find two elements to exchange
do { i++; } // slide i up
while( compare(str[i], files[i], pivotStr, pivotFile) < 0);
do { j--; } // slide j down
while( compare(str[j], files[j], pivotStr, pivotFile) > 0);
if( i >= j ) // if sliders meet or cross
break;
// swap the two elementes in both arrays
tempStr = str[i];
str[i] = str[j];
str[j] = tempStr;
tempFile = files[i];
files[i] = files[j];
files[j] = tempFile;
}
quicksort(str, files, low, i-1); // sort lower half
quicksort(str, files, j+1, high); // sort upper half
}
/**
* Compare two string/file pairs. This is a standard ordering comparison
* used by the sorting function. Directories are "less than" standard
* files. If the two pairs are of the same type (both directories or
* both files) then their names are compared using standard string
* comparisons. File objects are used to determine if a file is a standard
* file or a directroy.
*
*@param str1 the name of the first file
*@param f1 a file or directory whose name is str1
*@param str2 the name of the second file/direcotry
*@param f2 the second file.
*
*@return a negative value if the first pair is less than the second,
* a positive value if it is greater, and zero if they are equal.
*/
private static int compare(String str1, File f1, String str2, File f2)
{
if(!f1.isDirectory() && !f2.isDirectory())
return str1.compareTo(str2);
else if(!f1.isDirectory()) // but j.isDirecory
return 1;
else if(!f2.isDirectory())
return -1;
else
return str1.compareTo(str2);
}
/**
* Adds an entry to the list of files to be opened/imported. The entry
* is specified by the index. It will be shown in the list and in
* an internal list which will contain the actual file reference.
*
* @param selection the index of the file in the array of currently
* displayed files.
*/
protected void addFile(int selection)
{
if(selection >= 0) // make sure something is actually selected
{
openList.add(fileList[selection - 1].getName());
importFiles.addElement(fileList[selection - 1]);
}
}
/**
* Adds entries to the list of files to be opened/imported. The entry
* is specified by the index. It will be shown in the list and in
* an internal list which will contain the actual file reference.
*
* @param selection a list of indicies of files in the array of currently
* displayed files.
*/
protected void addFile(int selection[])
{
openList.setVisible(false);
for(int i=0; i < selection.length; i++)
{
addFile(selection[i]);
}
openList.setVisible(true);
}
/**
* Removes the specified file from the list of files to open/import.
*
* @param index the index of the file in the list.
*/
protected void removeFile(int index)
{
if(index >= 0) // make sure something is actually selected
{
openList.remove(index);
importFiles.removeElementAt(index);
}
}
/**
* Handle the button clicks as well as double clicks on the list.
* Double clicking on a directory will open it and double-clicking on
* a file will add it to the list to import.
*/
public void actionPerformed(ActionEvent e)
{
if(e.getSource() == directoryList)
{
int index = directoryList.getSelectedIndex();
// look for moving up to the parent first
if(index == 0)
{
if(currentFile.getParent() != null)
currentFile = new File(currentFile.getParent());
displayDirectory(currentFile);
}
else
{
// see what we selected, subtract 1 form the index since the
// file list does not include the parent ".."
if(fileList[index - 1].isDirectory())
{
currentFile = fileList[index - 1];
displayDirectory(currentFile);
}
else
{
// it must be a file to read in, so add it to the list
addFile(index);
}
}
}
else if(e.getSource() == addButton)
{
addFile(directoryList.getSelectedIndex());
}
else if(e.getSource() == removeButton)
{
removeFile(openList.getSelectedIndex());
}
else if(e.getSource() == addAllButton)
{
openList.setVisible(false);
for(int i=(1 + constantFiles.length); i <= fileList.length; i++)
addFile(i);
openList.setVisible(true);
}
else if(e.getSource() == clearButton)
{
importFiles = new Vector();
openList.removeAll();
}
else if(e.getSource() == importButton)
{
// get the files
importList = new File[importFiles.size()];
for(int i=0; i < importList.length; i++)
importList[i] = (File) importFiles.elementAt(i);
// set the filter
filter = (ImportFilter)
FeatureList.getImportMgr().get(fileType.getSelectedItem());
dispose();
setVisible(false);
}
else if(e.getSource() == cancelButton)
{
importList = null;
dispose();
setVisible(false);
}
}
/**
* Closes the dialog and is equivalent to clicking cancel.
*/
public void windowClosing(WindowEvent e)
{
importList = null;
dispose();
setVisible(false);
}
/**
* Lays out all of the components for the dialog box
*/
private void componentLayout()
{
setLayout(null);
setSize(WIDTH, HEIGHT);
setResizable(false);
int x = H_INSET;
int y = V_INSET;
pathLabel = new Label("Current Path: ");
add(pathLabel);
pathLabel.setBounds(x + COMPONENT_HSPACE, y, PATH_WIDTH, PATH_HEIGHT);
x += COMPONENT_HSPACE;
y += PATH_HEIGHT + COMPONENT_VSPACE;
Label filesLabel = new Label("Files: ");
add(filesLabel);
filesLabel.setBounds(x, y, DIRECTORY_WIDTH, PATH_HEIGHT);
y += PATH_HEIGHT;
directoryList = new List();
add(directoryList);
directoryList.addActionListener(this);
directoryList.setBounds(x, y, DIRECTORY_WIDTH, LIST_HEIGHT);
y += LIST_HEIGHT + COMPONENT_VSPACE;
Label fileTypeLabel = new Label("Files of Type:");
add(fileTypeLabel);
fileTypeLabel.setBounds(x, y, TYPE_LABEL_WIDTH, FILE_TYPE_HEIGHT);
//=============== File type Choice ====================
fileType = new Choice();
// add the items
String types[] = FeatureList.getImportMgr().getNames();
for(int i=0; i < types.length; i++)
fileType.add(types[i]);
fileType.select(FeatureList.getImportMgr().getDefaultName());
add(fileType);
fileType.setBounds(x + TYPE_LABEL_WIDTH, y, FILE_TYPE_WIDTH,
FILE_TYPE_HEIGHT);
x += DIRECTORY_WIDTH + COMPONENT_HSPACE;
y = y - LIST_HEIGHT - COMPONENT_VSPACE;
addButton = new Button("Add");
add(addButton);
addButton.addActionListener(this);
addButton.setBounds(x, y, BUTTON_WIDTH, BUTTON_HEIGHT);
y += COMPONENT_VSPACE + BUTTON_HEIGHT;
removeButton = new Button("Remove");
add(removeButton);
removeButton.addActionListener(this);
removeButton.setBounds(x, y, BUTTON_WIDTH, BUTTON_HEIGHT);
y += COMPONENT_VSPACE + BUTTON_HEIGHT;
addAllButton = new Button("Add All");
add(addAllButton);
addAllButton.addActionListener(this);
addAllButton.setBounds(x, y, BUTTON_WIDTH, BUTTON_HEIGHT);
y += COMPONENT_VSPACE + BUTTON_HEIGHT;
clearButton = new Button("Clear");
add(clearButton);
clearButton.addActionListener(this);
clearButton.setBounds(x, y, BUTTON_WIDTH, BUTTON_HEIGHT);
x += BUTTON_WIDTH + COMPONENT_HSPACE;
y = V_INSET + PATH_HEIGHT + COMPONENT_VSPACE;
Label importLabel = new Label("Import Files:");
add(importLabel);
importLabel.setBounds(x, y, IMPORT_WIDTH, PATH_HEIGHT);
y += PATH_HEIGHT;
openList = new List();
add(openList);
openList.setBounds(x, y, IMPORT_WIDTH, LIST_HEIGHT);
x += IMPORT_WIDTH + COMPONENT_HSPACE;
importButton = new Button("Import");
add(importButton);
importButton.addActionListener(this);
importButton.setBounds(x, y, BUTTON_WIDTH, BUTTON_HEIGHT);
y += BUTTON_HEIGHT + COMPONENT_VSPACE;
cancelButton = new Button("Cancel");
add(cancelButton);
cancelButton.addActionListener(this);
cancelButton.setBounds(x, y, BUTTON_WIDTH, BUTTON_HEIGHT);
}
// ==================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) {}
}