eaedf124e71beb558820e775fc41e9586672d6fc
[jalview.git] / src / jalview / gui / FeatureRenderer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
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.
9  *
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.
14  *
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
18  */
19 package jalview.gui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25 import java.awt.image.*;
26 import javax.swing.*;
27
28 import jalview.datamodel.*;
29
30 /**
31  * DOCUMENT ME!
32  *
33  * @author $author$
34  * @version $Revision$
35  */
36 public class FeatureRenderer
37 {
38   AlignViewport av;
39   Color resBoxColour;
40   float transparency = 1.0f;
41   FontMetrics fm;
42   int charOffset;
43
44   Hashtable featureColours = new Hashtable();
45
46   // A higher level for grouping features of a
47   // particular type
48   Hashtable featureGroups = null;
49
50   // This is actually an Integer held in the hashtable,
51   // Retrieved using the key feature type
52   Object currentColour;
53
54   String[] renderOrder;
55
56   /**
57    * Creates a new FeatureRenderer object.
58    *
59    * @param av DOCUMENT ME!
60    */
61   public FeatureRenderer(AlignViewport av)
62   {
63     this.av = av;
64   }
65
66   public void transferSettings(FeatureRenderer fr)
67   {
68     renderOrder = fr.renderOrder;
69     featureGroups = fr.featureGroups;
70     featureColours = fr.featureColours;
71     transparency = fr.transparency;
72   }
73
74   BufferedImage offscreenImage;
75   boolean offscreenRender = false;
76   public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
77   {
78     return new Color(findFeatureColour(initialCol.getRGB(),
79                                        seq, res));
80   }
81
82   /**
83    * This is used by the Molecule Viewer and Overview to
84    * get the accurate colourof the rendered sequence
85    */
86   public int findFeatureColour(int initialCol, SequenceI seq, int column)
87   {
88     if (!av.showSequenceFeatures)
89     {
90       return initialCol;
91     }
92
93     if (seq != lastSeq)
94     {
95       lastSeq = seq;
96       sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
97       if (sequenceFeatures == null)
98       {
99         return initialCol;
100       }
101
102       sfSize = sequenceFeatures.length;
103     }
104
105     if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
106     {
107       return Color.white.getRGB();
108     }
109
110     //Only bother making an offscreen image if transparency is applied
111     if (transparency != 1.0f && offscreenImage == null)
112     {
113       offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
114     }
115
116     currentColour = null;
117
118     offscreenRender = true;
119
120     if (offscreenImage != null)
121     {
122       offscreenImage.setRGB(0, 0, initialCol);
123       drawSequence(offscreenImage.getGraphics(),
124                    lastSeq,
125                    column, column, 0);
126
127       return offscreenImage.getRGB(0, 0);
128     }
129     else
130     {
131       drawSequence(null,
132                    lastSeq,
133                    lastSeq.findPosition(column),
134                    -1, -1);
135
136       if (currentColour == null)
137       {
138         return initialCol;
139       }
140       else
141       {
142         return ( (Integer) currentColour).intValue();
143       }
144     }
145
146   }
147
148   /**
149    * DOCUMENT ME!
150    *
151    * @param g DOCUMENT ME!
152    * @param seq DOCUMENT ME!
153    * @param sg DOCUMENT ME!
154    * @param start DOCUMENT ME!
155    * @param end DOCUMENT ME!
156    * @param x1 DOCUMENT ME!
157    * @param y1 DOCUMENT ME!
158    * @param width DOCUMENT ME!
159    * @param height DOCUMENT ME!
160    */
161   // String type;
162   // SequenceFeature sf;
163   SequenceI lastSeq;
164   SequenceFeature[] sequenceFeatures;
165   int sfSize, sfindex, spos, epos;
166
167   public void drawSequence(Graphics g, SequenceI seq,
168                            int start, int end, int y1)
169   {
170     if (seq.getDatasetSequence().getSequenceFeatures() == null
171         || seq.getDatasetSequence().getSequenceFeatures().length == 0)
172     {
173       return;
174     }
175
176     if (g != null)
177     {
178       fm = g.getFontMetrics();
179     }
180
181     if (av.featuresDisplayed == null
182         || renderOrder == null
183         || newFeatureAdded)
184     {
185       findAllFeatures();
186       if (av.featuresDisplayed.size() < 1)
187       {
188         return;
189       }
190
191       sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
192       sfSize = sequenceFeatures.length;
193     }
194
195     if (lastSeq == null || seq != lastSeq
196         || lastSeq.getSequenceFeatures()!=sequenceFeatures)
197     {
198       lastSeq = seq;
199       sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
200       sfSize = sequenceFeatures.length;
201     }
202
203     if (transparency != 1 && g != null)
204     {
205       Graphics2D g2 = (Graphics2D) g;
206       g2.setComposite(
207           AlphaComposite.getInstance(
208               AlphaComposite.SRC_OVER, transparency));
209     }
210
211     if (!offscreenRender)
212     {
213       spos = lastSeq.findPosition(start);
214       epos = lastSeq.findPosition(end);
215     }
216
217     String type;
218     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
219     {
220       type = renderOrder[renderIndex];
221
222       if (type == null || !av.featuresDisplayed.containsKey(type))
223       {
224         continue;
225       }
226
227       // loop through all features in sequence to find
228       // current feature to render
229       for (sfindex = 0; sfindex < sfSize; sfindex++)
230       {
231         if (sequenceFeatures.length <= sfindex)
232         {
233           continue;
234         }
235         if (!sequenceFeatures[sfindex].type.equals(type))
236         {
237           continue;
238         }
239
240         if (featureGroups != null
241             && sequenceFeatures[sfindex].featureGroup != null
242             &&
243             featureGroups.containsKey(sequenceFeatures[sfindex].featureGroup)
244             &&
245             ! ( (Boolean) featureGroups.get(sequenceFeatures[sfindex].
246                                             featureGroup)).
247             booleanValue())
248         {
249           continue;
250         }
251
252         if (!offscreenRender && (sequenceFeatures[sfindex].getBegin() > epos
253                                  || sequenceFeatures[sfindex].getEnd() < spos))
254         {
255           continue;
256         }
257
258         if (offscreenRender && offscreenImage == null)
259         {
260           if (sequenceFeatures[sfindex].begin <= start &&
261               sequenceFeatures[sfindex].end >= start)
262           {
263             currentColour = av.featuresDisplayed.get(sequenceFeatures[sfindex].
264                 type);
265           }
266         }
267         else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
268         {
269
270           renderFeature(g, seq,
271                         seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
272                         seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
273                         new Color( ( (Integer) av.featuresDisplayed.get(
274                             sequenceFeatures[sfindex].type)).intValue()),
275                         start, end, y1);
276           renderFeature(g, seq,
277                         seq.findIndex(sequenceFeatures[sfindex].end) - 1,
278                         seq.findIndex(sequenceFeatures[sfindex].end) - 1,
279                         new Color( ( (Integer) av.featuresDisplayed.get(
280                             sequenceFeatures[sfindex].type)).intValue()),
281                         start, end, y1);
282
283         }
284         else
285         {
286           renderFeature(g, seq,
287                         seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
288                         seq.findIndex(sequenceFeatures[sfindex].end) - 1,
289                         getColour(sequenceFeatures[sfindex].type),
290                         start, end, y1);
291         }
292
293       }
294
295     }
296
297     if (transparency != 1.0f && g != null)
298     {
299       Graphics2D g2 = (Graphics2D) g;
300       g2.setComposite(
301           AlphaComposite.getInstance(
302               AlphaComposite.SRC_OVER, 1.0f));
303     }
304   }
305
306   char s;
307   int i;
308   void renderFeature(Graphics g, SequenceI seq,
309                      int fstart, int fend, Color featureColour, int start,
310                      int end, int y1)
311   {
312
313     if ( ( (fstart <= end) && (fend >= start)))
314     {
315       if (fstart < start)
316       { // fix for if the feature we have starts before the sequence start,
317         fstart = start; // but the feature end is still valid!!
318       }
319
320       if (fend >= end)
321       {
322         fend = end;
323       }
324       int pady = (y1 + av.charHeight) - av.charHeight / 5;
325       for (i = fstart; i <= fend; i++)
326       {
327         s = seq.getCharAt(i);
328
329         if (jalview.util.Comparison.isGap(s))
330         {
331           continue;
332         }
333
334         g.setColor(featureColour);
335
336         g.fillRect( (i - start) * av.charWidth, y1, av.charWidth, av.charHeight);
337
338         if (offscreenRender || !av.validCharWidth)
339         {
340           continue;
341         }
342
343         g.setColor(Color.white);
344         charOffset = (av.charWidth - fm.charWidth(s)) / 2;
345         g.drawString(String.valueOf(s),
346                      charOffset + (av.charWidth * (i - start)),
347                      pady);
348
349       }
350     }
351   }
352
353   boolean newFeatureAdded = false;
354
355   public void featuresAdded()
356   {
357     lastSeq=null;
358     findAllFeatures();
359   }
360
361   boolean findingFeatures = false;
362   synchronized void findAllFeatures()
363   {
364     newFeatureAdded = false;
365
366     if (findingFeatures)
367     {
368       newFeatureAdded = true;
369       return;
370     }
371
372     findingFeatures = true;
373     jalview.schemes.UserColourScheme ucs = new
374         jalview.schemes.UserColourScheme();
375
376     if (av.featuresDisplayed == null)
377     {
378       av.featuresDisplayed = new Hashtable();
379     }
380
381     av.featuresDisplayed.clear();
382
383     Vector allfeatures = new Vector();
384     for (int i = 0; i < av.alignment.getHeight(); i++)
385     {
386       SequenceFeature[] features
387           = av.alignment.getSequenceAt(i).getDatasetSequence().
388           getSequenceFeatures();
389
390       if (features == null)
391       {
392         continue;
393       }
394
395       int index = 0;
396       while (index < features.length)
397       {
398         if (!av.featuresDisplayed.containsKey(features[index].getType()))
399         {
400           if (! (features[index].begin == 0 && features[index].end == 0))
401           {
402             // If beginning and end are 0, the feature is for the whole sequence
403             // and we don't want to render the feature in the normal way
404
405             if (getColour(features[index].getType()) == null)
406             {
407               featureColours.put(features[index].getType(),
408                                  ucs.createColourFromName(features[index].
409                   getType()));
410             }
411
412             av.featuresDisplayed.put(features[index].getType(),
413                                      new Integer(getColour(features[index].
414                 getType()).getRGB()));
415             allfeatures.addElement(features[index].getType());
416           }
417         }
418         index++;
419       }
420     }
421
422     renderOrder = new String[allfeatures.size()];
423     Enumeration en = allfeatures.elements();
424     int i = allfeatures.size() - 1;
425     while (en.hasMoreElements())
426     {
427       renderOrder[i] = en.nextElement().toString();
428       i--;
429     }
430
431     findingFeatures = false;
432   }
433
434   public Color getColour(String featureType)
435   {
436     Color colour = (Color) featureColours.get(featureType);
437     return colour;
438   }
439
440   static String lastFeatureAdded;
441   static String lastFeatureGroupAdded;
442   static String lastDescriptionAdded;
443
444   public boolean createNewFeatures(SequenceI[] sequences,
445                                    SequenceFeature[] features)
446   {
447     return amendFeatures(sequences, features, true, null);
448   }
449
450   int featureIndex = 0;
451   boolean amendFeatures(final SequenceI[] sequences,
452                         final SequenceFeature[] features,
453                         boolean newFeatures,
454                         final AlignmentPanel ap)
455   {
456     JPanel bigPanel = new JPanel(new BorderLayout());
457     final JComboBox name = new JComboBox();
458     final JComboBox source = new JComboBox();
459     final JTextArea description = new JTextArea(3, 25);
460     final JSpinner start = new JSpinner();
461     final JSpinner end = new JSpinner();
462     start.setPreferredSize(new Dimension(80, 20));
463     end.setPreferredSize(new Dimension(80, 20));
464     final JPanel colour = new JPanel();
465     colour.setBorder(BorderFactory.createEtchedBorder());
466     colour.setMaximumSize(new Dimension(40, 10));
467     colour.addMouseListener(new MouseAdapter()
468     {
469       public void mousePressed(MouseEvent evt)
470       {
471         colour.setBackground(
472             JColorChooser.showDialog(Desktop.desktop,
473                                      "Select Feature Colour",
474                                      colour.getBackground()));
475       }
476     });
477
478     JPanel panel = new JPanel(new GridLayout(3, 2));
479     panel.add(new JLabel("Sequence Feature Name: ", JLabel.RIGHT));
480     panel.add(name);
481     panel.add(new JLabel("Feature Group: ", JLabel.RIGHT));
482     panel.add(source);
483     panel.add(new JLabel("Feature Colour: ", JLabel.RIGHT));
484     JPanel tmp = new JPanel();
485     tmp.add(colour);
486     colour.setPreferredSize(new Dimension(150, 15));
487     panel.add(tmp);
488     name.setEditable(true);
489     source.setEditable(true);
490
491     bigPanel.add(panel, BorderLayout.NORTH);
492     panel = new JPanel();
493     panel.add(new JLabel("Description: ", JLabel.RIGHT));
494     description.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
495     description.setLineWrap(true);
496     panel.add(new JScrollPane(description));
497
498     if (!newFeatures)
499     {
500       bigPanel.add(panel, BorderLayout.SOUTH);
501
502       panel = new JPanel();
503       panel.add(new JLabel(" Start:", JLabel.RIGHT));
504       panel.add(start);
505       panel.add(new JLabel("  End:", JLabel.RIGHT));
506       panel.add(end);
507       bigPanel.add(panel, BorderLayout.CENTER);
508     }
509     else
510     {
511       bigPanel.add(panel, BorderLayout.CENTER);
512     }
513
514     if (lastFeatureAdded == null)
515     {
516       if (features[0].type != null)
517       {
518         lastFeatureAdded = features[0].type;
519       }
520       else
521       {
522         lastFeatureAdded = "feature_1";
523       }
524     }
525
526     if (lastFeatureGroupAdded == null)
527     {
528       if (features[0].featureGroup != null)
529       {
530         lastFeatureGroupAdded = features[0].featureGroup;
531       }
532       else
533       {
534         lastFeatureAdded = "Jalview";
535       }
536     }
537
538     Enumeration en;
539     if (featureGroups != null)
540     {
541       en = featureGroups.keys();
542       while (en.hasMoreElements())
543       {
544         source.addItem(en.nextElement().toString());
545       }
546     }
547
548     if (newFeatures)
549     {
550       if (av.featuresDisplayed != null)
551       {
552         en = av.featuresDisplayed.keys();
553         while (en.hasMoreElements())
554         {
555           name.addItem(en.nextElement().toString());
556         }
557       }
558
559       name.setSelectedItem(lastFeatureAdded);
560       source.setSelectedItem(lastFeatureGroupAdded);
561       description.setText(
562           lastDescriptionAdded == null ?
563           features[0].description : lastDescriptionAdded);
564
565       if (getColour(lastFeatureAdded) != null)
566       {
567         colour.setBackground(getColour(lastFeatureAdded));
568       }
569       else
570       {
571         colour.setBackground(new Color(60, 160, 115));
572       }
573
574     }
575     else if (!newFeatures)
576     {
577       featureIndex = 0;
578       for (int f = 0; f < features.length; f++)
579       {
580         name.addItem(features[f].getType().toString());
581       }
582
583       description.setText(features[0].getDescription());
584       source.setSelectedItem(features[0].getFeatureGroup());
585       start.setValue(new Integer(features[0].getBegin()));
586       end.setValue(new Integer(features[0].getEnd()));
587       colour.setBackground(
588           getColour(name.getSelectedItem().toString()));
589       name.addItemListener(new ItemListener()
590       {
591         public void itemStateChanged(ItemEvent e)
592         {
593           int index = name.getSelectedIndex();
594           if (index != -1)
595           {
596             featureIndex = index;
597             description.setText(features[index].getDescription());
598             source.setSelectedItem(features[index].getFeatureGroup());
599             start.setValue(new Integer(features[index].getBegin()));
600             end.setValue(new Integer(features[index].getEnd()));
601             colour.setBackground(
602                 getColour(name.getSelectedItem().toString()));
603
604             SearchResults highlight = new SearchResults();
605             highlight.addResult(sequences[0],
606                                 features[index].getBegin(),
607                                 features[index].getEnd());
608
609             ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
610
611           }
612           Color col = getColour(name.getSelectedItem().toString());
613           if (col == null)
614           {
615             col = new
616                 jalview.schemes.UserColourScheme()
617                 .createColourFromName(name.getSelectedItem().toString());
618           }
619
620           colour.setBackground(col);
621         }
622       });
623
624     }
625
626     Object[] options;
627     if (!newFeatures)
628     {
629       options = new Object[]
630           {
631           "Amend", "Delete", "Cancel"};
632     }
633     else
634     {
635       options = new Object[]
636           {
637           "OK", "Cancel"};
638     }
639
640     String title = newFeatures ? "Create New Sequence Feature(s)" :
641         "Amend/Delete Features for "
642         + sequences[0].getName();
643
644     int reply = JOptionPane.showInternalOptionDialog(Desktop.desktop,
645         bigPanel,
646         title,
647         JOptionPane.YES_NO_CANCEL_OPTION,
648         JOptionPane.QUESTION_MESSAGE,
649         null,
650         options, "OK");
651
652     jalview.io.FeaturesFile ffile = new jalview.io.FeaturesFile();
653
654     if (reply == JOptionPane.OK_OPTION
655         && name.getSelectedItem() != null
656         && source.getSelectedItem() != null)
657     {
658       //This ensures that the last sequence
659       //is refreshed and new features are rendered
660       lastSeq = null;
661       lastFeatureAdded = name.getSelectedItem().toString();
662       lastFeatureGroupAdded = source.getSelectedItem().toString();
663       lastDescriptionAdded = description.getText().replaceAll("\n", " ");
664     }
665
666     if (!newFeatures)
667     {
668       SequenceFeature sf = features[featureIndex];
669
670       if (reply == JOptionPane.NO_OPTION)
671       {
672         sequences[0].getDatasetSequence().deleteFeature(sf);
673       }
674       else if (reply == JOptionPane.YES_OPTION)
675       {
676         sf.type = lastFeatureAdded;
677         sf.featureGroup = lastFeatureGroupAdded;
678         sf.description = lastDescriptionAdded;
679         setColour(sf.type, colour.getBackground());
680         try
681         {
682           sf.begin = ( (Integer) start.getValue()).intValue();
683           sf.end = ( (Integer) end.getValue()).intValue();
684         }
685         catch (NumberFormatException ex)
686         {}
687
688         ffile.parseDescriptionHTML(sf, false);
689       }
690     }
691     else
692     {
693       if (reply == JOptionPane.OK_OPTION
694           && name.getSelectedItem() != null
695           && source.getSelectedItem() != null)
696       {
697         for (int i = 0; i < sequences.length; i++)
698         {
699           features[i].type = lastFeatureAdded;
700           features[i].featureGroup = lastFeatureGroupAdded;
701           features[i].description = lastDescriptionAdded;
702           sequences[i].addSequenceFeature(features[i]);
703           ffile.parseDescriptionHTML(features[i], false);
704         }
705
706         if (av.featuresDisplayed == null)
707         {
708           av.featuresDisplayed = new Hashtable();
709         }
710
711         if (featureGroups == null)
712         {
713           featureGroups = new Hashtable();
714         }
715
716         featureGroups.put(lastFeatureGroupAdded, new Boolean(true));
717
718         Color col = colour.getBackground();
719         setColour(lastFeatureAdded, colour.getBackground());
720
721         av.featuresDisplayed.put(lastFeatureGroupAdded,
722                                  new Integer(col.getRGB()));
723
724         findAllFeatures();
725
726         return true;
727       }
728       else
729       {
730         return false;
731       }
732     }
733
734     if (name.getSelectedIndex() == -1)
735     {
736       findAllFeatures();
737     }
738
739     return true;
740   }
741
742   public void setColour(String featureType, Color col)
743   {
744     featureColours.put(featureType, col);
745   }
746
747   public void setTransparency(float value)
748   {
749     transparency = value;
750   }
751
752   public float getTransparency()
753   {
754     return transparency;
755   }
756
757   public void setFeaturePriority(Object[][] data)
758   {
759     // The feature table will display high priority
760     // features at the top, but theses are the ones
761     // we need to render last, so invert the data
762     if (av.featuresDisplayed != null)
763     {
764       av.featuresDisplayed.clear();
765     }
766     else
767     {
768       av.featuresDisplayed = new Hashtable();
769     }
770     if (data!=null)
771       return;
772     renderOrder = new String[data.length];
773
774     if (data.length > 0)
775     {
776       for (int i = 0; i < data.length; i++)
777       {
778         String type = data[i][0].toString();
779         setColour(type, (Color) data[i][1]);
780         if ( ( (Boolean) data[i][2]).booleanValue())
781         {
782           av.featuresDisplayed.put(type, new Integer(getColour(type).getRGB()));
783         }
784
785         renderOrder[data.length - i - 1] = type;
786       }
787     }
788
789   }
790
791 }