Newer
Older
/* FeatureList.java
*
* created: Fri Oct 9 1998
*
* This file is part of Artemis
*
* Copyright (C) 1998,1999,2000,2001,2002 Genome Research Limited
*
* 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; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
* $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/FeatureList.java,v 1.27 2008-10-23 14:38:55 tjc Exp $
*/
package uk.ac.sanger.artemis.components;
import uk.ac.sanger.artemis.*;
import uk.ac.sanger.artemis.sequence.*;
import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
import uk.ac.sanger.artemis.io.GFFStreamFeature;
import uk.ac.sanger.artemis.io.Qualifier;
import uk.ac.sanger.artemis.io.InvalidRelationException;
import uk.ac.sanger.artemis.io.EntryInformation;
import uk.ac.sanger.artemis.io.QualifierInfo;
import uk.ac.sanger.artemis.io.QualifierVector;
import uk.ac.sanger.artemis.io.StreamQualifier;
import uk.ac.sanger.artemis.util.DatabaseDocument;
import java.awt.event.MouseEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Container;
import java.awt.Color;
import java.awt.Point;
import java.awt.Graphics;
import java.awt.Dimension;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.JComponent;
/**
* This component gives the user a list containing the details the current
* Features.
*
* @author Kim Rutherford
* @version $Id: FeatureList.java,v 1.27 2008-10-23 14:38:55 tjc Exp $
*
**/
public class FeatureList extends EntryGroupPanel
implements EntryGroupChangeListener,
EntryChangeListener, FeatureChangeListener,
SelectionChangeListener, DisplayComponent
{
/** true if correlation scores should be shown */
/** set to true by selectionChanged() and used by paintComponent(). */
private boolean selection_changed_flag = false;
/** colour used to draw the background. */
private Color background_colour = Color.white;
/**
* If true this component will show Feature.getIDString() (ie /gene or
* /label) instead of the key.
**/
private boolean show_gene_names = false;
/** show Feature.getSystematicName() */
private boolean show_systematic_names = false;
/** show the /product qualifier instead of /note field. */
private boolean show_qualifiers = false;
/**
* The is the maximum width of the strings containing the feature start and
* stop positions. Set in the constructor.
**/
private int max_base_pos_width;
/** JScrollPane viewport that this panel is the view of */
private JViewport viewport = null;
private boolean isDatabaseGroup = false;
/**
* Create a new FeatureList with the default number of rows.
* @param entry_group The EntryGroup that this component will display.
* @param selection The Selection object for this component. Selected
* objects will be highlighted.
* @param goto_event_source The object to use when we need to call
public FeatureList(final EntryGroup entry_group,
final Selection selection,
final GotoEventSource goto_event_source,
isDatabaseGroup = GeneUtils.isDatabaseEntry(getEntryGroup());
/**
* Listen for mouse press events so that we can do popup menus and
* selection.
**/
public void mousePressed(MouseEvent event)
{
if(isMenuTrigger(event))
{
if(popup == null)
popup = new FeaturePopup(FeatureList.this,
getEntryGroup(),
getSelection(),
getGotoEventSource(),
getBasePlotGroup());
final JComponent parent = (JComponent)event.getSource();
popup.show(parent, event.getX(), event.getY());
}
else
handleCanvasMousePress(event);
// changes to the EntryGroup will be noticed by listening for EntryChange
// and FeatureChange events.
getEntryGroup().addEntryGroupChangeListener(this);
getEntryGroup().addEntryChangeListener(this);
getEntryGroup().addFeatureChangeListener(this);
// find the maximum posible width for the high and low positions
final int sequence_length = getEntryGroup().getSequenceLength();
max_base_pos_width = (int)(Math.log(sequence_length)/Math.log(10)) + 1;
}
/**
* Remove this component from all the listener lists it is on.
**/
getSelection().removeSelectionChangeListener(this);
getEntryGroup().removeEntryGroupChangeListener(this);
getEntryGroup().removeEntryChangeListener(this);
getEntryGroup().removeFeatureChangeListener(this);
}
/**
* Set value of the show correlation scores flag.
* @param show_correlation_scores Show correlation scores in the list if
* and only if this argument is true.
**/
protected void setCorrelationScores(final boolean show_correlation_scores)
}
/**
* Get the value of the "show correlation scores" flag.
**/
return show_correlation_scores;
}
/**
* Set value of the show /gene flag.
* @param show_gene_names If true this component will show the /gene (really
if(this.show_gene_names != show_gene_names)
/**
* Set value of the show /systematic_id flag.
* @param show_systematic_names If true this component will show the /gene (really
* Feature.getSystematicName()) instead of the key.
**/
{
if(this.show_systematic_names != show_systematic_names)
{
this.show_systematic_names = show_systematic_names;
repaint();
}
}
protected void setShowUserDefinedQualifier(final String user_defined_qualifier)
{
this.user_defined_qualifier = user_defined_qualifier;
repaint();
}
protected StringVector getShowUserDefinedQualifier()
{
if(user_defined_qualifier == null)
return null;
return StringVector.getStrings(user_defined_qualifier);
}
/**
* Get the value of the "show systematic id" flag.
**/
{
return show_systematic_names;
}
/**
* Set value of the show qualifiers flag.
* @param show_quailfiers If true this component will show all the
* qualifiers after the note.
**/
if(show_qualifiers)
user_defined_qualifier = null;
}
}
/**
* Get the value of the "show qualifiers" flag.
**/
return show_qualifiers;
}
/**
* Set value of the show /product flag.
* @param show_products If true this component will show the /product
* qualifier instead of the /note.
**/
if(show_products)
user_defined_qualifier = null;
}
}
/**
* Get the value of the "show products" flag.
**/
/**
* Implementation of the EntryGroupChangeListener interface. We listen to
* EntryGroupChange events so that we can update the display if entries
* are added or deleted.
**/
public void entryGroupChanged(EntryGroupChangeEvent event)
final int hgt = getEntryGroup().getAllFeaturesCount() *
getLineHeight();
setPreferredSize(new Dimension(getSize().width*4,hgt));
}
/**
* Implementation of the FeatureChangeListener interface.
**/
public void featureChanged(FeatureChangeEvent event)
}
/**
* Implementation of the EntryChangeListener interface. We listen to
* EntryChange events so that we can update the list if features are added
* or deleted.
**/
public void entryChanged(EntryChangeEvent event)
}
/**
* Implementation of the SelectionChangeListener interface. We listen to
* SelectionChange events so that we can update the list to reflect the
* current selection.
**/
public void selectionChanged(SelectionChangeEvent event)
if(getSelection().getMarkerRange() != null &&
event.getType() == SelectionChangeEvent.OBJECT_CHANGED)
* Return the JViewport that this component is contained in.
*/
Container container = getParent();
while(!(container instanceof JScrollPane))
container = container.getParent();
viewport = ((JScrollPane)container).getViewport();
return viewport;
*
* Find the point at the top right hand corner of the
* scroll pane.
*
*/
private Point getScrollPoint()
}
/**
* Handle a mouse press event on the drawing canvas - select on click,
* select and broadcast it on double click.
**/
private void handleCanvasMousePress(final MouseEvent event)
{
if(event.getID() != MouseEvent.MOUSE_PRESSED)
final int clicked_feature_index = event.getY()/getLineHeight();
getEntryGroup().featureAt(clicked_feature_index);
if(selected_features.contains(clicked_feature))
{
getSelection().remove(clicked_feature);
getSelection().removeSegmentsOf(clicked_feature);
}
else
getSelection().add(clicked_feature);
if(event.getClickCount() == 2)
{
makeSelectionVisible();
if((event.getModifiers() & InputEvent.BUTTON2_MASK) != 0 ||
event.isAltDown())
{
if(Options.readWritePossible())
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
{
final JFrame frame = new JFrame("Artemis Feature Edit: " +
clicked_feature.getIDString() +
(clicked_feature.isReadOnly() ?
" - (read only)" :
""));
final FeatureEdit fe = new FeatureEdit(clicked_feature, getEntryGroup(),
getSelection(), getGotoEventSource(), frame);
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent event)
{
fe.stopListening();
frame.dispose();
}
});
frame.getContentPane().add(fe);
frame.pack();
final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
frame.setLocation(new Point((screen.width - getSize().width)/2,
(screen.height - getSize().height)/2));
frame.setVisible(true);
}
selection_changed_flag = false;
final FeatureVector selected_features =
getSelection().getAllFeatures();
if(selected_features.size() > 0)
{
Point viewPoint = getScrollPoint();
final EntryGroup entry_group = getEntryGroup();
final int feature_count = entry_group.getAllFeaturesCount();
// set to true if any of the selected features is visible
boolean a_selected_feature_is_visible = false;
int first_line_in_view = viewPoint.y/getLineHeight();
if(first_line_in_view == -1)
first_line_in_view = 0;
int numberLines = linesInView();
for(int i = first_line_in_view;
i < feature_count && i <= first_line_in_view + numberLines;
final Feature this_feature = entry_group.featureAt(i);
a_selected_feature_is_visible = true;
break;
}
if(!a_selected_feature_is_visible)
{
// make the first selected feature visible
final Feature first_selected_feature =
selected_features.elementAt(0);
final int index_of_first_selected_feature =
if( index_of_first_selected_feature > -1 &&
(index_of_first_selected_feature < first_line_in_view ||
index_of_first_selected_feature >= first_line_in_view + numberLines))
getViewport().setViewPosition(new Point(0,
index_of_first_selected_feature * getLineHeight()));
/**
* The main paint function for the canvas. An off screen image used for
* double buffering when drawing the canvas.
* @param g The Graphics object of the canvas.
**/
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Point viewPoint = getScrollPoint();
final int feature_count = getEntryGroup().getAllFeaturesCount();
// must check size in case new features/entries added
if(feature_count*getLineHeight() > getPreferredSize().height)
{
final int hgt = feature_count * getLineHeight();
setPreferredSize(new Dimension(getSize().width*4,hgt));
final int lines_in_view = linesInView()+1;
int first_index_in_view = (viewPoint.y/getLineHeight());
last_index_in_view = feature_count - 1;
final FeatureVector features_in_view =
getEntryGroup().getFeaturesInIndexRange(first_index_in_view,
last_index_in_view);
final int features_in_view_size = features_in_view.size();
for(int i = 0; i < features_in_view_size; i++)
final Feature this_feature = features_in_view.elementAt(i);
drawFeatureLine(g, this_feature, feature_string);
/**
* Return the number of visible text lines on canvas.
**/
return getViewport().getExtentSize().height/getLineHeight();
}
/**
* Draw the given Feature at the given line of the list, taking the
* selection into account.
**/
// width of coloured blob at the left of the text
final int y_pos = getEntryGroup().indexOf(feature)*BOX_WIDTH;
g.fillRect(1, y_pos+1,
BOX_WIDTH, BOX_WIDTH - 1);
g.fillRect(BOX_WIDTH + 4, y_pos,
getSize().width + getScrollPoint().x,
if( feature.getEmblFeature() instanceof GFFStreamFeature &&
!getSelection().contains(feature) &&
!((GFFStreamFeature)feature.getEmblFeature()).isVisible() )
{
//
// use gray for the key if the feature is NOT visible
g.setColor(Color.gray);
int ind = feature_string.indexOf(' ');
final String keyString = feature_string.substring(0, ind);
g.drawString(keyString,
BOX_WIDTH + 5,
y_pos + getFontAscent());
g.setColor(Color.black);
g.drawString(feature_string.substring(ind),
BOX_WIDTH + 5 + getFontMetrics(getFont()).stringWidth(keyString),
y_pos + getFontAscent());
}
else
g.drawString(feature_string,
}
/**
* Return a String object suitable for displaying in the list of features.
* @param dont_truncate if true the gene name / key field won't be
* truncated if it is longer than the field width
**/
private String makeFeatureString(final Feature feature,
final boolean dont_truncate)
{
key_string = feature.getGeneName();
if(key_string == null)
key_string = feature.getSystematicName();
key_string = key_string.substring(0, KEY_FIELD_WIDTH);
else if(show_systematic_names)
{
key_string = feature.getSystematicName();
if(key_string.length() > KEY_FIELD_WIDTH && !dont_truncate)
key_string = key_string.substring(0, KEY_FIELD_WIDTH);
}
final Marker low_marker = feature.getFirstBaseMarker();
if(user_defined_qualifier != null && !user_defined_qualifier.equals(""))
{
StringVector sv = StringVector.getStrings(user_defined_qualifier);
for(int i=0; i<sv.size(); i++)
{
final String user_defined_qualifier_string =
feature.getValueOfQualifier((String)sv.get(i));
if(user_defined_qualifier_string != null)
description_string_buffer.append("/"+sv.get(i)+"="+user_defined_qualifier_string+" ");
}
}
catch(InvalidRelationException ire){}
}
else if(show_products)
// description is not blank
if(feature.isCDS())
description_string_buffer.append("[no /product]");
description_string_buffer.append(product_string);
String note = null;
if(isDatabaseGroup)
{
try
{
note = feature.getValueOfQualifier("comment");
}
catch(InvalidRelationException e){}
}
if(note == null)
note = feature.getNote();
padRightWithSpaces(note, QUALIFIER_COLUMN);
description_string_buffer.append(note_string);
description_string_buffer.append(" ");
description_string_buffer.append(getQualifierString(feature));
}
else
{
if(low_marker.getRawPosition() < high_marker.getRawPosition())
{
low_pos = String.valueOf(low_marker.getRawPosition());
high_pos = String.valueOf(high_marker.getRawPosition());
}
else
{
low_pos = String.valueOf(high_marker.getRawPosition());
high_pos = String.valueOf(low_marker.getRawPosition());
new_list_line.append(padRightWithSpaces(key_string, KEY_FIELD_WIDTH));
new_list_line.append(" ");
new_list_line.append(padLeftWithSpaces(low_pos, max_base_pos_width));
new_list_line.append(" ");
new_list_line.append(padLeftWithSpaces(high_pos, max_base_pos_width));
if(feature.isForwardFeature())
new_list_line.append(" ");
else
new_list_line.append("c ");
if(feature.isCDS() ||
feature.getKey().getKeyString().equals(DatabaseDocument.EXONMODEL))
new_list_line.append(getScoresString(feature));
new_list_line.append(" ");
if(getBasePlotGroup().getCodonUsageAlgorithm() != null)
new_list_line.append(" ");
}
/**
* Return a String containing the given Qualifier and it's values (in EMBL
* format).
* @param start_index ignore the values before this index
**/
private String formatQualifier(final String qualifier_name,
final Feature feature,
final int start_index)
{
final StringBuffer buffer = new StringBuffer();
final Qualifier qualifier = feature.getQualifierByName(qualifier_name);
final int qualifier_strings_size = qualifier_strings.size();
for(int i = start_index; i < qualifier_strings_size; ++i)
final String qualifier_string = (String)qualifier_strings.elementAt(i);
}
/**
* Return a String containing all the qualifiers of the given Feature
* (except /note) in EMBL format. Any /similarity qualifier will come
* first.
**/
private String getQualifierString(final Feature feature)
{
final StringBuffer buffer = new StringBuffer();
// if there is a /note and it has more than one value put it next (without
// the first value)
final Qualifier note_qualifier =
if(note_qualifier != null && note_qualifier.getValues().size() > 1)
{
buffer.append(formatQualifier("note", feature, 1));
buffer.append(" ");
}
// put /similarity before all but the /note qualifier
final Qualifier similarity_qualifier =
buffer.append(formatQualifier("similarity", feature, 0));
buffer.append(" ");
final int qualifiers_size = qualifiers.size();
for(int i = 0 ; i < qualifiers_size; ++i)
final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(i);
final String this_qualifier_name = this_qualifier.getName();
if(!this_qualifier_name.equals("note") &&
!this_qualifier_name.equals("similarity"))
buffer.append(formatQualifier(this_qualifier_name, feature, 0));
buffer.append(" ");
}
/**
* Return a String containing the correlation scores.
**/
final int base_total = feature.getTranslationBases().length();
final int c_total = feature.getBaseCount(Bases.getIndexOfBase('c'));
final int g_total = feature.getBaseCount(Bases.getIndexOfBase('g'));
feature.getPositionalBaseCount(0, Bases.getIndexOfBase('g'));
feature.getPositionalBaseCount(2, Bases.getIndexOfBase('c'));
feature.getPositionalBaseCount(2, Bases.getIndexOfBase('g'));
final double c3_score = 100.0 * (3 * c3_count - c_total) / c_total;
final double g1_score = 100.0 * (3 * g1_count - g_total) / g_total;
final double g3_score = 100.0 * (3 * g3_count - g_total) / g_total;
final double cor1_2_score = feature.get12CorrelationScore();
final NumberFormat number_format = NumberFormat.getNumberInstance();
number_format.setMaximumFractionDigits(1);
number_format.setMinimumFractionDigits(1);
final String cor1_2_score_string = number_format.format(cor1_2_score);
final String c3_score_string;
final String g1_score_string;
final String g3_score_string;
c3_score_string = number_format.format(c3_score);
g1_score_string = number_format.format(g1_score);
g3_score_string = number_format.format(g3_score);
String codon_usage_score_string = "";
final CodonUsageAlgorithm codon_usage_alg =
number_format.setMaximumFractionDigits(3);
number_format.setMinimumFractionDigits(3);
number_format.format(codon_usage_alg.getFeatureScore(feature)) + " ";
return codon_usage_score_string +
padRightWithSpaces(cor1_2_score_string, 5) + " " +
padRightWithSpaces(c3_score_string, 5) + " " +
padRightWithSpaces(g1_score_string, 5) + " " +
padRightWithSpaces(g3_score_string, 5);
}
/**
* Return the given string padded with spaces to the given width. The
* spaces are added on the right of the string.
**/
private String padRightWithSpaces(final String string, final int width)
final StringBuffer buffer = new StringBuffer(string);
}
/**
* Return the given string padded with spaces to the given width. The
* spaces are added on the left of the string.
**/
private String padLeftWithSpaces(final String string, final int width)
final StringBuffer buffer = new StringBuffer();