/* BEGIN_HEADER Java TreeView
*
* $Author: alokito $
* $RCSfile: ViewFrame.java,v $
* $Revision: 1.37 $
* $Date: 2009-08-26 11:48:27 $
* $Name: $
*
* This file is part of Java TreeView
* Copyright (C) 2001-2003 Alok Saldanha, All Rights Reserved. Modified by Alex Segal 2004/08/13. Modifications Copyright (C) Lawrence Berkeley Lab.
*
* This software is provided under the GNU GPL Version 2. In particular,
*
* 1) If you modify a source file, make a comment in it containing your name and the date.
* 2) If you distribute a modified version, you must do it under the GPL 2.
* 3) Developers are encouraged but not required to notify the Java TreeView maintainers at alok@genome.stanford.edu when they make a useful addition. It would be nice if significant contributions could be merged into the main distribution.
*
* A full copy of the license can be found in gpl.txt or online at
*
* http://www.gnu.org/licenses/gpl.txt *
* END_HEADER
*/
package edu.stanford.genetics.treeview;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
import edu.stanford.genetics.treeview.core.FileMru;
import edu.stanford.genetics.treeview.core.HeaderFinder;
import edu.stanford.genetics.treeview.model.DataModelWriter;
import edu.stanford.genetics.treeview.model.ReorderedDataModel;
/**
* Any frame that wants to contain MainPanels must extend this.
*
* @author Alok Saldanha <alok@genome.stanford.edu>
* @version @version $Revision: 1.37 $ $Date: 2009-08-26 11:48:27 $
*/
public abstract class ViewFrame extends JFrame implements Observer {
// must override in subclass...
/**
* This is to ensure that we can observe the MainPanels when they change.
*
* @param observable The MainPanel or other thing which changed.
* @param object Generally null.
*/
public abstract void update(Observable observable, Object object);
/**
* This routine should return any instances of the plugin
* of the indicated name (i.e. it will loop over all instantiated
* MainPanel calling their getName() properties, find all that are
* equal to the indicated string, and return all matching ones
*/
public abstract MainPanel[] getMainPanelsByName(String name);
/**
*
* @return all mainPanels managed by this viewFrame
*/
public abstract MainPanel[] getMainPanels();
/**
* Sets up a <code>FileMru</code> using a particular config node.
* This <code>FileMru</code> can later be edited or used to show a mru menu.
*
* @param fileMruNode Node which will be bound to the FileMru
*/
protected void setupFileMru(ConfigNode fileMruNode) {
fileMru = new FileMru();
fileMru.bindConfig(fileMruNode);
try {
fileMru.removeMoved();
} catch (Exception e) {
LogBuffer.println("problem checking MRU in ViewFrame constructor: " + e.toString());
e.printStackTrace();
}
fileMru.addObserver(this);
fileMru.notifyObservers();//sends us message
}
/**
* Centers the frame onscreen.
*
* @param rectangle A rectangle describing the outlines of the screen.
*/
private void center(Rectangle rectangle) {
Dimension dimension = getSize();
setLocation((rectangle.width - dimension.width) / 3 + rectangle.x, (rectangle.height - dimension.height) / 3 + rectangle.y);
}
/** Determines dimension of screen and centers frame onscreen. */
public void centerOnscreen() {
// trying this for mac...
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension dimension = toolkit.getScreenSize();
Rectangle rectangle = new Rectangle(dimension);
// XXX should drag out of global config
setSize(rectangle.width * 3 / 4, rectangle.height * 4 / 5);
center(rectangle);
}
/** Sets a listener on self, so taht we can grab focus when activated, and close ourselves when closed.*/
private void setupWindowListener() {
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
addWindowListener(
new WindowAdapter() {
public void windowActivated(WindowEvent windowEvent) {
setWindowActive(true);
}
public void windowClosing(WindowEvent windowEvent) {
closeWindow();
}
public void windowDeactivated(WindowEvent windowEvent) {
setWindowActive(false);
}
});
}
/**
* Constructor for the ViewFrame object
* Sets title and window listeners
*
* @param title Title for the viewframe.
*/
public ViewFrame(String title) {
super(title);
setupWindowListener();
}
/** construts an untitled <code>ViewFrame</code> */
public ViewFrame() {
super();
setupWindowListener();
}
/**
* Keep track of when active, so that clicks don't get passed through too much.
*
* @param flag The new windowActive value
*/
protected void setWindowActive(boolean flag) {
windowActive = flag;
}
/**
* Keep track of when active, so that clicks don't get passed through too much.
*
* @return True if window is active.
*/
public boolean windowActive() {
return windowActive;
}
/** Keep track of when active, so that clicks don't get passed through too much. */
private boolean windowActive;
/** close window cleanly.
* causes documentConfig to be stored.
*/
public void closeWindow() {
try {
DataModel dataModel = getDataModel();
if (dataModel != null) {
if (dataModel.getModified()) {
int option = JOptionPane.showConfirmDialog(this, "DataModel is modified. Do you wish to save?");
switch (option) {
case JOptionPane.YES_OPTION:
DataModelWriter writer = new DataModelWriter(getDataModel());
writer.writeIncremental(getDataModel().getFileSet());
break;
case JOptionPane.CANCEL_OPTION:
return;
case JOptionPane.NO_OPTION:
break;
}
}
ConfigNode documentConfig = dataModel.getDocumentConfigRoot();
if (documentConfig != null) {
documentConfig.store();
}
}
} catch (Exception e) {
System.out.println("ViewFrame.closeWindow() Got exception: " + e);
}
System.out.println("ViewFrame.dispose");
dispose();
}
/**
* required by all <code>ModelPanel</code>s
*
* @return The shared TreeSelection object for genes.
*/
public TreeSelectionI getGeneSelection() {
return geneSelection;
}
protected void setGeneSelection(TreeSelectionI newSelection) {
geneSelection = newSelection;
}
/**
* required by all <code>ModelPanel</code>s
*
* @return The shared TreeSelection object for arrays.
*/
public TreeSelectionI getArraySelection() {
return arraySelection;
}
protected void setArraySelection(TreeSelectionI newSelection) {
arraySelection = newSelection;
}
/**
* used by data model to signal completion of loading.
* The <code>ViewFrame</code> will react by reconfiguring it's widgets.
*
* @param b The new loaded value
*/
public abstract void setLoaded(boolean b);
/**
* returns special nodata value.
* generally, just cribs from the <code>DataModel</code>
*
* @return A special double which means nodata available.
*/
public abstract double noData();
/**
* returns the UrlPresets for the views to make use of when configuring linking
* for genes
*
* @return The shared <code>UrlPresets</code> object for genes
*/
public abstract UrlPresets getGeneUrlPresets();
/**
* returns the UrlPresets for the views to make use of when configuring linking
* for arrays
*
* @return The shared <code>UrlPresets</code> object for arrays
*/
public abstract UrlPresets getArrayUrlPresets();
/**
* Gets the loaded attribute of the ViewFrame object
*
* @return True if there is currently a model loaded.
*/
public abstract boolean getLoaded();
/**
* Gets the shared <code>DataModel</code>
*
* @return Gets the shared <code>DataModel</code>
*/
public abstract DataModel getDataModel();
/**
* Sets the shared <code>DataModel</code>
*
* @return Sets the shared <code>DataModel</code>
* @throws LoadException
*/
public abstract void setDataModel(DataModel model);
/**
* Should scroll all MainPanels in this view frame to the specified gene.
* The index provided is respect to the TreeSelection object.
*
* @param i gene index in model to scroll the mainpanel to.
*/
public abstract void scrollToGene(int i);
public abstract void scrollToArray(int i);
/** The shared selection objects */
TreeSelectionI geneSelection = null;
TreeSelectionI arraySelection = null;
public void deselectAll() {
geneSelection.deselectAllIndexes();
arraySelection.deselectAllIndexes();
}
/***
* This routine causes all data views to
* select and scroll to a particular gene.
*/
public void seekGene(int i) {
geneSelection.deselectAllIndexes();
geneSelection.setIndex(i, true);
geneSelection.notifyObservers();
scrollToGene(i);
}
/***
* This routine causes all data views to
* select and scroll to a particular array.
*/
public void seekArray(int i) {
arraySelection.deselectAllIndexes();
arraySelection.setIndex(i, true);
arraySelection.notifyObservers();
scrollToGene(i);
}
/**
* This routine extends the selected range to include the index
* i.
*/
public void extendRange(int i) {
if (geneSelection.getMinIndex() == -1)
seekGene(i);
geneSelection.setIndex(i, true);
geneSelection.notifyObservers();
scrollToGene(i);
}
public boolean geneIsSelected(int i) {
return getGeneSelection().isIndexSelected(i);
}
/**
* url linking support
*
* @param i index of gene who's url you would like to display.
*/
public void displayURL(int i) {
displayURL(getUrl(i));
}
/**
* Gets the url for a particular gene.
*
* @param i index of the gene, for the gene's <code>UrlExtractor</code>
* @return A string representation of the url
*/
public String getUrl(int i) {
if (urlExtractor == null) {
return null;
}
return urlExtractor.getUrl(i);
}
/**
* Gets the url for a particular array.
*
* @param i index of the array, for the array's <code>UrlExtractor</code>
* @return A string representation of the url
*/
public String getArrayUrl(int i) {
if (arrayUrlExtractor == null) {
return null;
}
return arrayUrlExtractor.getUrl(i);
}
/**
* Pops up a browser window with the specified url
*
* @param string String representation of the url.
*/
public void displayURL(String string) {
if (string == null) {
return;
}
try {
if (browserControl == null) {
browserControl = BrowserControl.getBrowserControl();
}
browserControl.displayURL(string);
} catch (MalformedURLException e) {
String message = new StringBuffer("Problem loading url: ").append(e).toString();
LogBuffer.println(message);
e.printStackTrace();
JOptionPane.showMessageDialog(this,message);
} catch (IOException e) {
String message = new StringBuffer("Could not load url: ").append(e).toString();
LogBuffer.println(message);
e.printStackTrace();
JOptionPane.showMessageDialog(this,message);
}
}
/**
* Gets the UrlExtractor for the arrays.
*
* This object is used to convert a given array index into a url string. It can be configured to do this in multiple ways.
*
* @return The UrlExtractor for the arrays
*/
public UrlExtractor getArrayUrlExtractor() {
return arrayUrlExtractor;
}
/**
* Gets the UrlExtractor for the genes.
*
* This object is used to convert a given gene index into a url string. It can be configured to do this in multiple ways.
*
* @return The UrlExtractor for the genes
*/
public UrlExtractor getUrlExtractor() {
return urlExtractor;
}
/**
* Sets the arrayUrlExtractor attribute of the ViewFrame object
*
* @param ue The new arrayUrlExtractor value
*/
public void setArrayUrlExtractor(UrlExtractor ue) {
arrayUrlExtractor = ue;
}
/**
* Sets the urlExtractor attribute of the ViewFrame object
*
* @param ue The new urlExtractor value
*/
public void setUrlExtractor(UrlExtractor ue) {
urlExtractor = ue;
}
abstract public HeaderFinder getGeneFinder();
/**
* Open a dialog which allows the user to select a new data file
*
* @return The fileset corresponding to the dataset.
*/
protected FileSet offerSelection()
throws LoadException
{
FileSet fileSet1; // will be chosen...
JFileChooser fileDialog = new JFileChooser();
setupFileDialog(fileDialog);
int retVal = fileDialog.showOpenDialog(this);
if (retVal == JFileChooser.APPROVE_OPTION) {
File chosen = fileDialog.getSelectedFile();
fileSet1 = new FileSet(chosen.getName(), chosen.getParent()+File.separator);
/*
// check existing file nodes...
ConfigNode aconfigNode[] = fileMru.getConfigs();
for (int i = 0; i < aconfigNode.length; i++) {
FileSet fileSet2 = new FileSet(aconfigNode[i]);
if (fileSet2.equals(fileSet1)) {
LogPanel.println("Found Existing node in MRU list for " + fileSet1);
return fileSet2;
}
}
*/
} else {
throw new LoadException("File Dialog closed without selection...", LoadException.NOFILE);
}
/* Don't enforce suffixes...
// see if we match at all...
try {
if (!ff.accept(null, fileSet1.getCdt()))
throw new LoadException(fileSet1.getCdt() +
" did not end in .cdt or .pcl", LoadException.EXT);
} catch (NullPointerException e) {
throw new LoadException(e + ",most likely, no file selected so cdt is null", LoadException.NOFILE);
}
*/
/*
ConfigNode configNode = fileMru.createSubNode();
fileMru.setLast(configNode);
FileSet fileSet3 = new FileSet(configNode);
fileSet3.copyState(fileSet1);
fileMru.notifyObservers();
*/
return fileSet1;
}
protected void setupFileDialog(JFileChooser fileDialog) {
CdtFilter ff = new CdtFilter();
try {
fileDialog.addChoosableFileFilter(ff);
// will fail on pre-1.3 swings
fileDialog.setAcceptAllFileFilterUsed(true);
} catch (Exception e) {
// hmm... I'll just assume that there's no accept all.
fileDialog.addChoosableFileFilter(new javax.swing.filechooser.FileFilter() {
public boolean accept (File f) {
return true;
}
public String getDescription () {
return "All Files";
}
});
}
fileDialog.setFileFilter(ff);
fileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY);
String string = fileMru.getMostRecentDir();
if (string != null) {
fileDialog.setCurrentDirectory(new File(string));
}
}
/**
* Rebuild a particular window menu.
*
* @param windows the list of windows to add elements to.
*
* Add a menu item for each window which grants that window the foreground when selected.
*/
public void rebuildWindowMenu(Vector windows) {
synchronized (menubar) {
menubar.setMenu(TreeviewMenuBarI.windowMenu);
menubar.removeAll();
int max = windows.size();
for (int i = 0; i < max; i++) {
if (i > 8) {
break;
}// just want first 9 windows...
addFocusItem(windows,i);
}
menubar.addSeparator();
menubar.addMenuItem("New Window", new ActionListener() {
public void actionPerformed(ActionEvent actionEvent)
{
createNewFrame().setVisible(true);
}
});
menubar.setAccelerator(KeyEvent.VK_N);
menubar.setMnemonic(KeyEvent.VK_N);
menubar.addMenuItem("Close Window", new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
closeWindow();
}
});
menubar.setAccelerator(KeyEvent.VK_W);
menubar.setMnemonic(KeyEvent.VK_W);
}
}
/**
* currenlty, only the concrete subclass has a reference to the application, and hence can create new frames.
* perhaps this will change if I add an interface for the App classes.
*/
public ViewFrame createNewFrame() {
return getApp().openNew();
}
public abstract TreeViewApp getApp();
/**
* Constructs a MenuItem which causes the i'th window to be moved to the front.
*
* @param windows a list of windows
* @param i which window to move to the front.
* @return a menuItem which focuses the i'th window, or null if more than 9 windows.
*/
private void addFocusItem(Vector windows, int i) {
int p1 = i + 1;
if (p1 > 9) {
return;
}
final ViewFrame source = (ViewFrame) windows.elementAt(i);
String name;
if (source.getLoaded()) {
name = source.getDataModel().getName();
} else {
name = "Not Loaded";
}
menubar.addMenuItem(name, new ActionListener() {
public void actionPerformed(ActionEvent e) {
source.toFront();
}
});
menubar.setAccelerator(getKey(p1));
}
/**
* Gets the key corresponding to a particular number.
*
* @param i The number
* @return The VK_blah key value
*/
protected int getKey(int i) {
switch (i) {
case 0:
return KeyEvent.VK_0;
case 1:
return KeyEvent.VK_1;
case 2:
return KeyEvent.VK_2;
case 3:
return KeyEvent.VK_3;
case 4:
return KeyEvent.VK_4;
case 5:
return KeyEvent.VK_5;
case 6:
return KeyEvent.VK_6;
case 7:
return KeyEvent.VK_7;
case 8:
return KeyEvent.VK_8;
case 9:
return KeyEvent.VK_9;
}
return 0;
}
public void showSubDataModel(int[] indexes, String source, String name) {
if (indexes.length == 0) {
JOptionPane.showMessageDialog(this, "No Genes to show summary of!");
return;
}
showSubDataModel(indexes, null, source, name);
}
public void showSubDataModel(int[] geneIndexes,int[] arrayIndexes, String source, String name) {
ReorderedDataModel dataModel = new ReorderedDataModel(getDataModel(), geneIndexes, arrayIndexes);
if (source != null) dataModel.setSource(source);
if (name != null) dataModel.setName(name);
ViewFrame window = getApp().openNew();
window.setDataModel(dataModel);
window.setLoaded(true);
window.setVisible(true);
}
/** The global most recently used object. */
protected FileMru fileMru;
/** allows opening of urls in external browser */
protected BrowserControl browserControl = null;
/** url extractor for genes */
private UrlExtractor urlExtractor;
/** url extractor for arrays */
private UrlExtractor arrayUrlExtractor;
protected TreeviewMenuBarI menubar;
}