2 * Jalview - A Sequence Alignment Editor and Viewer
3 * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 import java.awt.event.*;
25 import java.awt.image.*;
26 import java.beans.PropertyChangeListener;
27 import java.beans.PropertyChangeSupport;
31 import jalview.datamodel.*;
39 public class FeatureRenderer
43 float transparency = 1.0f;
47 Hashtable featureColours = new Hashtable();
49 // A higher level for grouping features of a
51 Hashtable featureGroups = null;
53 // This is actually an Integer held in the hashtable,
54 // Retrieved using the key feature type
58 PropertyChangeSupport changeSupport=new PropertyChangeSupport(this);
61 * Creates a new FeatureRenderer object.
66 public FeatureRenderer(AlignViewport av)
71 public void transferSettings(FeatureRenderer fr)
73 renderOrder = fr.renderOrder;
74 featureGroups = fr.featureGroups;
75 featureColours = fr.featureColours;
76 transparency = fr.transparency;
77 featureOrder = fr.featureOrder;
80 BufferedImage offscreenImage;
81 boolean offscreenRender = false;
82 public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
84 return new Color(findFeatureColour(initialCol.getRGB(),
89 * This is used by the Molecule Viewer and Overview to get the accurate
90 * colourof the rendered sequence
92 public int findFeatureColour(int initialCol, SequenceI seq, int column)
94 if (!av.showSequenceFeatures)
102 sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
103 if (sequenceFeatures!=null)
105 sfSize = sequenceFeatures.length;
109 if (sequenceFeatures!=lastSeq.getDatasetSequence().getSequenceFeatures()) {
110 sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
111 if (sequenceFeatures != null)
113 sfSize = sequenceFeatures.length;
117 if (sequenceFeatures == null)
123 if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
125 return Color.white.getRGB();
128 // Only bother making an offscreen image if transparency is applied
129 if (transparency != 1.0f && offscreenImage == null)
131 offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
134 currentColour = null;
136 offscreenRender = true;
138 if (offscreenImage != null)
140 offscreenImage.setRGB(0, 0, initialCol);
141 drawSequence(offscreenImage.getGraphics(),
145 return offscreenImage.getRGB(0, 0);
151 lastSeq.findPosition(column),
154 if (currentColour == null)
160 return ( (Integer) currentColour).intValue();
189 // SequenceFeature sf;
191 SequenceFeature[] sequenceFeatures;
192 int sfSize, sfindex, spos, epos;
194 public void drawSequence(Graphics g, SequenceI seq,
195 int start, int end, int y1)
197 if (seq.getDatasetSequence().getSequenceFeatures() == null
198 || seq.getDatasetSequence().getSequenceFeatures().length == 0)
205 fm = g.getFontMetrics();
208 if (av.featuresDisplayed == null
209 || renderOrder == null
213 if (av.featuresDisplayed.size() < 1)
218 sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
219 sfSize = sequenceFeatures.length;
222 if (lastSeq == null || seq != lastSeq
223 || seq.getDatasetSequence().getSequenceFeatures()!=sequenceFeatures)
226 sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
227 sfSize = sequenceFeatures.length;
230 if (transparency != 1 && g != null)
232 Graphics2D g2 = (Graphics2D) g;
234 AlphaComposite.getInstance(
235 AlphaComposite.SRC_OVER, transparency));
238 if (!offscreenRender)
240 spos = lastSeq.findPosition(start);
241 epos = lastSeq.findPosition(end);
245 for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
247 type = renderOrder[renderIndex];
249 if (type == null || !av.featuresDisplayed.containsKey(type))
254 // loop through all features in sequence to find
255 // current feature to render
256 for (sfindex = 0; sfindex < sfSize; sfindex++)
258 if (sequenceFeatures.length <= sfindex)
262 if (!sequenceFeatures[sfindex].type.equals(type))
267 if (featureGroups != null
268 && sequenceFeatures[sfindex].featureGroup != null
270 featureGroups.containsKey(sequenceFeatures[sfindex].featureGroup)
272 ! ( (Boolean) featureGroups.get(sequenceFeatures[sfindex].
279 if (!offscreenRender && (sequenceFeatures[sfindex].getBegin() > epos
280 || sequenceFeatures[sfindex].getEnd() < spos))
285 if (offscreenRender && offscreenImage == null)
287 if (sequenceFeatures[sfindex].begin <= start &&
288 sequenceFeatures[sfindex].end >= start)
290 currentColour = av.featuresDisplayed.get(sequenceFeatures[sfindex].
294 else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
297 renderFeature(g, seq,
298 seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
299 seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
300 new Color( ( (Integer) av.featuresDisplayed.get(
301 sequenceFeatures[sfindex].type)).intValue()),
303 renderFeature(g, seq,
304 seq.findIndex(sequenceFeatures[sfindex].end) - 1,
305 seq.findIndex(sequenceFeatures[sfindex].end) - 1,
306 new Color( ( (Integer) av.featuresDisplayed.get(
307 sequenceFeatures[sfindex].type)).intValue()),
313 renderFeature(g, seq,
314 seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
315 seq.findIndex(sequenceFeatures[sfindex].end) - 1,
316 getColour(sequenceFeatures[sfindex].type),
324 if (transparency != 1.0f && g != null)
326 Graphics2D g2 = (Graphics2D) g;
328 AlphaComposite.getInstance(
329 AlphaComposite.SRC_OVER, 1.0f));
335 void renderFeature(Graphics g, SequenceI seq,
336 int fstart, int fend, Color featureColour, int start,
340 if ( ( (fstart <= end) && (fend >= start)))
343 { // fix for if the feature we have starts before the sequence start,
344 fstart = start; // but the feature end is still valid!!
351 int pady = (y1 + av.charHeight) - av.charHeight / 5;
352 for (i = fstart; i <= fend; i++)
354 s = seq.getCharAt(i);
356 if (jalview.util.Comparison.isGap(s))
361 g.setColor(featureColour);
363 g.fillRect( (i - start) * av.charWidth, y1, av.charWidth, av.charHeight);
365 if (offscreenRender || !av.validCharWidth)
370 g.setColor(Color.white);
371 charOffset = (av.charWidth - fm.charWidth(s)) / 2;
372 g.drawString(String.valueOf(s),
373 charOffset + (av.charWidth * (i - start)),
380 boolean newFeatureAdded = false;
382 * Called when alignment in associated view has new/modified features
383 * to discover and display.
386 public void featuresAdded()
392 boolean findingFeatures = false;
394 * search the alignment for all new features, give them a colour and display
395 * them. Then fires a PropertyChangeEvent on the changeSupport object.
398 synchronized void findAllFeatures()
400 findAllFeatures(true); // add all new features as visible
401 if (!newFeatureAdded && !firing) {
403 changeSupport.firePropertyChange("changeSupport",null,null);
408 * Searches alignment for all features and updates colours
410 * @param newMadeVisible
411 * if true newly added feature types will be rendered immediatly
413 synchronized void findAllFeatures(boolean newMadeVisible) {
414 newFeatureAdded = false;
418 newFeatureAdded = true;
422 findingFeatures = true;
424 if (av.featuresDisplayed == null)
426 av.featuresDisplayed = new Hashtable();
429 Vector allfeatures = new Vector();
430 Vector oldfeatures = new Vector();
431 if (renderOrder!=null)
433 for (int i=0; i<renderOrder.length; i++) {
434 if (renderOrder[i]!=null)
436 oldfeatures.addElement(renderOrder[i]);
440 for (int i = 0; i < av.alignment.getHeight(); i++)
442 SequenceFeature[] features
443 = av.alignment.getSequenceAt(i).getDatasetSequence().
444 getSequenceFeatures();
446 if (features == null)
452 while (index < features.length)
454 if (!av.featuresDisplayed.containsKey(features[index].getType()))
456 if (! (features[index].begin == 0 && features[index].end == 0))
458 // If beginning and end are 0, the feature is for the whole sequence
459 // and we don't want to render the feature in the normal way
461 if (newMadeVisible && !oldfeatures.contains(features[index].getType())) {
462 // this is a new feature type on the alignment. Mark it for
464 av.featuresDisplayed.put(features[index].getType(),
465 new Integer(getColour(features[index].
466 getType()).getRGB()));
467 setOrder(features[index].getType(),0);
471 if (!allfeatures.contains(features[index].getType()))
473 allfeatures.addElement(features[index].getType());
478 updateRenderOrder(allfeatures);
479 findingFeatures = false;
481 boolean firing=false;
483 * replaces the current renderOrder with the unordered features in allfeatures.
484 * The ordering of any types in both renderOrder and allfeatures is preserved,
485 * and all new feature types are rendered on top of the existing types, in
486 * the order given by getOrder or the order given in allFeatures.
487 * Note. this operates directly on the featureOrder hash for efficiency. TODO:
488 * eliminate the float storage for computing/recalling the persistent ordering
492 public void updateRenderOrder(Vector allFeatures) {
493 Vector allfeatures = new Vector(allFeatures);
494 String[] oldRender = renderOrder;
495 renderOrder = new String[allfeatures.size()];
496 boolean initOrders=(featureOrder==null);
500 for (int j=0; i<oldRender.length; j++)
502 if (oldRender[j]!=null)
506 setOrder(oldRender[j], (1-((float)j)/(float) oldRender.length));
508 if (allfeatures.contains(oldRender[j])) {
509 renderOrder[opos++] = oldRender[j]; // existing features always
510 // appear below new features
511 allfeatures.removeElement(oldRender[j]);
516 if (allfeatures.size()==0) {
517 // no new features - leave order unchanged.
520 int i=allfeatures.size()-1;
523 String[] newf = new String[allfeatures.size()];
524 float[] sortOrder = new float[allfeatures.size()];
525 Enumeration en = allfeatures.elements();
526 // sort remaining elements
527 while (en.hasMoreElements())
529 newf[i] = en.nextElement().toString();
530 if (initOrders || !featureOrder.containsKey(newf[i]))
532 int denom = initOrders ? allfeatures.size() : featureOrder.size();
533 // new unordered feature - compute persistent ordering at tail of
534 // existing features.
535 setOrder(newf[i], ((float)2*denom)/(float)(1+2*denom));
537 // set order from newly found feature from persisted ordering.
538 sortOrder[i] = 2-((Float) featureOrder.get(newf[i])).floatValue();
541 // only sort if we need to
542 sort = sort || sortOrder[i]>sortOrder[i+1];
547 jalview.util.QuickSort.sort(sortOrder, newf);
549 System.arraycopy(newf, 0, renderOrder, opos, newf.length);
551 public Color getColour(String featureType)
553 Color colour = (Color) featureColours.get(featureType);
556 jalview.schemes.UserColourScheme ucs = new
557 jalview.schemes.UserColourScheme();
558 featureColours.put(featureType,
559 colour=ucs.createColourFromName(featureType));
564 static String lastFeatureAdded;
565 static String lastFeatureGroupAdded;
566 static String lastDescriptionAdded;
568 public boolean createNewFeatures(SequenceI[] sequences,
569 SequenceFeature[] features)
571 return amendFeatures(sequences, features, true, null);
574 int featureIndex = 0;
575 boolean amendFeatures(final SequenceI[] sequences,
576 final SequenceFeature[] features,
578 final AlignmentPanel ap)
580 JPanel bigPanel = new JPanel(new BorderLayout());
581 final JComboBox name = new JComboBox();
582 final JComboBox source = new JComboBox();
583 final JTextArea description = new JTextArea(3, 25);
584 final JSpinner start = new JSpinner();
585 final JSpinner end = new JSpinner();
586 start.setPreferredSize(new Dimension(80, 20));
587 end.setPreferredSize(new Dimension(80, 20));
588 final JPanel colour = new JPanel();
589 colour.setBorder(BorderFactory.createEtchedBorder());
590 colour.setMaximumSize(new Dimension(40, 10));
591 colour.addMouseListener(new MouseAdapter()
593 public void mousePressed(MouseEvent evt)
595 colour.setBackground(
596 JColorChooser.showDialog(Desktop.desktop,
597 "Select Feature Colour",
598 colour.getBackground()));
602 JPanel panel = new JPanel(new GridLayout(3, 2));
603 panel.add(new JLabel("Sequence Feature Name: ", JLabel.RIGHT));
605 panel.add(new JLabel("Feature Group: ", JLabel.RIGHT));
607 panel.add(new JLabel("Feature Colour: ", JLabel.RIGHT));
608 JPanel tmp = new JPanel();
610 colour.setPreferredSize(new Dimension(150, 15));
612 name.setEditable(true);
613 source.setEditable(true);
615 bigPanel.add(panel, BorderLayout.NORTH);
616 panel = new JPanel();
617 panel.add(new JLabel("Description: ", JLabel.RIGHT));
618 description.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
619 description.setLineWrap(true);
620 panel.add(new JScrollPane(description));
624 bigPanel.add(panel, BorderLayout.SOUTH);
626 panel = new JPanel();
627 panel.add(new JLabel(" Start:", JLabel.RIGHT));
629 panel.add(new JLabel(" End:", JLabel.RIGHT));
631 bigPanel.add(panel, BorderLayout.CENTER);
635 bigPanel.add(panel, BorderLayout.CENTER);
638 if (lastFeatureAdded == null)
640 if (features[0].type != null)
642 lastFeatureAdded = features[0].type;
646 lastFeatureAdded = "feature_1";
650 if (lastFeatureGroupAdded == null)
652 if (features[0].featureGroup != null)
654 lastFeatureGroupAdded = features[0].featureGroup;
658 lastFeatureAdded = "Jalview";
663 if (featureGroups != null)
665 en = featureGroups.keys();
666 while (en.hasMoreElements())
668 source.addItem(en.nextElement().toString());
674 if (av.featuresDisplayed != null)
676 en = av.featuresDisplayed.keys();
677 while (en.hasMoreElements())
679 name.addItem(en.nextElement().toString());
683 name.setSelectedItem(lastFeatureAdded);
684 source.setSelectedItem(lastFeatureGroupAdded);
686 lastDescriptionAdded == null ?
687 features[0].description : lastDescriptionAdded);
689 if (getColour(lastFeatureAdded) != null)
691 colour.setBackground(getColour(lastFeatureAdded));
695 colour.setBackground(new Color(60, 160, 115));
699 else if (!newFeatures)
702 for (int f = 0; f < features.length; f++)
704 name.addItem(features[f].getType().toString());
707 description.setText(features[0].getDescription());
708 source.setSelectedItem(features[0].getFeatureGroup());
709 start.setValue(new Integer(features[0].getBegin()));
710 end.setValue(new Integer(features[0].getEnd()));
711 colour.setBackground(
712 getColour(name.getSelectedItem().toString()));
713 name.addItemListener(new ItemListener()
715 public void itemStateChanged(ItemEvent e)
717 int index = name.getSelectedIndex();
720 featureIndex = index;
721 description.setText(features[index].getDescription());
722 source.setSelectedItem(features[index].getFeatureGroup());
723 start.setValue(new Integer(features[index].getBegin()));
724 end.setValue(new Integer(features[index].getEnd()));
725 colour.setBackground(
726 getColour(name.getSelectedItem().toString()));
728 SearchResults highlight = new SearchResults();
729 highlight.addResult(sequences[0],
730 features[index].getBegin(),
731 features[index].getEnd());
733 ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
736 Color col = getColour(name.getSelectedItem().toString());
740 jalview.schemes.UserColourScheme()
741 .createColourFromName(name.getSelectedItem().toString());
744 colour.setBackground(col);
753 options = new Object[]
755 "Amend", "Delete", "Cancel"};
759 options = new Object[]
764 String title = newFeatures ? "Create New Sequence Feature(s)" :
765 "Amend/Delete Features for "
766 + sequences[0].getName();
768 int reply = JOptionPane.showInternalOptionDialog(Desktop.desktop,
771 JOptionPane.YES_NO_CANCEL_OPTION,
772 JOptionPane.QUESTION_MESSAGE,
776 jalview.io.FeaturesFile ffile = new jalview.io.FeaturesFile();
778 if (reply == JOptionPane.OK_OPTION
779 && name.getSelectedItem() != null
780 && source.getSelectedItem() != null)
782 // This ensures that the last sequence
783 // is refreshed and new features are rendered
785 lastFeatureAdded = name.getSelectedItem().toString();
786 lastFeatureGroupAdded = source.getSelectedItem().toString();
787 lastDescriptionAdded = description.getText().replaceAll("\n", " ");
792 SequenceFeature sf = features[featureIndex];
794 if (reply == JOptionPane.NO_OPTION)
796 sequences[0].getDatasetSequence().deleteFeature(sf);
798 else if (reply == JOptionPane.YES_OPTION)
800 sf.type = lastFeatureAdded;
801 sf.featureGroup = lastFeatureGroupAdded;
802 sf.description = lastDescriptionAdded;
803 setColour(sf.type, colour.getBackground());
806 sf.begin = ( (Integer) start.getValue()).intValue();
807 sf.end = ( (Integer) end.getValue()).intValue();
809 catch (NumberFormatException ex)
812 ffile.parseDescriptionHTML(sf, false);
817 if (reply == JOptionPane.OK_OPTION
818 && name.getSelectedItem() != null
819 && source.getSelectedItem() != null)
821 for (int i = 0; i < sequences.length; i++)
823 features[i].type = lastFeatureAdded;
824 features[i].featureGroup = lastFeatureGroupAdded;
825 features[i].description = lastDescriptionAdded;
826 sequences[i].addSequenceFeature(features[i]);
827 ffile.parseDescriptionHTML(features[i], false);
830 if (av.featuresDisplayed == null)
832 av.featuresDisplayed = new Hashtable();
835 if (featureGroups == null)
837 featureGroups = new Hashtable();
840 featureGroups.put(lastFeatureGroupAdded, new Boolean(true));
842 Color col = colour.getBackground();
843 setColour(lastFeatureAdded, colour.getBackground());
845 av.featuresDisplayed.put(lastFeatureGroupAdded,
846 new Integer(col.getRGB()));
858 if (name.getSelectedIndex() == -1)
866 public void setColour(String featureType, Color col)
868 featureColours.put(featureType, col);
871 public void setTransparency(float value)
873 transparency = value;
876 public float getTransparency()
881 * Replace current ordering with new ordering
882 * @param data { String(Type), Colour(Type), Boolean(Displayed) }
884 public void setFeaturePriority(Object[][] data)
886 setFeaturePriority(data, true);
890 * @param data { String(Type), Colour(Type), Boolean(Displayed) }
891 * @param visibleNew when true current featureDisplay list will be cleared
893 public void setFeaturePriority(Object[][] data, boolean visibleNew)
897 if (av.featuresDisplayed != null)
899 av.featuresDisplayed.clear();
903 av.featuresDisplayed = new Hashtable();
911 // The feature table will display high priority
912 // features at the top, but theses are the ones
913 // we need to render last, so invert the data
914 renderOrder = new String[data.length];
918 for (int i = 0; i < data.length; i++)
920 String type = data[i][0].toString();
921 setColour(type, (Color) data[i][1]);
922 if ( ( (Boolean) data[i][2]).booleanValue())
924 av.featuresDisplayed.put(type, new Integer(getColour(type).getRGB()));
927 renderOrder[data.length - i - 1] = type;
932 Hashtable featureOrder=null;
934 * analogous to colour - store a normalized ordering for all feature types in
935 * this rendering context.
938 * Feature type string
940 * normalized priority - 0 means always appears on top, 1 means
943 public float setOrder(String type, float position)
945 if (featureOrder==null)
947 featureOrder = new Hashtable();
949 featureOrder.put(type, new Float(position));
953 * get the global priority (0 (top) to 1 (bottom))
956 * @return [0,1] or -1 for a type without a priority
958 public float getOrder(String type) {
959 if (featureOrder!=null)
961 if (featureOrder.containsKey(type))
963 return ((Float)featureOrder.get(type)).floatValue();
971 * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
973 public void addPropertyChangeListener(PropertyChangeListener listener)
975 changeSupport.addPropertyChangeListener(listener);
980 * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
982 public void removePropertyChangeListener(PropertyChangeListener listener)
984 changeSupport.removePropertyChangeListener(listener);