2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3 * Copyright (C) 2008 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
47 float transparency = 1.0f;
53 Hashtable featureColours = new Hashtable();
55 // A higher level for grouping features of a
57 Hashtable featureGroups = new Hashtable();
59 // This is actually an Integer held in the hashtable,
60 // Retrieved using the key feature type
65 PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
70 * Creates a new FeatureRenderer object.
75 public FeatureRenderer(AlignmentPanel ap)
81 public class FeatureRendererSettings implements Cloneable
85 Hashtable featureGroups;
87 Hashtable featureColours;
91 Hashtable featureOrder;
93 public FeatureRendererSettings(String[] renderOrder,
94 Hashtable featureGroups, Hashtable featureColours,
95 float transparency, Hashtable featureOrder)
98 this.renderOrder = renderOrder;
99 this.featureGroups = featureGroups;
100 this.featureColours = featureColours;
101 this.transparency = transparency;
102 this.featureOrder = featureOrder;
105 public FeatureRendererSettings(FeatureRenderer fr)
107 this.renderOrder = fr.renderOrder;
108 this.featureGroups = fr.featureGroups;
109 this.featureColours = fr.featureColours;
110 this.transparency = fr.transparency;
111 this.featureOrder = fr.featureOrder;
115 public FeatureRendererSettings getSettings()
117 return new FeatureRendererSettings(this);
120 public void transferSettings(FeatureRendererSettings fr)
122 this.renderOrder = fr.renderOrder;
123 this.featureGroups = fr.featureGroups;
124 this.featureColours = fr.featureColours;
125 this.transparency = fr.transparency;
126 this.featureOrder = fr.featureOrder;
129 public void transferSettings(FeatureRenderer fr)
131 this.renderOrder = fr.renderOrder;
132 this.featureGroups = fr.featureGroups;
133 this.featureColours = fr.featureColours;
134 this.transparency = fr.transparency;
135 this.featureOrder = fr.featureOrder;
138 BufferedImage offscreenImage;
140 boolean offscreenRender = false;
142 public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
144 return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
148 * This is used by the Molecule Viewer and Overview to get the accurate
149 * colourof the rendered sequence
151 public int findFeatureColour(int initialCol, SequenceI seq, int column)
153 if (!av.showSequenceFeatures)
161 sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
162 if (sequenceFeatures != null)
164 sfSize = sequenceFeatures.length;
168 if (sequenceFeatures != lastSeq.getDatasetSequence()
169 .getSequenceFeatures())
171 sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
172 if (sequenceFeatures != null)
174 sfSize = sequenceFeatures.length;
178 if (sequenceFeatures == null || sfSize == 0)
183 if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
185 return Color.white.getRGB();
188 // Only bother making an offscreen image if transparency is applied
189 if (transparency != 1.0f && offscreenImage == null)
191 offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
194 currentColour = null;
196 offscreenRender = true;
198 if (offscreenImage != null)
200 offscreenImage.setRGB(0, 0, initialCol);
201 drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
203 return offscreenImage.getRGB(0, 0);
207 drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
209 if (currentColour == null)
215 return ((Integer) currentColour).intValue();
244 // SequenceFeature sf;
247 SequenceFeature[] sequenceFeatures;
249 int sfSize, sfindex, spos, epos;
251 synchronized public void drawSequence(Graphics g, SequenceI seq,
252 int start, int end, int y1)
255 if (seq.getDatasetSequence().getSequenceFeatures() == null
256 || seq.getDatasetSequence().getSequenceFeatures().length == 0)
263 fm = g.getFontMetrics();
266 if (av.featuresDisplayed == null || renderOrder == null
270 if (av.featuresDisplayed.size() < 1)
275 sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
280 || seq.getDatasetSequence().getSequenceFeatures() != sequenceFeatures)
283 sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
286 if (transparency != 1 && g != null)
288 Graphics2D g2 = (Graphics2D) g;
289 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
293 if (!offscreenRender)
295 spos = lastSeq.findPosition(start);
296 epos = lastSeq.findPosition(end);
299 sfSize = sequenceFeatures.length;
301 for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
303 type = renderOrder[renderIndex];
305 if (type == null || !av.featuresDisplayed.containsKey(type))
310 // loop through all features in sequence to find
311 // current feature to render
312 for (sfindex = 0; sfindex < sfSize; sfindex++)
314 if (!sequenceFeatures[sfindex].type.equals(type))
319 if (featureGroups != null
320 && sequenceFeatures[sfindex].featureGroup != null
321 && sequenceFeatures[sfindex].featureGroup.length() != 0
323 .containsKey(sequenceFeatures[sfindex].featureGroup)
324 && !((Boolean) featureGroups
325 .get(sequenceFeatures[sfindex].featureGroup))
332 && (sequenceFeatures[sfindex].getBegin() > epos || sequenceFeatures[sfindex]
338 if (offscreenRender && offscreenImage == null)
340 if (sequenceFeatures[sfindex].begin <= start
341 && sequenceFeatures[sfindex].end >= start)
343 currentColour = av.featuresDisplayed
344 .get(sequenceFeatures[sfindex].type);
347 else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
353 seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
354 seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
355 new Color(((Integer) av.featuresDisplayed
356 .get(sequenceFeatures[sfindex].type)).intValue()),
361 seq.findIndex(sequenceFeatures[sfindex].end) - 1,
362 seq.findIndex(sequenceFeatures[sfindex].end) - 1,
363 new Color(((Integer) av.featuresDisplayed
364 .get(sequenceFeatures[sfindex].type)).intValue()),
370 renderFeature(g, seq, seq
371 .findIndex(sequenceFeatures[sfindex].begin) - 1, seq
372 .findIndex(sequenceFeatures[sfindex].end) - 1,
373 getColour(sequenceFeatures[sfindex].type), start, end, y1);
380 if (transparency != 1.0f && g != null)
382 Graphics2D g2 = (Graphics2D) g;
383 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
392 void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
393 Color featureColour, int start, int end, int y1)
396 if (((fstart <= end) && (fend >= start)))
399 { // fix for if the feature we have starts before the sequence start,
400 fstart = start; // but the feature end is still valid!!
407 int pady = (y1 + av.charHeight) - av.charHeight / 5;
408 for (i = fstart; i <= fend; i++)
410 s = seq.getCharAt(i);
412 if (jalview.util.Comparison.isGap(s))
417 g.setColor(featureColour);
419 g.fillRect((i - start) * av.charWidth, y1, av.charWidth,
422 if (offscreenRender || !av.validCharWidth)
427 g.setColor(Color.white);
428 charOffset = (av.charWidth - fm.charWidth(s)) / 2;
429 g.drawString(String.valueOf(s), charOffset
430 + (av.charWidth * (i - start)), pady);
436 boolean newFeatureAdded = false;
439 * Called when alignment in associated view has new/modified features to
440 * discover and display.
443 public void featuresAdded()
449 boolean findingFeatures = false;
452 * search the alignment for all new features, give them a colour and display
453 * them. Then fires a PropertyChangeEvent on the changeSupport object.
456 void findAllFeatures()
458 synchronized (firing)
460 if (firing.equals(Boolean.FALSE))
462 firing = Boolean.TRUE;
463 findAllFeatures(true); // add all new features as visible
464 changeSupport.firePropertyChange("changeSupport", null, null);
465 firing = Boolean.FALSE;
471 * Searches alignment for all features and updates colours
473 * @param newMadeVisible
474 * if true newly added feature types will be rendered
477 synchronized void findAllFeatures(boolean newMadeVisible)
479 newFeatureAdded = false;
483 newFeatureAdded = true;
487 findingFeatures = true;
489 if (av.featuresDisplayed == null)
491 av.featuresDisplayed = new Hashtable();
494 allfeatures = new Vector();
495 Vector oldfeatures = new Vector();
496 if (renderOrder != null)
498 for (int i = 0; i < renderOrder.length; i++)
500 if (renderOrder[i] != null)
502 oldfeatures.addElement(renderOrder[i]);
506 for (int i = 0; i < av.alignment.getHeight(); i++)
508 SequenceFeature[] features = av.alignment.getSequenceAt(i)
509 .getDatasetSequence().getSequenceFeatures();
511 if (features == null)
517 while (index < features.length)
519 if (!av.featuresDisplayed.containsKey(features[index].getType()))
522 if (featureGroups.containsKey(features[index].getType()))
524 boolean visible = ((Boolean) featureGroups
525 .get(features[index].featureGroup)).booleanValue();
534 if (!(features[index].begin == 0 && features[index].end == 0))
536 // If beginning and end are 0, the feature is for the whole sequence
537 // and we don't want to render the feature in the normal way
540 && !oldfeatures.contains(features[index].getType()))
542 // this is a new feature type on the alignment. Mark it for
544 av.featuresDisplayed.put(features[index].getType(),
545 new Integer(getColour(features[index].getType())
547 setOrder(features[index].getType(), 0);
551 if (!allfeatures.contains(features[index].getType()))
553 allfeatures.addElement(features[index].getType());
558 updateRenderOrder(allfeatures);
559 findingFeatures = false;
562 protected Boolean firing = Boolean.FALSE;
565 * replaces the current renderOrder with the unordered features in
566 * allfeatures. The ordering of any types in both renderOrder and allfeatures
567 * is preserved, and all new feature types are rendered on top of the existing
568 * types, in the order given by getOrder or the order given in allFeatures.
569 * Note. this operates directly on the featureOrder hash for efficiency. TODO:
570 * eliminate the float storage for computing/recalling the persistent ordering
574 private void updateRenderOrder(Vector allFeatures)
576 Vector allfeatures = new Vector(allFeatures);
577 String[] oldRender = renderOrder;
578 renderOrder = new String[allfeatures.size()];
579 boolean initOrders = (featureOrder == null);
581 if (oldRender != null && oldRender.length > 0)
583 for (int j = 0; j < oldRender.length; j++)
585 if (oldRender[j] != null)
589 setOrder(oldRender[j], (1 - (1 + (float) j)
590 / (float) oldRender.length));
592 if (allfeatures.contains(oldRender[j]))
594 renderOrder[opos++] = oldRender[j]; // existing features always
595 // appear below new features
596 allfeatures.removeElement(oldRender[j]);
601 if (allfeatures.size() == 0)
603 // no new features - leave order unchanged.
606 int i = allfeatures.size() - 1;
608 boolean sort = false;
609 String[] newf = new String[allfeatures.size()];
610 float[] sortOrder = new float[allfeatures.size()];
611 Enumeration en = allfeatures.elements();
612 // sort remaining elements
613 while (en.hasMoreElements())
615 newf[i] = en.nextElement().toString();
616 if (initOrders || !featureOrder.containsKey(newf[i]))
618 int denom = initOrders ? allfeatures.size() : featureOrder.size();
619 // new unordered feature - compute persistent ordering at head of
620 // existing features.
621 setOrder(newf[i], i / (float) denom);
623 // set order from newly found feature from persisted ordering.
624 sortOrder[i] = 2 - ((Float) featureOrder.get(newf[i])).floatValue();
627 // only sort if we need to
628 sort = sort || sortOrder[i] > sortOrder[i + 1];
632 if (iSize > 1 && sort)
633 jalview.util.QuickSort.sort(sortOrder, newf);
635 System.arraycopy(newf, 0, renderOrder, opos, newf.length);
638 public Color getColour(String featureType)
640 if (!featureColours.containsKey(featureType))
642 jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
643 Color col = ucs.createColourFromName(featureType);
644 featureColours.put(featureType, col);
648 return (Color) featureColours.get(featureType);
651 static String lastFeatureAdded;
653 static String lastFeatureGroupAdded;
655 static String lastDescriptionAdded;
657 int featureIndex = 0;
659 boolean amendFeatures(final SequenceI[] sequences,
660 final SequenceFeature[] features, boolean newFeatures,
661 final AlignmentPanel ap)
666 JPanel bigPanel = new JPanel(new BorderLayout());
667 final JComboBox overlaps;
668 final JTextField name = new JTextField(25);
669 final JTextField source = new JTextField(25);
670 final JTextArea description = new JTextArea(3, 25);
671 final JSpinner start = new JSpinner();
672 final JSpinner end = new JSpinner();
673 start.setPreferredSize(new Dimension(80, 20));
674 end.setPreferredSize(new Dimension(80, 20));
676 final JPanel colour = new JPanel();
677 colour.setBorder(BorderFactory.createEtchedBorder());
678 colour.setMaximumSize(new Dimension(40, 10));
679 colour.addMouseListener(new MouseAdapter()
681 public void mousePressed(MouseEvent evt)
683 Color col = JColorChooser.showDialog(Desktop.desktop,
684 "Select Feature Colour", colour.getBackground());
686 colour.setBackground(col);
691 JPanel tmp = new JPanel();
692 JPanel panel = new JPanel(new GridLayout(3, 1));
694 // /////////////////////////////////////
695 // /MULTIPLE FEATURES AT SELECTED RESIDUE
696 if (!newFeatures && features.length > 1)
698 panel = new JPanel(new GridLayout(4, 1));
700 tmp.add(new JLabel("Select Feature: "));
701 overlaps = new JComboBox();
702 for (int i = 0; i < features.length; i++)
704 overlaps.addItem(features[i].getType() + "/"
705 + features[i].getBegin() + "-" + features[i].getEnd()
706 + " (" + features[i].getFeatureGroup() + ")");
711 overlaps.addItemListener(new ItemListener()
713 public void itemStateChanged(ItemEvent e)
715 int index = overlaps.getSelectedIndex();
718 featureIndex = index;
719 name.setText(features[index].getType());
720 description.setText(features[index].getDescription());
721 source.setText(features[index].getFeatureGroup());
722 start.setValue(new Integer(features[index].getBegin()));
723 end.setValue(new Integer(features[index].getEnd()));
725 SearchResults highlight = new SearchResults();
726 highlight.addResult(sequences[0], features[index].getBegin(),
727 features[index].getEnd());
729 ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
732 Color col = getColour(name.getText());
735 col = new jalview.schemes.UserColourScheme()
736 .createColourFromName(name.getText());
739 colour.setBackground(col);
746 // ////////////////////////////////////
750 tmp.add(new JLabel("Name: ", JLabel.RIGHT));
755 tmp.add(new JLabel("Group: ", JLabel.RIGHT));
760 tmp.add(new JLabel("Colour: ", JLabel.RIGHT));
762 colour.setPreferredSize(new Dimension(150, 15));
764 bigPanel.add(panel, BorderLayout.NORTH);
766 panel = new JPanel();
767 panel.add(new JLabel("Description: ", JLabel.RIGHT));
768 description.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
769 description.setLineWrap(true);
770 panel.add(new JScrollPane(description));
774 bigPanel.add(panel, BorderLayout.SOUTH);
776 panel = new JPanel();
777 panel.add(new JLabel(" Start:", JLabel.RIGHT));
779 panel.add(new JLabel(" End:", JLabel.RIGHT));
781 bigPanel.add(panel, BorderLayout.CENTER);
785 bigPanel.add(panel, BorderLayout.CENTER);
788 if (lastFeatureAdded == null)
790 if (features[0].type != null)
792 lastFeatureAdded = features[0].type;
796 lastFeatureAdded = "feature_1";
800 if (lastFeatureGroupAdded == null)
802 if (features[0].featureGroup != null)
804 lastFeatureGroupAdded = features[0].featureGroup;
808 lastFeatureGroupAdded = "Jalview";
814 name.setText(lastFeatureAdded);
815 source.setText(lastFeatureGroupAdded);
819 name.setText(features[0].getType());
820 source.setText(features[0].getFeatureGroup());
823 start.setValue(new Integer(features[0].getBegin()));
824 end.setValue(new Integer(features[0].getEnd()));
825 description.setText(features[0].getDescription());
826 colour.setBackground(getColour(name.getText()));
831 options = new Object[]
832 { "Amend", "Delete", "Cancel" };
836 options = new Object[]
840 String title = newFeatures ? "Create New Sequence Feature(s)"
841 : "Amend/Delete Features for " + sequences[0].getName();
843 int reply = JOptionPane.showInternalOptionDialog(Desktop.desktop,
844 bigPanel, title, JOptionPane.YES_NO_CANCEL_OPTION,
845 JOptionPane.QUESTION_MESSAGE, null, options, "OK");
847 jalview.io.FeaturesFile ffile = new jalview.io.FeaturesFile();
849 if (reply == JOptionPane.OK_OPTION && name.getText().length() > 0)
851 // This ensures that the last sequence
852 // is refreshed and new features are rendered
854 lastFeatureAdded = name.getText().trim();
855 lastFeatureGroupAdded = source.getText().trim();
856 lastDescriptionAdded = description.getText().replaceAll("\n", " ");
858 if (lastFeatureGroupAdded.length() < 1)
859 lastFeatureGroupAdded = null;
864 SequenceFeature sf = features[featureIndex];
866 if (reply == JOptionPane.NO_OPTION)
868 sequences[0].getDatasetSequence().deleteFeature(sf);
870 else if (reply == JOptionPane.YES_OPTION)
872 sf.type = lastFeatureAdded;
873 sf.featureGroup = lastFeatureGroupAdded;
874 sf.description = lastDescriptionAdded;
876 setColour(sf.type, colour.getBackground());
877 av.featuresDisplayed.put(sf.type, new Integer(colour
878 .getBackground().getRGB()));
882 sf.begin = ((Integer) start.getValue()).intValue();
883 sf.end = ((Integer) end.getValue()).intValue();
884 } catch (NumberFormatException ex)
888 ffile.parseDescriptionHTML(sf, false);
892 // NEW FEATURES ADDED
894 if (reply == JOptionPane.OK_OPTION && lastFeatureAdded.length() > 0)
896 for (int i = 0; i < sequences.length; i++)
898 features[i].type = lastFeatureAdded;
899 if (lastFeatureGroupAdded != null)
900 features[i].featureGroup = lastFeatureGroupAdded;
901 features[i].description = lastDescriptionAdded;
902 sequences[i].addSequenceFeature(features[i]);
903 ffile.parseDescriptionHTML(features[i], false);
906 if (av.featuresDisplayed == null)
908 av.featuresDisplayed = new Hashtable();
911 if (lastFeatureGroupAdded != null)
913 if (featureGroups == null)
914 featureGroups = new Hashtable();
915 featureGroups.put(lastFeatureGroupAdded, new Boolean(true));
918 Color col = colour.getBackground();
919 setColour(lastFeatureAdded, colour.getBackground());
920 av.featuresDisplayed.put(lastFeatureAdded,
921 new Integer(col.getRGB()));
923 findAllFeatures(false);
925 ap.paintAlignment(true);
935 ap.paintAlignment(true);
940 public void setColour(String featureType, Color col)
942 featureColours.put(featureType, col);
945 public void setTransparency(float value)
947 transparency = value;
950 public float getTransparency()
956 * Replace current ordering with new ordering
959 * String(Type), Colour(Type), Boolean(Displayed) }
961 public void setFeaturePriority(Object[][] data)
963 setFeaturePriority(data, true);
969 * String(Type), Colour(Type), Boolean(Displayed) }
971 * when true current featureDisplay list will be cleared
973 public void setFeaturePriority(Object[][] data, boolean visibleNew)
977 if (av.featuresDisplayed != null)
979 av.featuresDisplayed.clear();
983 av.featuresDisplayed = new Hashtable();
991 // The feature table will display high priority
992 // features at the top, but theses are the ones
993 // we need to render last, so invert the data
994 renderOrder = new String[data.length];
998 for (int i = 0; i < data.length; i++)
1000 String type = data[i][0].toString();
1001 setColour(type, (Color) data[i][1]);
1002 if (((Boolean) data[i][2]).booleanValue())
1004 av.featuresDisplayed.put(type, new Integer(getColour(type)
1008 renderOrder[data.length - i - 1] = type;
1014 Hashtable featureOrder = null;
1017 * analogous to colour - store a normalized ordering for all feature types in
1018 * this rendering context.
1021 * Feature type string
1023 * normalized priority - 0 means always appears on top, 1 means
1026 public float setOrder(String type, float position)
1028 if (featureOrder == null)
1030 featureOrder = new Hashtable();
1032 featureOrder.put(type, new Float(position));
1037 * get the global priority (0 (top) to 1 (bottom))
1040 * @return [0,1] or -1 for a type without a priority
1042 public float getOrder(String type)
1044 if (featureOrder != null)
1046 if (featureOrder.containsKey(type))
1048 return ((Float) featureOrder.get(type)).floatValue();
1056 * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
1058 public void addPropertyChangeListener(PropertyChangeListener listener)
1060 changeSupport.addPropertyChangeListener(listener);
1065 * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
1067 public void removePropertyChangeListener(PropertyChangeListener listener)
1069 changeSupport.removePropertyChangeListener(listener);