/*
Copyright (C) 2003 EBI, GRL
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.ensembl.mart.explorer;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import org.ensembl.mart.lib.InvalidQueryException;
import org.ensembl.mart.lib.Query;
import org.ensembl.mart.lib.Attribute;
import org.ensembl.mart.lib.SequenceDescription;
import org.ensembl.mart.lib.config.AttributeCollection;
import org.ensembl.mart.lib.config.AttributeDescription;
import org.ensembl.mart.lib.config.AttributeGroup;
import org.ensembl.mart.lib.config.AttributePage;
import org.ensembl.mart.lib.config.ConfigurationException;
import org.ensembl.mart.lib.config.DatasetConfig;
import org.ensembl.mart.util.LoggingUtil;
/**
* Widget for viewing the current sequence attribute on a query and enabling the user to
* add, remove or select a different one. This class implements the Model View Controller Design pattern where
* the query is the model, actionPerformed(...) handles user control actions and
* sequenceDescriptionChanged(...) updates the view when the model changes.
*
* @author <a href="mailto:craig@ebi.ac.uk">Craig Melsopp</a>
*
* TODO Modify SequenceDescription.left/right to use -1 for unset, then update this to reflect that.
*/
public class SequenceGroupWidget
extends InputPage
implements ActionListener, TreeSelectionListener {
private static final Logger logger =
Logger.getLogger(SequenceGroupWidget.class.getName());
private class LabelledTextField extends JTextField {
private LabelledTextField(String initialValue) {
super(initialValue);
Dimension d = new Dimension(100, 24);
setPreferredSize(d);
setMaximumSize(d);
}
public int getTextAsInt() {
if (getText() == null || getText().length() < 1)
return 0;
return Integer.parseInt(getText());
}
}
private final int IMAGE_WIDTH = 248;
private final int IMAGE_HEIGHT = 69;
private final int UNSUPPORTED = -100;
private Feedback feedback = new Feedback(this);
private LabelledTextField flank5 = new LabelledTextField("0");
private LabelledTextField flank3 = new LabelledTextField("0");
private JButton clearButton = new JButton("Clear");
private JRadioButton transcript = new JRadioButton("Transcripts/proteins");
private JRadioButton gene = new JRadioButton("Genes");
private JRadioButton none = new JRadioButton();
private final String[] gene_disables = new String[] {"transcript_exon_intron",
"transcript_exon",
"transcript_flank",
"3utr",
"5utr",
"cdna",
"coding",
"coding_transcript_flank",
"peptide"
};
private JRadioButton includeNone = new JRadioButton();
private JRadioButton[] typeButtons = { transcript, gene, none };
private JRadioButton[] genericSeqButtons = { none };
private JRadioButton[] includeButtons;
private JRadioButton[] tranButtons;
private JComponent[] rightColumn;
private JComponent[] leftColumn;
private JRadioButton[] geneButtons;
private JLabel schematicSequenceImageHolder = new JLabel();
private ImageIcon blankIcon;
private DatasetConfig dsv;
private AdaptorManager manager;
private String[] seqTypes;
private JRadioButton lastButton = null;
private String iname = null;
private boolean containsImage = false;
/**
* @param name
* @param query
* @param tree
* @throws ConfigurationException
*/
public SequenceGroupWidget(
String name,
String iname,
Query query,
QueryTreeView tree,
DatasetConfig dsv,
AdaptorManager manager) {
super(query, name, tree);
this.dsv = dsv;
this.manager = manager;
this.iname = iname;
if (tree != null)
tree.addTreeSelectionListener(this);
buildGUI();
sequenceDescriptionChanged(query, null, query.getSequenceDescription());
}
private void buildGUI() {
if (iname.matches("\\w+seq_scope"))
buildGUIGeneric();
else
buildGUIEnsembl();
}
private void buildGUIGeneric() {
Box b = Box.createVerticalBox();
b.add(addAll(Box.createHorizontalBox(), new JComponent[]{clearButton}, true));
containsImage = false;
Box columns = Box.createHorizontalBox();
//need to get all sequence types from the Registry, and make JRadioButtons for them
AttributePage seqPage = dsv.getAttributePageByInternalName("sequences");
if (seqPage == null ) seqPage = dsv.getAttributePageByInternalName("sequence");
AttributeGroup seqGroup = (AttributeGroup) seqPage.getAttributeGroupByName("sequence");
AttributeCollection seqCol = seqGroup.getAttributeCollectionByName(iname);
List seq_atts = seqCol.getAttributeDescriptions();
includeButtons = new JRadioButton[seq_atts.size()];
leftColumn = new JComponent[seq_atts.size()];
seqTypes = new String[seq_atts.size()];
for (int i = 0, n = seq_atts.size(); i < n; i++) {
AttributeDescription pointer = (AttributeDescription) seq_atts.get(i);
AttributeDescription realAtt = manager.getPointerAttribute(pointer);
JRadioButton button = new JRadioButton(realAtt.getDisplayName());
button.addActionListener(this);
//all buttons go in includeButtons, and their root type descriptions map with them
includeButtons[i] = button;
leftColumn[i] = button;
seqTypes[i] = pointer.getInternalName();
}
columns.add(addAll(Box.createVerticalBox(), leftColumn, true));
columns.add(Box.createHorizontalGlue());
b.add(columns);
if (iname.matches("snp\\w+")) {
b.add(
addAll(
Box.createHorizontalBox(),
new Component[] {
new JLabel("5' Flank (bp)"),
flank5,
Box.createHorizontalStrut(50),
new JLabel("3' Flank (bp)"),
flank3 },
false));
flank3.addActionListener(this);
flank5.addActionListener(this);
}
add(b);
none.setSelected(true);
ButtonGroup bg = new ButtonGroup();
for (int i = 0; i < genericSeqButtons.length; i++) {
bg.add(genericSeqButtons[i]);
genericSeqButtons[i].addActionListener(this);
}
clearButton.addActionListener(this);
clearButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
none.doClick();
}
});
bg = new ButtonGroup();
for (int i = 0; i < includeButtons.length; i++) {
bg.add(includeButtons[i]);
includeButtons[i].addActionListener(this);
}
bg.add(includeNone);
//default state
setDefaultStateGeneric();
}
private void buildGUIEnsembl() {
containsImage = true;
gene.setToolTipText(
" Transcript information ignored (one output per gene)");
Box b = Box.createVerticalBox();
b.add(addAll(Box.createHorizontalBox(), new JComponent[]{transcript,gene,clearButton}, true));
b.add(
addAll(
Box.createHorizontalBox(),
new JComponent[] { schematicSequenceImageHolder },
true));
Box columns = Box.createHorizontalBox();
//need to get all sequence types from the Registry, and make JRadioButtons for them
AttributePage seqPage = dsv.getAttributePageByInternalName("sequences");
// another hack this time for wormart
if (seqPage == null) seqPage = dsv.getAttributePageByInternalName("sequence");
AttributeGroup seqGroup = (AttributeGroup) seqPage.getAttributeGroupByName("sequence");
AttributeCollection seqCol = seqGroup.getAttributeCollectionByName("seq_scope_type");
List seq_atts = seqCol.getAttributeDescriptions();
includeButtons = new JRadioButton[seq_atts.size()];
seqTypes = new String[seq_atts.size()];
//turn the gene_exclude Array into a List for easier reference
List gene_excludes = Arrays.asList(gene_disables);
ArrayList gene_atts = new ArrayList();
ArrayList tran_atts = new ArrayList();
for (int i = 0, n = seq_atts.size(); i < n; i++) {
AttributeDescription pointer = (AttributeDescription) seq_atts.get(i);
AttributeDescription realAtt = manager.getPointerAttribute(pointer);
JRadioButton button = new JRadioButton(realAtt.getDisplayName());
button.addActionListener(this);
//all buttons go in includeButtons, and their root type descriptions map with them
includeButtons[i] = button;
seqTypes[i] = pointer.getInternalName();
if (pointer.getPointerAttribute()!=null && gene_excludes.contains(pointer.getPointerAttribute()))
tran_atts.add(button);
else
gene_atts.add(button);
}
geneButtons = new JRadioButton[gene_atts.size()];
gene_atts.toArray(geneButtons);
rightColumn = new JComponent[gene_atts.size()];
gene_atts.toArray(rightColumn);
tranButtons = new JRadioButton[tran_atts.size()];
tran_atts.toArray(tranButtons);
leftColumn = new JComponent[tran_atts.size()];
tran_atts.toArray(leftColumn);
columns.add(addAll(Box.createVerticalBox(), leftColumn, true));
columns.add(addAll(Box.createVerticalBox(), rightColumn, false));
columns.add(Box.createHorizontalGlue());
b.add(columns);
b.add(
addAll(
Box.createHorizontalBox(),
new Component[] {
new JLabel("5' Flank (bp)"),
flank5,
Box.createHorizontalStrut(50),
new JLabel("3' Flank (bp)"),
flank3 },
false));
add(b);
BufferedImage blank =
new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = blank.createGraphics();
g.setBackground(Color.WHITE);
g.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
blankIcon = new ImageIcon(blank);
none.setSelected(true);
ButtonGroup bg = new ButtonGroup();
for (int i = 0; i < typeButtons.length; i++) {
bg.add(typeButtons[i]);
typeButtons[i].addActionListener(this);
}
clearButton.addActionListener(this);
clearButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
none.doClick();
}
});
bg = new ButtonGroup();
for (int i = 0; i < includeButtons.length; i++) {
bg.add(includeButtons[i]);
includeButtons[i].addActionListener(this);
}
bg.add(includeNone);
flank3.addActionListener(this);
flank5.addActionListener(this);
//default state
setDefaultStateEns();
}
private void setDefaultState() {
if (iname.matches("\\w+seq_scope"))
setDefaultStateGeneric();
else
setDefaultStateEns();
}
private void setDefaultStateGeneric() {
lastButton = null;
if (iname.matches("snp\\w+")) {
flank3.setText("100");
flank5.setText("100");
flank3.setEnabled(true);
flank5.setEnabled(true);
}
setButtonsEnabled(includeButtons, true);
}
private void setDefaultStateEns() {
schematicSequenceImageHolder.setIcon(blankIcon);
lastButton = null;
flank3.setEnabled(true);
flank5.setEnabled(true);
gene.setSelected(false);
transcript.setSelected(true);
setButtonsEnabled(includeButtons, false);
setButtonsEnabled(tranButtons, true);
}
private Box addAll(
Box container,
Component[] components,
boolean addGlueAtEnd) {
for (int i = 0; i < components.length; i++)
container.add(components[i]);
if (addGlueAtEnd)
container.add(Box.createGlue());
return container;
}
private ImageIcon loadIcon(String filepath) {
ImageIcon icon = null;
URL testImage = getClass().getClassLoader().getResource(filepath);
if (testImage != null)
icon = new ImageIcon(testImage);
else {
System.err.println("Problem loading file: " + filepath);
}
return icon;
}
/**
* Runs a graphical test of this widget.
* @param args ignored
* @throws Exception
*/
public static void main(String[] args) throws Exception {
LoggingUtil.setAllRootHandlerLevelsToFinest();
logger.setLevel(Level.ALL);
//DSAttributeGroup g = new DSAttributeGroup("sequences");
Query q = new Query();
//q.addQueryChangeListener(new DebugQueryListener(System.out));
//TODO: load defaultMartRegistry.xml, get human sequence config
//SequenceGroupWidget w = new SequenceGroupWidget("seq widget", q, null);
//new QuickFrame("Sequence Attribute Widget test", w);
}
/**
* Updates the query in response to a user action (control part of model-control-view pattern).
* Adds / removes a filtter to/from query as necessary.
*/
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src == clearButton) {
changeQuery(null, 0, 0);
setDefaultState();
} else if (src == transcript) {
transcript.setSelected(true);
if (lastButton == null)
changeQuery(null, 0, 0);
setButtonsEnabled(includeButtons, false);
setButtonsEnabled(tranButtons, true);
} else if (src == gene) {
gene.setSelected(true);
if (lastButton != null) {
String seqType = null;
for (int i = 0, n = geneButtons.length; i < n; i++) {
JRadioButton button = geneButtons[i];
if (lastButton == button) {
seqType = seqTypes[i];
break;
}
}
//seqType may be null if a transcript only type button was chosen before the gene switch was applied
if (seqType == null) {
//runs setDefaultState by virtue of null sequenceDescription sent to the changed method
changeQuery(null, 0, 0);
transcript.setSelected(false);
gene.setSelected(true);
} else
changeQuery( seqType,
flank5.getTextAsInt(),
flank3.getTextAsInt());
}
setButtonsEnabled(includeButtons, false);
setButtonsEnabled(geneButtons, true);
} else if (src == flank5 ) {
if (lastButton != null) {
String seqType = null;
for (int i = 0, n = includeButtons.length; i < n; i++) {
JRadioButton button = includeButtons[i];
if (lastButton == button) {
seqType = seqTypes[i];
lastButton = button;
break;
}
}
changeQuery( seqType,
flank5.getTextAsInt(),
flank3.getTextAsInt());
}
} else if (src == flank3) {
if (lastButton != null) {
String seqType = null;
for (int i = 0, n = includeButtons.length; i < n; i++) {
JRadioButton button = includeButtons[i];
if (lastButton == button) {
seqType = seqTypes[i];
lastButton = button;
break;
}
}
changeQuery( seqType,
flank5.getTextAsInt(),
flank3.getTextAsInt());
}
} else {
String seqType = null;
for (int i = 0, n = includeButtons.length; i < n; i++) {
JRadioButton button = includeButtons[i];
if (src == button) {
seqType = seqTypes[i];
lastButton = button;
break;
}
}
changeQuery( seqType,
flank5.getTextAsInt(),
flank3.getTextAsInt());
}
}
/**
* Updates the view (GUI state) to represent the sequence description.
* @param description
*/
public void sequenceDescriptionChanged(
Query sourceQuery,
SequenceDescription oldSequenceDescription,
SequenceDescription sd) {
if (sd == null)
setDefaultState();
else {
if (containsImage) {
//just need to know which image to load
String seqD = sd.getSeqDescription();
if (sd.getLeftFlank() > 0)
seqD += "_5";
if (sd.getRightFlank() > 0)
seqD += "_3";
String imagePath = "data/image/gene_schematic_"+seqD+".gif";
schematicSequenceImageHolder.setIcon(
loadIcon(imagePath));
}
}
}
/**
* Updates sequence description on the query
* @param imageFilePath image to be displayed
* @param sequenceType sequence type (constant from SeequenceDescription), or UNSUPPORTED if unsupported
* @param leftFlank left flank in base pairs
* @param rightFlank rightt flank in base pairs
*/
private void changeQuery(String sequenceType, int leftFlank, int rightFlank) {
if (sequenceType == null) {
query.setSequenceDescription(null);
} else {
try {
AttributePage seqPage = dsv.getAttributePageByInternalName("sequences");
// for wormmart
if (seqPage == null) seqPage = dsv.getAttributePageByInternalName("sequence");
AttributeDescription attrDesc = seqPage.getAttributeDescriptionByInternalName(sequenceType);
String seqDs = attrDesc.getPointerDataset();
if (seqDs==null || "".equals(seqDs)) seqDs = dsv.getDataset();
SequenceDescription newAttribute =
new SequenceDescription(dsv.getDataset(), seqDs, sequenceType, manager.getRootAdaptor(), leftFlank, rightFlank);
SequenceDescription oldAttribute = query.getSequenceDescription();
if (oldAttribute != newAttribute
&& !newAttribute.equals(oldAttribute)) {
query.setSequenceDescription(newAttribute);
// try to add remove atts logic here
ArrayList attsToRemove = new ArrayList();
boolean removeSeq = false;
Attribute[] queryAtts = query.getAttributes();
for (int i = 0, n = queryAtts.length; i < n; i++) {
Attribute thisAtt = queryAtts[i];
if (seqPage.getAttributeDescriptionByFieldNameTableConstraint(thisAtt.getField(), thisAtt.getTableConstraint()) == null) {
attsToRemove.add(thisAtt);
}
}
if (query.getSequenceDescription() != null) {
if (!seqPage.getInternalName().equals("sequences") && !seqPage.getInternalName().equals("sequence") )
removeSeq = true;
}
if (attsToRemove.size() > 0 || removeSeq) {
feedback.info("Removing attributes from pages not compatible with " + seqPage.getDisplayName());
for (int i = 0, n = attsToRemove.size(); i < n; i++) {
Attribute attToRemove = (Attribute) attsToRemove.get(i);
System.out.println("revmoving "+attToRemove.getField());
query.removeAttribute(attToRemove);
}
if (removeSeq)
query.setSequenceDescription(null);
}
//System.out.println(" seq11 descripiton "+query.getSequenceDescription());
sequenceDescritpionChanged(query,oldAttribute,newAttribute);
}
} catch (InvalidQueryException e) {
feedback.warning("Invalid sequence attribute. " + e.getMessage());
e.printStackTrace();
}
}
}
private void setButtonsEnabled(JRadioButton[] buttons, boolean enabled) {
for (int i = 0; i < buttons.length; i++)
buttons[i].setEnabled(enabled);
}
/**
* Callback method called when an item in the tree is selected.
* Brings this widget to the front if the selected node in the tree is a sequence description.
* TODO get scrolling to a selected attribute working properly
*/
public void valueChanged(TreeSelectionEvent e) {
if (query.getSequenceDescription() != null) {
if (e.getNewLeadSelectionPath() != null
&& e.getNewLeadSelectionPath().getLastPathComponent() != null) {
DefaultMutableTreeNode node =
(DefaultMutableTreeNode) e
.getNewLeadSelectionPath()
.getLastPathComponent();
if (node != null) {
TreeNodeData tnd = (TreeNodeData) node.getUserObject();
if (tnd.getSequenceDescription() != null)
for (Component p, c = this; c != null; c = p) {
p = c.getParent();
if (p instanceof JTabbedPane)
((JTabbedPane) p).setSelectedComponent(c);
else if (p instanceof JScrollPane) {
// not sure if this is being used
Point pt = c.getLocation();
Rectangle r = new Rectangle(pt);
((JScrollPane) p).scrollRectToVisible(r);
}
}
}
}
}
}
}