/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview 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 3
* of the License, or (at your option) any later version.
*
* Jalview 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 Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
package jalview.appletgui;
import jalview.api.FeatureColourI;
import jalview.datamodel.SearchResults;
import jalview.datamodel.SearchResultsI;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
import jalview.io.FeaturesFile;
import jalview.schemes.FeatureColour;
import jalview.util.ColorUtils;
import jalview.util.MessageManager;
import jalview.viewmodel.AlignmentViewport;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.Panel;
import java.awt.ScrollPane;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.TextEvent;
import java.awt.event.TextListener;
import java.util.Hashtable;
import java.util.List;
/**
* DOCUMENT ME!
*
* @author $author$
* @version $Revision$
*/
public class FeatureRenderer
extends jalview.renderer.seqfeatures.FeatureRenderer
{
/*
* creating a new feature defaults to the type and group as
* the last one created
*/
static String lastFeatureAdded = "feature_1";
static String lastFeatureGroupAdded = "Jalview";
// Holds web links for feature groups and feature types
// in the form label|link
Hashtable featureLinks = null;
/**
* Creates a new FeatureRenderer object.
*
* @param av
*/
public FeatureRenderer(AlignmentViewport av)
{
super(av);
}
int featureIndex = 0;
boolean deleteFeature = false;
FeatureColourPanel colourPanel;
class FeatureColourPanel extends Panel
{
String label = "";
private Color maxCol;
private boolean isColourByLabel, isGcol;
/**
* render a feature style in the amend feature dialog box
*/
public void updateColor(FeatureColourI newcol)
{
Color bg = null;
String vlabel = "";
if (newcol.isSimpleColour())
{
bg = newcol.getColour();
setBackground(bg);
}
else
{
if (newcol.isAboveThreshold())
{
vlabel += " (>)";
}
else if (newcol.isBelowThreshold())
{
vlabel += " (<)";
}
if (isColourByLabel = newcol.isColourByLabel())
{
setBackground(bg = Color.white);
vlabel += " (by Label)";
}
else
{
setBackground(bg = newcol.getMinColour());
maxCol = newcol.getMaxColour();
}
}
label = vlabel;
setBackground(bg);
repaint();
}
FeatureColourPanel()
{
super(null);
}
@Override
public void paint(Graphics g)
{
Dimension d = getSize();
if (isGcol)
{
if (isColourByLabel)
{
g.setColor(Color.white);
g.fillRect(d.width / 2, 0, d.width / 2, d.height);
g.setColor(Color.black);
Font f = new Font("Verdana", Font.PLAIN, 10);
g.setFont(f);
g.drawString(MessageManager.getString("label.label"), 0, 0);
}
else
{
g.setColor(maxCol);
g.fillRect(d.width / 2, 0, d.width / 2, d.height);
}
}
}
}
/**
* Shows a dialog allowing the user to create, or amend or delete, sequence
* features. If null in the supplied feature(s), feature type and group
* default to those for the last feature created (with initial defaults of
* "feature_1" and "Jalview").
*
* @param sequences
* @param features
* @param create
* @param ap
* @return
*/
boolean amendFeatures(final List sequences,
final List features, boolean create,
final AlignmentPanel ap)
{
final Panel bigPanel = new Panel(new BorderLayout());
final TextField name = new TextField(16);
final TextField group = new TextField(16);
final TextArea description = new TextArea(3, 35);
final TextField start = new TextField(8);
final TextField end = new TextField(8);
final Choice overlaps;
Button deleteButton = new Button("Delete");
deleteFeature = false;
name.addTextListener(new TextListener()
{
@Override
public void textValueChanged(TextEvent e)
{
warnIfTypeHidden(ap.alignFrame, name.getText());
}
});
group.addTextListener(new TextListener()
{
@Override
public void textValueChanged(TextEvent e)
{
warnIfGroupHidden(ap.alignFrame, group.getText());
}
});
colourPanel = new FeatureColourPanel();
colourPanel.setSize(110, 15);
final FeatureRenderer fr = this;
Panel panel = new Panel(new GridLayout(3, 1));
featureIndex = 0; // feature to be amended.
Panel tmp;
// /////////////////////////////////////
// /MULTIPLE FEATURES AT SELECTED RESIDUE
if (!create && features.size() > 1)
{
panel = new Panel(new GridLayout(4, 1));
tmp = new Panel();
tmp.add(new Label("Select Feature: "));
overlaps = new Choice();
for (SequenceFeature sf : features)
{
String item = sf.getType() + "/" + sf.getBegin() + "-"
+ sf.getEnd();
if (sf.getFeatureGroup() != null)
{
item += " (" + sf.getFeatureGroup() + ")";
}
overlaps.addItem(item);
}
tmp.add(overlaps);
overlaps.addItemListener(new java.awt.event.ItemListener()
{
@Override
public void itemStateChanged(java.awt.event.ItemEvent e)
{
int index = overlaps.getSelectedIndex();
if (index != -1)
{
featureIndex = index;
SequenceFeature sf = features.get(index);
name.setText(sf.getType());
description.setText(sf.getDescription());
group.setText(sf.getFeatureGroup());
start.setText(sf.getBegin() + "");
end.setText(sf.getEnd() + "");
SearchResultsI highlight = new SearchResults();
highlight.addResult(sequences.get(0), sf.getBegin(),
sf.getEnd());
ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
}
FeatureColourI col = getFeatureStyle(name.getText());
if (col == null)
{
Color generatedColour = ColorUtils
.createColourFromName(name.getText());
col = new FeatureColour(generatedColour);
}
colourPanel.updateColor(col);
}
});
panel.add(tmp);
}
// ////////
// ////////////////////////////////////
tmp = new Panel();
panel.add(tmp);
tmp.add(new Label(MessageManager.getString("label.name:"),
Label.RIGHT));
tmp.add(name);
tmp = new Panel();
panel.add(tmp);
tmp.add(new Label(MessageManager.getString("label.group:"),
Label.RIGHT));
tmp.add(group);
tmp = new Panel();
panel.add(tmp);
tmp.add(new Label(MessageManager.getString("label.colour"),
Label.RIGHT));
tmp.add(colourPanel);
bigPanel.add(panel, BorderLayout.NORTH);
panel = new Panel();
panel.add(new Label(MessageManager.getString("label.description:"),
Label.RIGHT));
panel.add(new ScrollPane().add(description));
if (!create)
{
bigPanel.add(panel, BorderLayout.SOUTH);
panel = new Panel();
panel.add(new Label(MessageManager.getString("label.start"),
Label.RIGHT));
panel.add(start);
panel.add(new Label(MessageManager.getString("label.end"),
Label.RIGHT));
panel.add(end);
bigPanel.add(panel, BorderLayout.CENTER);
}
else
{
bigPanel.add(panel, BorderLayout.CENTER);
}
/*
* use defaults for type and group (and update them on Confirm) only
* if feature type has not been supplied by the caller
* (e.g. for Amend, or create features from Find)
*/
SequenceFeature firstFeature = features.get(0);
boolean useLastDefaults = firstFeature.getType() == null;
String featureType = useLastDefaults ? lastFeatureAdded
: firstFeature.getType();
String featureGroup = useLastDefaults ? lastFeatureGroupAdded
: firstFeature.getFeatureGroup();
String title = create
? MessageManager.getString("label.create_new_sequence_features")
: MessageManager.formatMessage("label.amend_delete_features",
new String[]
{ sequences.get(0).getName() });
final JVDialog dialog = new JVDialog(ap.alignFrame, title, true, 385,
240);
dialog.setMainPanel(bigPanel);
name.setText(featureType);
group.setText(featureGroup);
if (!create)
{
dialog.ok.setLabel(MessageManager.getString("label.amend"));
dialog.buttonPanel.add(deleteButton, 1);
deleteButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent evt)
{
deleteFeature = true;
dialog.setVisible(false);
}
});
}
start.setText(firstFeature.getBegin() + "");
end.setText(firstFeature.getEnd() + "");
description.setText(firstFeature.getDescription());
// lookup (or generate) the feature colour
FeatureColourI fcol = getFeatureStyle(name.getText());
// simply display the feature color in a box
colourPanel.updateColor(fcol);
dialog.setResizable(true);
// TODO: render the graduated color in the box.
colourPanel.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent evt)
{
if (!colourPanel.isGcol)
{
new UserDefinedColours(fr, ap.alignFrame);
}
else
{
new FeatureColourChooser(ap.alignFrame, name.getText());
dialog.transferFocus();
}
}
});
dialog.setVisible(true);
FeaturesFile ffile = new FeaturesFile();
/*
* only update default type and group if we used defaults
*/
final String enteredType = name.getText().trim();
final String enteredGroup = group.getText().trim();
final String enteredDesc = description.getText().replace('\n', ' ');
if (dialog.accept && useLastDefaults)
{
lastFeatureAdded = enteredType;
lastFeatureGroupAdded = enteredGroup;
}
if (!create)
{
SequenceFeature sf = features.get(featureIndex);
if (dialog.accept)
{
if (!colourPanel.isGcol)
{
// update colour - otherwise its already done.
setColour(enteredType,
new FeatureColour(colourPanel.getBackground()));
}
int newBegin = sf.begin;
int newEnd = sf.end;
try
{
newBegin = Integer.parseInt(start.getText());
newEnd = Integer.parseInt(end.getText());
} catch (NumberFormatException ex)
{
//
}
/*
* replace the feature by deleting it and adding a new one
* (to ensure integrity of SequenceFeatures data store)
*/
sequences.get(0).deleteFeature(sf);
SequenceFeature newSf = new SequenceFeature(sf, enteredType,
newBegin, newEnd, enteredGroup, sf.getScore());
newSf.setDescription(enteredDesc);
ffile.parseDescriptionHTML(newSf, false);
// amend features dialog only updates one sequence at a time
sequences.get(0).addSequenceFeature(newSf);
boolean typeOrGroupChanged = (!featureType.equals(newSf.getType())
|| !featureGroup.equals(newSf.getFeatureGroup()));
ffile.parseDescriptionHTML(sf, false);
if (typeOrGroupChanged)
{
featuresAdded();
}
}
if (deleteFeature)
{
sequences.get(0).deleteFeature(sf);
// ensure Feature Settings reflects removal of feature / group
featuresAdded();
}
}
else
{
/*
* adding feature(s)
*/
if (dialog.accept && name.getText().length() > 0)
{
for (int i = 0; i < sequences.size(); i++)
{
SequenceFeature sf = features.get(i);
SequenceFeature sf2 = new SequenceFeature(enteredType,
enteredDesc, sf.getBegin(), sf.getEnd(), enteredGroup);
ffile.parseDescriptionHTML(sf2, false);
sequences.get(i).addSequenceFeature(sf2);
}
Color newColour = colourPanel.getBackground();
// setColour(lastFeatureAdded, fcol);
setColour(enteredType, new FeatureColour(newColour)); // was fcol
featuresAdded();
}
else
{
// no update to the alignment
return false;
}
}
// refresh the alignment and the feature settings dialog
if (((jalview.appletgui.AlignViewport) av).featureSettings != null)
{
((jalview.appletgui.AlignViewport) av).featureSettings.refreshTable();
}
// findAllFeatures();
ap.paintAlignment(true, true);
return true;
}
protected void warnIfGroupHidden(Frame frame, String group)
{
if (featureGroups.containsKey(group) && !featureGroups.get(group))
{
String msg = MessageManager.formatMessage("label.warning_hidden",
MessageManager.getString("label.group"), group);
showWarning(frame, msg);
}
}
protected void warnIfTypeHidden(Frame frame, String type)
{
if (getRenderOrder().contains(type))
{
if (!showFeatureOfType(type))
{
String msg = MessageManager.formatMessage("label.warning_hidden",
MessageManager.getString("label.feature_type"), type);
showWarning(frame, msg);
}
}
}
/**
* @param frame
* @param msg
*/
protected void showWarning(Frame frame, String msg)
{
JVDialog d = new JVDialog(frame, "", true, 350, 200);
Panel mp = new Panel();
d.ok.setLabel(MessageManager.getString("action.ok"));
d.cancel.setVisible(false);
mp.setLayout(new FlowLayout());
mp.add(new Label(msg));
d.setMainPanel(mp);
d.setVisible(true);
}
}