JAL-2664 Updates following review
[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     String enteredType = name.getText().trim();
399     if (dialog.accept && useLastDefaults)
400     {
401       lastFeatureAdded = enteredType;
402       lastFeatureGroupAdded = group.getText().trim();
403     }
404
405     if (!create)
406     {
407       SequenceFeature sf = features.get(featureIndex);
408       if (dialog.accept)
409       {
410         sf.type = enteredType;
411         sf.featureGroup = group.getText().trim();
412         if (sf.featureGroup != null && sf.featureGroup.length() < 1)
413         {
414           sf.featureGroup = null;
415         }
416         sf.description = description.getText().replace('\n', ' ');
417         if (!colourPanel.isGcol)
418         {
419           // update colour - otherwise its already done.
420           setColour(sf.type, new FeatureColour(colourPanel.getBackground()));
421         }
422         try
423         {
424           sf.begin = Integer.parseInt(start.getText());
425           sf.end = Integer.parseInt(end.getText());
426         } catch (NumberFormatException ex)
427         {
428           //
429         }
430         boolean typeOrGroupChanged = (!featureType.equals(sf.type) || !featureGroup
431                 .equals(sf.featureGroup));
432
433         ffile.parseDescriptionHTML(sf, false);
434         if (typeOrGroupChanged)
435         {
436           featuresAdded();
437         }
438       }
439       if (deleteFeature)
440       {
441         sequences.get(0).deleteFeature(sf);
442         // ensure Feature Settings reflects removal of feature / group
443         featuresAdded();
444       }
445     }
446     else
447     {
448       /*
449        * adding feature(s)
450        */
451       if (dialog.accept && name.getText().length() > 0)
452       {
453         for (int i = 0; i < sequences.size(); i++)
454         {
455           features.get(i).type = enteredType;
456           features.get(i).featureGroup = group.getText().trim();
457           features.get(i).description = description.getText()
458                   .replace('\n', ' ');
459           sequences.get(i).addSequenceFeature(features.get(i));
460           ffile.parseDescriptionHTML(features.get(i), false);
461         }
462
463         Color newColour = colourPanel.getBackground();
464         // setColour(lastFeatureAdded, fcol);
465
466         setColour(enteredType, new FeatureColour(newColour)); // was fcol
467         featuresAdded();
468       }
469       else
470       {
471         // no update to the alignment
472         return false;
473       }
474     }
475     // refresh the alignment and the feature settings dialog
476     if (((jalview.appletgui.AlignViewport) av).featureSettings != null)
477     {
478       ((jalview.appletgui.AlignViewport) av).featureSettings.refreshTable();
479     }
480     // findAllFeatures();
481
482     ap.paintAlignment(true);
483
484     return true;
485   }
486
487   protected void warnIfGroupHidden(Frame frame, String group)
488   {
489     if (featureGroups.containsKey(group) && !featureGroups.get(group))
490     {
491       String msg = MessageManager.formatMessage("label.warning_hidden",
492               MessageManager.getString("label.group"), group);
493       showWarning(frame, msg);
494     }
495   }
496
497   protected void warnIfTypeHidden(Frame frame, String type)
498   {
499     if (getRenderOrder().contains(type))
500     {
501       if (!showFeatureOfType(type))
502       {
503         String msg = MessageManager.formatMessage("label.warning_hidden",
504                 MessageManager.getString("label.feature_type"), type);
505         showWarning(frame, msg);
506       }
507     }
508   }
509
510   /**
511    * @param frame
512    * @param msg
513    */
514   protected void showWarning(Frame frame, String msg)
515   {
516     JVDialog d = new JVDialog(frame, "", true, 350, 200);
517     Panel mp = new Panel();
518     d.ok.setLabel(MessageManager.getString("action.ok"));
519     d.cancel.setVisible(false);
520     mp.setLayout(new FlowLayout());
521     mp.add(new Label(msg));
522     d.setMainPanel(mp);
523     d.setVisible(true);
524   }
525 }