JAL-2446 merged to spike branch
[jalview.git] / src / jalview / appletgui / FeatureRenderer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.appletgui;
22
23 import jalview.api.FeatureColourI;
24 import jalview.datamodel.SearchResults;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceFeature;
27 import jalview.datamodel.SequenceI;
28 import jalview.io.FeaturesFile;
29 import jalview.schemes.FeatureColour;
30 import jalview.util.ColorUtils;
31 import jalview.util.MessageManager;
32 import jalview.viewmodel.AlignmentViewport;
33
34 import java.awt.BorderLayout;
35 import java.awt.Button;
36 import java.awt.Choice;
37 import java.awt.Color;
38 import java.awt.Dimension;
39 import java.awt.FlowLayout;
40 import java.awt.Font;
41 import java.awt.Frame;
42 import java.awt.Graphics;
43 import java.awt.GridLayout;
44 import java.awt.Label;
45 import java.awt.Panel;
46 import java.awt.ScrollPane;
47 import java.awt.TextArea;
48 import java.awt.TextField;
49 import java.awt.event.ActionEvent;
50 import java.awt.event.ActionListener;
51 import java.awt.event.MouseAdapter;
52 import java.awt.event.MouseEvent;
53 import java.awt.event.TextEvent;
54 import java.awt.event.TextListener;
55 import java.util.Hashtable;
56 import java.util.List;
57
58 /**
59  * DOCUMENT ME!
60  * 
61  * @author $author$
62  * @version $Revision$
63  */
64 public class FeatureRenderer extends
65         jalview.renderer.seqfeatures.FeatureRenderer
66 {
67   /*
68    * creating a new feature defaults to the type and group as
69    * the last one created
70    */
71   static String lastFeatureAdded = "feature_1";
72
73   static String lastFeatureGroupAdded = "Jalview";
74
75   // Holds web links for feature groups and feature types
76   // in the form label|link
77   Hashtable featureLinks = null;
78
79   /**
80    * Creates a new FeatureRenderer object.
81    * 
82    * @param av
83    */
84   public FeatureRenderer(AlignmentViewport av)
85   {
86     super(av);
87
88   }
89
90   int featureIndex = 0;
91
92   boolean deleteFeature = false;
93
94   FeatureColourPanel colourPanel;
95
96   class FeatureColourPanel extends Panel
97   {
98     String label = "";
99
100     private Color maxCol;
101
102     private boolean isColourByLabel, isGcol;
103
104     /**
105      * render a feature style in the amend feature dialog box
106      */
107     public void updateColor(FeatureColourI newcol)
108     {
109       Color bg = null;
110       String vlabel = "";
111       if (newcol.isSimpleColour())
112       {
113         bg = newcol.getColour();
114         setBackground(bg);
115       }
116       else
117       {
118         if (newcol.isAboveThreshold())
119         {
120           vlabel += " (>)";
121         }
122         else if (newcol.isBelowThreshold())
123         {
124           vlabel += " (<)";
125         }
126
127         if (isColourByLabel = newcol.isColourByLabel())
128         {
129           setBackground(bg = Color.white);
130           vlabel += " (by Label)";
131         }
132         else
133         {
134           setBackground(bg = newcol.getMinColour());
135           maxCol = newcol.getMaxColour();
136         }
137       }
138       label = vlabel;
139       setBackground(bg);
140       repaint();
141     }
142
143     FeatureColourPanel()
144     {
145       super(null);
146     }
147
148     @Override
149     public void paint(Graphics g)
150     {
151       Dimension d = getSize();
152       if (isGcol)
153       {
154         if (isColourByLabel)
155         {
156           g.setColor(Color.white);
157           g.fillRect(d.width / 2, 0, d.width / 2, d.height);
158           g.setColor(Color.black);
159           Font f = new Font("Verdana", Font.PLAIN, 10);
160           g.setFont(f);
161           g.drawString(MessageManager.getString("label.label"), 0, 0);
162         }
163         else
164         {
165           g.setColor(maxCol);
166           g.fillRect(d.width / 2, 0, d.width / 2, d.height);
167
168         }
169       }
170     }
171
172   }
173
174   /**
175    * Shows a dialog allowing the user to create, or amend or delete, sequence
176    * features. If null in the supplied feature(s), feature type and group
177    * default to those for the last feature created (with initial defaults of
178    * "feature_1" and "Jalview").
179    * 
180    * @param sequences
181    * @param features
182    * @param create
183    * @param ap
184    * @return
185    */
186   boolean amendFeatures(final List<SequenceI> sequences,
187           final List<SequenceFeature> features, boolean create,
188           final AlignmentPanel ap)
189   {
190     final Panel bigPanel = new Panel(new BorderLayout());
191     final TextField name = new TextField(16);
192     final TextField group = new TextField(16);
193     final TextArea description = new TextArea(3, 35);
194     final TextField start = new TextField(8);
195     final TextField end = new TextField(8);
196     final Choice overlaps;
197     Button deleteButton = new Button("Delete");
198     deleteFeature = false;
199
200     name.addTextListener(new TextListener()
201     {
202       @Override
203       public void textValueChanged(TextEvent e)
204       {
205         warnIfTypeHidden(ap.alignFrame, name.getText());
206       }
207     });
208     group.addTextListener(new TextListener()
209     {
210       @Override
211       public void textValueChanged(TextEvent e)
212       {
213         warnIfGroupHidden(ap.alignFrame, group.getText());
214       }
215     });
216     colourPanel = new FeatureColourPanel();
217     colourPanel.setSize(110, 15);
218     final FeatureRenderer fr = this;
219
220     Panel panel = new Panel(new GridLayout(3, 1));
221
222     featureIndex = 0; // feature to be amended.
223     Panel tmp;
224
225     // /////////////////////////////////////
226     // /MULTIPLE FEATURES AT SELECTED RESIDUE
227     if (!create && features.size() > 1)
228     {
229       panel = new Panel(new GridLayout(4, 1));
230       tmp = new Panel();
231       tmp.add(new Label("Select Feature: "));
232       overlaps = new Choice();
233       for (SequenceFeature sf : features)
234       {
235         String item = sf.getType() + "/" + sf.getBegin() + "-"
236                 + sf.getEnd();
237         if (sf.getFeatureGroup() != null)
238         {
239           item += " (" + sf.getFeatureGroup() + ")";
240         }
241         overlaps.addItem(item);
242       }
243
244       tmp.add(overlaps);
245
246       overlaps.addItemListener(new java.awt.event.ItemListener()
247       {
248         @Override
249         public void itemStateChanged(java.awt.event.ItemEvent e)
250         {
251           int index = overlaps.getSelectedIndex();
252           if (index != -1)
253           {
254             featureIndex = index;
255             SequenceFeature sf = features.get(index);
256             name.setText(sf.getType());
257             description.setText(sf.getDescription());
258             group.setText(sf.getFeatureGroup());
259             start.setText(sf.getBegin() + "");
260             end.setText(sf.getEnd() + "");
261
262             SearchResultsI highlight = new SearchResults();
263             highlight.addResult(sequences.get(0), sf.getBegin(),
264                     sf.getEnd());
265
266             ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
267
268           }
269           FeatureColourI col = getFeatureStyle(name.getText());
270           if (col == null)
271           {
272             Color generatedColour = ColorUtils.createColourFromName(name
273                     .getText());
274             col = new FeatureColour(generatedColour);
275           }
276
277           colourPanel.updateColor(col);
278         }
279       });
280
281       panel.add(tmp);
282     }
283     // ////////
284     // ////////////////////////////////////
285
286     tmp = new Panel();
287     panel.add(tmp);
288     tmp.add(new Label(MessageManager.getString("label.name:"), Label.RIGHT));
289     tmp.add(name);
290
291     tmp = new Panel();
292     panel.add(tmp);
293     tmp.add(new Label(MessageManager.getString("label.group:"), Label.RIGHT));
294     tmp.add(group);
295
296     tmp = new Panel();
297     panel.add(tmp);
298     tmp.add(new Label(MessageManager.getString("label.colour"), Label.RIGHT));
299     tmp.add(colourPanel);
300
301     bigPanel.add(panel, BorderLayout.NORTH);
302
303     panel = new Panel();
304     panel.add(new Label(MessageManager.getString("label.description:"),
305             Label.RIGHT));
306     panel.add(new ScrollPane().add(description));
307
308     if (!create)
309     {
310       bigPanel.add(panel, BorderLayout.SOUTH);
311
312       panel = new Panel();
313       panel.add(new Label(MessageManager.getString("label.start"),
314               Label.RIGHT));
315       panel.add(start);
316       panel.add(new Label(MessageManager.getString("label.end"),
317               Label.RIGHT));
318       panel.add(end);
319       bigPanel.add(panel, BorderLayout.CENTER);
320     }
321     else
322     {
323       bigPanel.add(panel, BorderLayout.CENTER);
324     }
325
326     /*
327      * use defaults for type and group (and update them on Confirm) only
328      * if feature type has not been supplied by the caller
329      * (e.g. for Amend, or create features from Find) 
330      */
331     SequenceFeature firstFeature = features.get(0);
332     boolean useLastDefaults = firstFeature.getType() == null;
333     String featureType = useLastDefaults ? lastFeatureAdded : firstFeature
334             .getType();
335     String featureGroup = useLastDefaults ? lastFeatureGroupAdded
336             : firstFeature.getFeatureGroup();
337
338     String title = create ? MessageManager
339             .getString("label.create_new_sequence_features")
340             : MessageManager.formatMessage("label.amend_delete_features",
341                     new String[] { sequences.get(0).getName() });
342
343     final JVDialog dialog = new JVDialog(ap.alignFrame, title, true, 385,
344             240);
345
346     dialog.setMainPanel(bigPanel);
347
348     name.setText(featureType);
349     group.setText(featureGroup);
350
351     if (!create)
352     {
353       dialog.ok.setLabel(MessageManager.getString("label.amend"));
354       dialog.buttonPanel.add(deleteButton, 1);
355       deleteButton.addActionListener(new ActionListener()
356       {
357         @Override
358         public void actionPerformed(ActionEvent evt)
359         {
360           deleteFeature = true;
361           dialog.setVisible(false);
362         }
363       });
364     }
365
366     start.setText(firstFeature.getBegin() + "");
367     end.setText(firstFeature.getEnd() + "");
368     description.setText(firstFeature.getDescription());
369     // lookup (or generate) the feature colour
370     FeatureColourI fcol = getFeatureStyle(name.getText());
371     // simply display the feature color in a box
372     colourPanel.updateColor(fcol);
373     dialog.setResizable(true);
374     // TODO: render the graduated color in the box.
375     colourPanel.addMouseListener(new MouseAdapter()
376     {
377       @Override
378       public void mousePressed(MouseEvent evt)
379       {
380         if (!colourPanel.isGcol)
381         {
382           new UserDefinedColours(fr, ap.alignFrame);
383         }
384         else
385         {
386           new FeatureColourChooser(ap.alignFrame, name.getText());
387           dialog.transferFocus();
388         }
389       }
390     });
391     dialog.setVisible(true);
392
393     FeaturesFile ffile = new FeaturesFile();
394
395     /*
396      * only update default type and group if we used defaults
397      */
398     final String enteredType = name.getText().trim();
399     final String enteredGroup = group.getText().trim();
400     final String enteredDesc = description.getText().replace('\n', ' ');
401
402     if (dialog.accept && useLastDefaults)
403     {
404       lastFeatureAdded = enteredType;
405       lastFeatureGroupAdded = enteredGroup;
406     }
407
408     if (!create)
409     {
410       SequenceFeature sf = features.get(featureIndex);
411       if (dialog.accept)
412       {
413         if (!colourPanel.isGcol)
414         {
415           // update colour - otherwise its already done.
416           setColour(sf.type, new FeatureColour(colourPanel.getBackground()));
417         }
418         int newBegin = sf.begin;
419         int newEnd = sf.end;
420         try
421         {
422           newBegin = Integer.parseInt(start.getText());
423           newEnd = Integer.parseInt(end.getText());
424         } catch (NumberFormatException ex)
425         {
426           // 
427         }
428
429         /*
430          * replace the feature by deleting it and adding a new one
431          * (to ensure integrity of SequenceFeatures data store)
432          */
433         sequences.get(0).deleteFeature(sf);
434         SequenceFeature newSf = new SequenceFeature(sf, newBegin, newEnd,
435                 enteredGroup, sf.getScore());
436         newSf.setDescription(enteredDesc);
437         ffile.parseDescriptionHTML(newSf, false);
438         // amend features dialog only updates one sequence at a time
439         sequences.get(0).addSequenceFeature(newSf);
440         boolean typeOrGroupChanged = (!featureType.equals(sf.type) || !featureGroup
441                 .equals(sf.featureGroup));
442
443         ffile.parseDescriptionHTML(sf, false);
444         if (typeOrGroupChanged)
445         {
446           featuresAdded();
447         }
448       }
449       if (deleteFeature)
450       {
451         sequences.get(0).deleteFeature(sf);
452         // ensure Feature Settings reflects removal of feature / group
453         featuresAdded();
454       }
455     }
456     else
457     {
458       /*
459        * adding feature(s)
460        */
461       if (dialog.accept && name.getText().length() > 0)
462       {
463         for (int i = 0; i < sequences.size(); i++)
464         {
465           SequenceFeature sf = features.get(i);
466           SequenceFeature sf2 = new SequenceFeature(enteredType,
467                   enteredDesc, sf.getBegin(), sf.getEnd(), enteredGroup);
468           ffile.parseDescriptionHTML(sf2, false);
469           sequences.get(i).addSequenceFeature(sf2);
470         }
471
472         Color newColour = colourPanel.getBackground();
473         // setColour(lastFeatureAdded, fcol);
474
475         setColour(enteredType, new FeatureColour(newColour)); // was fcol
476         featuresAdded();
477       }
478       else
479       {
480         // no update to the alignment
481         return false;
482       }
483     }
484     // refresh the alignment and the feature settings dialog
485     if (((jalview.appletgui.AlignViewport) av).featureSettings != null)
486     {
487       ((jalview.appletgui.AlignViewport) av).featureSettings.refreshTable();
488     }
489     // findAllFeatures();
490
491     ap.paintAlignment(true);
492
493     return true;
494   }
495
496   protected void warnIfGroupHidden(Frame frame, String group)
497   {
498     if (featureGroups.containsKey(group) && !featureGroups.get(group))
499     {
500       String msg = MessageManager.formatMessage("label.warning_hidden",
501               MessageManager.getString("label.group"), group);
502       showWarning(frame, msg);
503     }
504   }
505
506   protected void warnIfTypeHidden(Frame frame, String type)
507   {
508     if (getRenderOrder().contains(type))
509     {
510       if (!showFeatureOfType(type))
511       {
512         String msg = MessageManager.formatMessage("label.warning_hidden",
513                 MessageManager.getString("label.feature_type"), type);
514         showWarning(frame, msg);
515       }
516     }
517   }
518
519   /**
520    * @param frame
521    * @param msg
522    */
523   protected void showWarning(Frame frame, String msg)
524   {
525     JVDialog d = new JVDialog(frame, "", true, 350, 200);
526     Panel mp = new Panel();
527     d.ok.setLabel(MessageManager.getString("action.ok"));
528     d.cancel.setVisible(false);
529     mp.setLayout(new FlowLayout());
530     mp.add(new Label(msg));
531     d.setMainPanel(mp);
532     d.setVisible(true);
533   }
534 }