import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
import javax.swing.*;
import jalview.datamodel.*;
/**
* DOCUMENT ME!
- *
+ *
* @author $author$
* @version $Revision$
*/
Object currentColour;
String[] renderOrder;
+ PropertyChangeSupport changeSupport=new PropertyChangeSupport(this);
/**
* Creates a new FeatureRenderer object.
- *
- * @param av DOCUMENT ME!
+ *
+ * @param av
+ * DOCUMENT ME!
*/
public FeatureRenderer(AlignViewport av)
{
featureGroups = fr.featureGroups;
featureColours = fr.featureColours;
transparency = fr.transparency;
+ featureOrder = fr.featureOrder;
}
BufferedImage offscreenImage;
}
/**
- * This is used by the Molecule Viewer and Overview to
- * get the accurate colourof the rendered sequence
+ * This is used by the Molecule Viewer and Overview to get the accurate
+ * colourof the rendered sequence
*/
public int findFeatureColour(int initialCol, SequenceI seq, int column)
{
{
lastSeq = seq;
sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
- if (sequenceFeatures == null)
+ if (sequenceFeatures!=null)
{
- return initialCol;
+ sfSize = sequenceFeatures.length;
}
-
- sfSize = sequenceFeatures.length;
}
+
+ if (sequenceFeatures!=lastSeq.getDatasetSequence().getSequenceFeatures()) {
+ sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
+ if (sequenceFeatures != null)
+ {
+ sfSize = sequenceFeatures.length;
+ }
+ }
+
+ if (sequenceFeatures == null)
+ {
+ return initialCol;
+ }
+
if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
{
return Color.white.getRGB();
}
- //Only bother making an offscreen image if transparency is applied
+ // Only bother making an offscreen image if transparency is applied
if (transparency != 1.0f && offscreenImage == null)
{
offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
/**
* DOCUMENT ME!
- *
- * @param g DOCUMENT ME!
- * @param seq DOCUMENT ME!
- * @param sg DOCUMENT ME!
- * @param start DOCUMENT ME!
- * @param end DOCUMENT ME!
- * @param x1 DOCUMENT ME!
- * @param y1 DOCUMENT ME!
- * @param width DOCUMENT ME!
- * @param height DOCUMENT ME!
+ *
+ * @param g
+ * DOCUMENT ME!
+ * @param seq
+ * DOCUMENT ME!
+ * @param sg
+ * DOCUMENT ME!
+ * @param start
+ * DOCUMENT ME!
+ * @param end
+ * DOCUMENT ME!
+ * @param x1
+ * DOCUMENT ME!
+ * @param y1
+ * DOCUMENT ME!
+ * @param width
+ * DOCUMENT ME!
+ * @param height
+ * DOCUMENT ME!
*/
// String type;
// SequenceFeature sf;
}
if (lastSeq == null || seq != lastSeq
- || lastSeq.getSequenceFeatures()!=sequenceFeatures)
+ || seq.getDatasetSequence().getSequenceFeatures()!=sequenceFeatures)
{
lastSeq = seq;
sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
}
boolean newFeatureAdded = false;
-
+ /**
+ * Called when alignment in associated view has new/modified features
+ * to discover and display.
+ *
+ */
public void featuresAdded()
{
lastSeq=null;
}
boolean findingFeatures = false;
+ /**
+ * search the alignment for all new features, give them a colour and display
+ * them. Then fires a PropertyChangeEvent on the changeSupport object.
+ *
+ */
synchronized void findAllFeatures()
{
+ findAllFeatures(true); // add all new features as visible
+ if (!newFeatureAdded && !firing) {
+ firing=true;
+ changeSupport.firePropertyChange("changeSupport",null,null);
+ firing=false;
+ }
+ }
+ /**
+ * Searches alignment for all features and updates colours
+ *
+ * @param newMadeVisible
+ * if true newly added feature types will be rendered immediatly
+ */
+ synchronized void findAllFeatures(boolean newMadeVisible) {
newFeatureAdded = false;
if (findingFeatures)
}
findingFeatures = true;
- jalview.schemes.UserColourScheme ucs = new
- jalview.schemes.UserColourScheme();
if (av.featuresDisplayed == null)
{
av.featuresDisplayed = new Hashtable();
}
- av.featuresDisplayed.clear();
-
Vector allfeatures = new Vector();
+ Vector oldfeatures = new Vector();
+ if (renderOrder!=null)
+ {
+ for (int i=0; i<renderOrder.length; i++) {
+ if (renderOrder[i]!=null)
+ {
+ oldfeatures.addElement(renderOrder[i]);
+ }
+ }
+ }
for (int i = 0; i < av.alignment.getHeight(); i++)
{
SequenceFeature[] features
// If beginning and end are 0, the feature is for the whole sequence
// and we don't want to render the feature in the normal way
- if (getColour(features[index].getType()) == null)
- {
- featureColours.put(features[index].getType(),
- ucs.createColourFromName(features[index].
- getType()));
- }
-
- av.featuresDisplayed.put(features[index].getType(),
+ if (newMadeVisible && !oldfeatures.contains(features[index].getType())) {
+ // this is a new feature type on the alignment. Mark it for
+ // display.
+ av.featuresDisplayed.put(features[index].getType(),
new Integer(getColour(features[index].
getType()).getRGB()));
- allfeatures.addElement(features[index].getType());
- }
+ setOrder(features[index].getType(),0);
+ }
+ }
+ }
+ if (!allfeatures.contains(features[index].getType()))
+ {
+ allfeatures.addElement(features[index].getType());
}
index++;
}
}
-
+ updateRenderOrder(allfeatures);
+ findingFeatures = false;
+ }
+ boolean firing=false;
+ /**
+ * replaces the current renderOrder with the unordered features in allfeatures.
+ * The ordering of any types in both renderOrder and allfeatures is preserved,
+ * and all new feature types are rendered on top of the existing types, in
+ * the order given by getOrder or the order given in allFeatures.
+ * Note. this operates directly on the featureOrder hash for efficiency. TODO:
+ * eliminate the float storage for computing/recalling the persistent ordering
+ *
+ * @param allFeatures
+ */
+ public void updateRenderOrder(Vector allFeatures) {
+ Vector allfeatures = new Vector(allFeatures);
+ String[] oldRender = renderOrder;
renderOrder = new String[allfeatures.size()];
+ boolean initOrders=(featureOrder==null);
+ int opos=0;
+ if (renderOrder!=null)
+ {
+ for (int j=0; i<oldRender.length; j++)
+ {
+ if (oldRender[j]!=null)
+ {
+ if (initOrders)
+ {
+ setOrder(oldRender[j], (1-((float)j)/(float) oldRender.length));
+ }
+ if (allfeatures.contains(oldRender[j])) {
+ renderOrder[opos++] = oldRender[j]; // existing features always
+ // appear below new features
+ allfeatures.removeElement(oldRender[j]);
+ }
+ }
+ }
+ }
+ if (allfeatures.size()==0) {
+ // no new features - leave order unchanged.
+ return;
+ }
+ int i=allfeatures.size()-1;
+ int iSize=i;
+ boolean sort=false;
+ String[] newf = new String[allfeatures.size()];
+ float[] sortOrder = new float[allfeatures.size()];
Enumeration en = allfeatures.elements();
- int i = allfeatures.size() - 1;
+ // sort remaining elements
while (en.hasMoreElements())
{
- renderOrder[i] = en.nextElement().toString();
+ newf[i] = en.nextElement().toString();
+ if (initOrders || !featureOrder.containsKey(newf[i]))
+ {
+ int denom = initOrders ? allfeatures.size() : featureOrder.size();
+ // new unordered feature - compute persistent ordering at tail of
+ // existing features.
+ setOrder(newf[i], ((float)2*denom)/(float)(1+2*denom));
+ }
+ // set order from newly found feature from persisted ordering.
+ sortOrder[i] = 2-((Float) featureOrder.get(newf[i])).floatValue();
+ if (i<iSize)
+ {
+ // only sort if we need to
+ sort = sort || sortOrder[i]>sortOrder[i+1];
+ }
i--;
}
-
- findingFeatures = false;
+ if (sort)
+ jalview.util.QuickSort.sort(sortOrder, newf);
+ sortOrder=null;
+ System.arraycopy(newf, 0, renderOrder, opos, newf.length);
}
-
public Color getColour(String featureType)
{
Color colour = (Color) featureColours.get(featureType);
+ if (colour == null)
+ {
+ jalview.schemes.UserColourScheme ucs = new
+ jalview.schemes.UserColourScheme();
+ featureColours.put(featureType,
+ colour=ucs.createColourFromName(featureType));
+ }
return colour;
}
&& name.getSelectedItem() != null
&& source.getSelectedItem() != null)
{
- //This ensures that the last sequence
- //is refreshed and new features are rendered
+ // This ensures that the last sequence
+ // is refreshed and new features are rendered
lastSeq = null;
lastFeatureAdded = name.getSelectedItem().toString();
lastFeatureGroupAdded = source.getSelectedItem().toString();
{
return transparency;
}
-
+ /**
+ * Replace current ordering with new ordering
+ * @param data { String(Type), Colour(Type), Boolean(Displayed) }
+ */
public void setFeaturePriority(Object[][] data)
{
- // The feature table will display high priority
- // features at the top, but theses are the ones
- // we need to render last, so invert the data
- if (av.featuresDisplayed != null)
- {
- av.featuresDisplayed.clear();
+ setFeaturePriority(data, true);
+ }
+ /**
+ *
+ * @param data { String(Type), Colour(Type), Boolean(Displayed) }
+ * @param visibleNew when true current featureDisplay list will be cleared
+ */
+ public void setFeaturePriority(Object[][] data, boolean visibleNew)
+ {
+ if (visibleNew)
+ {
+ if (av.featuresDisplayed != null)
+ {
+ av.featuresDisplayed.clear();
+ }
+ else
+ {
+ av.featuresDisplayed = new Hashtable();
+ }
}
- else
+ if (data==null)
{
- av.featuresDisplayed = new Hashtable();
- }
- if (data!=null)
return;
+ }
+
+ // The feature table will display high priority
+ // features at the top, but theses are the ones
+ // we need to render last, so invert the data
renderOrder = new String[data.length];
if (data.length > 0)
}
}
+ Hashtable featureOrder=null;
+ /**
+ * analogous to colour - store a normalized ordering for all feature types in
+ * this rendering context.
+ *
+ * @param type
+ * Feature type string
+ * @param position
+ * normalized priority - 0 means always appears on top, 1 means
+ * always last.
+ */
+ public float setOrder(String type, float position)
+ {
+ if (featureOrder==null)
+ {
+ featureOrder = new Hashtable();
+ }
+ featureOrder.put(type, new Float(position));
+ return position;
+ }
+ /**
+ * get the global priority (0 (top) to 1 (bottom))
+ *
+ * @param type
+ * @return [0,1] or -1 for a type without a priority
+ */
+ public float getOrder(String type) {
+ if (featureOrder!=null)
+ {
+ if (featureOrder.containsKey(type))
+ {
+ return ((Float)featureOrder.get(type)).floatValue();
+ }
+ }
+ return -1;
+ }
+ /**
+ * @param listener
+ * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
+ */
+ public void addPropertyChangeListener(PropertyChangeListener listener)
+ {
+ changeSupport.addPropertyChangeListener(listener);
+ }
+
+ /**
+ * @param listener
+ * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
+ */
+ public void removePropertyChangeListener(PropertyChangeListener listener)
+ {
+ changeSupport.removePropertyChangeListener(listener);
+ }
}
import java.awt.*;
import java.awt.event.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
if (af.getViewport().featuresDisplayed == null || fr.renderOrder == null)
{
- fr.findAllFeatures();
+ fr.findAllFeatures(true); // display everything!
}
setTableData();
-
+ final PropertyChangeListener change;
+ final FeatureSettings fs=this;
+ fr.addPropertyChangeListener(change=new PropertyChangeListener() {
+ private boolean firing=false;
+ public void propertyChange(PropertyChangeEvent evt)
+ {
+ if (!fs.resettingTable && !firing) {
+ firing=true;
+ resetTable(null);
+ firing=false;
+ }
+ }
+
+ });
+
frame = new JInternalFrame();
frame.setContentPane(this);
Desktop.addInternalFrame(frame, "Sequence Feature Settings", 400, 450);
+ frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
+ {
+ public void internalFrameClosed(
+ javax.swing.event.InternalFrameEvent evt)
+ {
+ fr.removePropertyChangeListener(change);
+ }
+ ;
+ });
frame.setLayer(JLayeredPane.PALETTE_LAYER);
}
-
+ /**
+ * contains a float[3] for each feature type string. created by setTableData
+ */
+ Hashtable typeWidth=null;
synchronized public void setTableData()
{
if (fr.featureGroups == null)
{
fr.featureGroups = new Hashtable();
}
-
Vector allFeatures = new Vector();
Vector allGroups = new Vector();
SequenceFeature[] tmpfeatures;
String group;
-
for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
{
if (af.getViewport().alignment.getSequenceAt(i).getDatasetSequence().
{
allGroups.addElement(group);
- boolean visible = true;
+ boolean visible;
if (fr.featureGroups.containsKey(group))
{
visible = ( (Boolean) fr.featureGroups.get(group)).booleanValue();
+ } else {
+ visible=true; //initGroups || fr.av.featuresDisplayed.containsKey(tmpfeatures[index].getType());
}
if (groupPanel == null)
if (alreadyAdded)
{
+
continue;
}
fr.featureGroups.put(group, new Boolean(visible));
-
+ final String grp = group;
final JCheckBox check = new JCheckBox(group, visible);
check.setFont(new Font("Serif", Font.BOLD, 12));
check.addItemListener(new ItemListener()
af.alignPanel.overviewPanel.updateOverviewImage();
}
- resetTable(true);
+ resetTable(new String[] { grp } );
}
});
groupPanel.add(check);
index++;
}
}
-
- resetTable(false);
+
+ resetTable(null);
validate();
}
-
- void resetTable(boolean groupsChanged)
+ boolean resettingTable=false;
+ void resetTable(String[] groupChanged)
{
+ resettingTable=true;
+ typeWidth=new Hashtable();
+ Vector groupchanged = new Vector();
+ // TODO: change avWidth calculation to 'per-sequence' average and use long rather than float
+ float[] avWidth=null;
SequenceFeature[] tmpfeatures;
String group = null, type;
Vector visibleChecks = new Vector();
//Find out which features should be visible depending on which groups
//are selected / deselected
+ //and recompute average width ordering
for (int i = 0; i < af.getViewport().alignment.getHeight(); i++)
{
( (Boolean) fr.featureGroups.get(group)).booleanValue())
{
type = tmpfeatures[index].getType();
+ if (groupchanged==null || groupchanged.contains(group)) {
+ //af.getViewport().featuresDisplayed.put(type, fr.getColour(type));
+ }
if (!visibleChecks.contains(type))
{
visibleChecks.addElement(type);
}
}
+ if (!typeWidth.containsKey(tmpfeatures[index].getType())) {
+ typeWidth.put(tmpfeatures[index].getType(), avWidth=new float[3]);
+ } else {
+ avWidth = (float[]) typeWidth.get(tmpfeatures[index].getType());
+ }
+ avWidth[0]++;
+ if (tmpfeatures[index].getBegin()>tmpfeatures[index].getEnd())
+ {
+ avWidth[1]+=1+tmpfeatures[index].getBegin()-tmpfeatures[index].getEnd();
+ } else {
+ avWidth[1]+=1+tmpfeatures[index].getEnd()-tmpfeatures[index].getBegin();
+ }
index++;
}
}
if (fr.renderOrder != null)
{
+ fr.findAllFeatures(groupChanged!=null); // prod to update colourschemes. but don't affect display
//First add the checks in the previous render order,
//in case the window has been closed and reopened
for (int ro = fr.renderOrder.length - 1; ro > -1; ro--)
data[dataIndex][0] = type;
data[dataIndex][1] = fr.getColour(type);
- data[dataIndex][2] = new Boolean(af.getViewport().featuresDisplayed.
- containsKey(type));
+ data[dataIndex][2] = new Boolean(af.getViewport().featuresDisplayed.containsKey(type));
dataIndex++;
visibleChecks.removeElement(type);
}
bigPanel.add(groupPanel, BorderLayout.NORTH);
}
- updateFeatureRenderer(data);
-
+ updateFeatureRenderer(data, groupChanged!=null);
+ resettingTable=false;
+ }
+ /**
+ * reorder data based on the featureRenderers global priority list.
+ * @param data
+ */
+ private void ensureOrder(Object[][] data)
+ {
+ boolean sort=false;
+ float[] order = new float[data.length];
+ for (int i=0;i<order.length; i++)
+ {
+ order[i] = fr.getOrder(data[i][0].toString());
+ if (order[i]<0)
+ order[i] = fr.setOrder(data[i][0].toString(), i/order.length);
+ if (i>1)
+ sort = sort || order[i-1]>order[i];
+ }
+ if (sort)
+ jalview.util.QuickSort.sort(order, data);
}
void load()
for (int i = 0; i < jucs.getColourCount(); i++)
{
- fr.setColour(jucs.getColour(i).getName(),
+ String name;
+ fr.setColour(name=jucs.getColour(i).getName(),
new Color(Integer.parseInt(jucs.getColour(i).getRGB(),
16)));
+ fr.setOrder(name,i/jucs.getColourCount());
}
setTableData();
i, 2);
}
}
-
+ public void orderByAvWidth() {
+ if (table==null || table.getModel()==null)
+ return;
+ Object[][] data = ((FeatureTableModel) table.getModel()).getData();
+ float[] width = new float[data.length];
+ float[] awidth;
+ float max=0;
+ int num=0;
+ for (int i=0;i<data.length;i++) {
+ awidth = (float[]) typeWidth.get(data[i][0]);
+ if (awidth[0]>0) {
+ width[i] = awidth[1]/awidth[0];// *awidth[0]*awidth[2]; - better weight - but have to make per sequence, too (awidth[2])
+ //if (width[i]==1) // hack to distinguish single width sequences.
+ num++;
+ } else {
+ width[i]=0;
+ }
+ if (max<width[i])
+ max=width[i];
+ }
+ boolean sort=false;
+ for (int i=0;i<width.length; i++) {
+ //awidth = (float[]) typeWidth.get(data[i][0]);
+ if (width[i]==0)
+ {
+ width[i] = fr.getOrder(data[i][0].toString());
+ if (width[i]<0)
+ {
+ width[i] = fr.setOrder(data[i][0].toString(), i/data.length);
+ }
+ } else {
+ width[i] /=max; // normalize
+ fr.setOrder(data[i][0].toString(), width[i]); // store for later
+ }
+ if (i>0)
+ sort = sort || width[i-1]>width[i];
+ }
+ if (sort)
+ jalview.util.QuickSort.sort(width, data);
+ // update global priority order
+
+ updateFeatureRenderer(data,false);
+ table.repaint();
+ }
public void close()
{
try
public void updateFeatureRenderer(Object[][] data)
{
- fr.setFeaturePriority(data);
+ updateFeatureRenderer(data, true);
+ }
+ private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
+ {
+ fr.setFeaturePriority(data, visibleNew);
af.alignPanel.paintAlignment(true);
if (af.alignPanel.overviewPanel != null)
JButton fetchDAS = new JButton();
JButton saveDAS = new JButton();
JButton cancelDAS = new JButton();
+ JButton optimizeOrder = new JButton();
+ JPanel transbuttons = new JPanel(new BorderLayout());
private void jbInit()
throws Exception
{
invertSelection();
}
});
+ optimizeOrder.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
+ optimizeOrder.setText("Optimise Order");
+ optimizeOrder.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ orderByAvWidth();
+ }
+ });
cancel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
cancel.setText("Cancel");
cancel.addActionListener(new ActionListener()
tabbedPane.addTab("DAS Settings", dasSettingsPane);
bigPanel.add(transPanel, java.awt.BorderLayout.SOUTH);
transPanel.add(transparency);
- transPanel.add(invert);
+ transbuttons.add(invert, java.awt.BorderLayout.NORTH);
+ transbuttons.add(optimizeOrder,java.awt.BorderLayout.SOUTH);
+ transPanel.add(transbuttons);
buttonPanel.add(ok);
buttonPanel.add(cancel);
buttonPanel.add(loadColours);