JAL-3732 - sequences can be moved up/down in cursor mode via alt-up/down arrow
[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
65         extends 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
273                     .createColourFromName(name.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:"),
289             Label.RIGHT));
290     tmp.add(name);
291
292     tmp = new Panel();
293     panel.add(tmp);
294     tmp.add(new Label(MessageManager.getString("label.group:"),
295             Label.RIGHT));
296     tmp.add(group);
297
298     tmp = new Panel();
299     panel.add(tmp);
300     tmp.add(new Label(MessageManager.getString("label.colour"),
301             Label.RIGHT));
302     tmp.add(colourPanel);
303
304     bigPanel.add(panel, BorderLayout.NORTH);
305
306     panel = new Panel();
307     panel.add(new Label(MessageManager.getString("label.description:"),
308             Label.RIGHT));
309     panel.add(new ScrollPane().add(description));
310
311     if (!create)
312     {
313       bigPanel.add(panel, BorderLayout.SOUTH);
314
315       panel = new Panel();
316       panel.add(new Label(MessageManager.getString("label.start"),
317               Label.RIGHT));
318       panel.add(start);
319       panel.add(new Label(MessageManager.getString("label.end"),
320               Label.RIGHT));
321       panel.add(end);
322       bigPanel.add(panel, BorderLayout.CENTER);
323     }
324     else
325     {
326       bigPanel.add(panel, BorderLayout.CENTER);
327     }
328
329     /*
330      * use defaults for type and group (and update them on Confirm) only
331      * if feature type has not been supplied by the caller
332      * (e.g. for Amend, or create features from Find) 
333      */
334     SequenceFeature firstFeature = features.get(0);
335     boolean useLastDefaults = firstFeature.getType() == null;
336     String featureType = useLastDefaults ? lastFeatureAdded
337             : firstFeature.getType();
338     String featureGroup = useLastDefaults ? lastFeatureGroupAdded
339             : firstFeature.getFeatureGroup();
340
341     String title = create
342             ? MessageManager.getString("label.create_new_sequence_features")
343             : MessageManager.formatMessage("label.amend_delete_features",
344                     new String[]
345                     { sequences.get(0).getName() });
346
347     final JVDialog dialog = new JVDialog(ap.alignFrame, title, true, 385,
348             240);
349
350     dialog.setMainPanel(bigPanel);
351
352     name.setText(featureType);
353     group.setText(featureGroup);
354
355     if (!create)
356     {
357       dialog.ok.setLabel(MessageManager.getString("label.amend"));
358       dialog.buttonPanel.add(deleteButton, 1);
359       deleteButton.addActionListener(new ActionListener()
360       {
361         @Override
362         public void actionPerformed(ActionEvent evt)
363         {
364           deleteFeature = true;
365           dialog.setVisible(false);
366         }
367       });
368     }
369
370     start.setText(firstFeature.getBegin() + "");
371     end.setText(firstFeature.getEnd() + "");
372     description.setText(firstFeature.getDescription());
373     // lookup (or generate) the feature colour
374     FeatureColourI fcol = getFeatureStyle(name.getText());
375     // simply display the feature color in a box
376     colourPanel.updateColor(fcol);
377     dialog.setResizable(true);
378     // TODO: render the graduated color in the box.
379     colourPanel.addMouseListener(new MouseAdapter()
380     {
381       @Override
382       public void mousePressed(MouseEvent evt)
383       {
384         if (!colourPanel.isGcol)
385         {
386           new UserDefinedColours(fr, ap.alignFrame);
387         }
388         else
389         {
390           new FeatureColourChooser(ap.alignFrame, name.getText());
391           dialog.transferFocus();
392         }
393       }
394     });
395     dialog.setVisible(true);
396
397     FeaturesFile ffile = new FeaturesFile();
398
399     /*
400      * only update default type and group if we used defaults
401      */
402     final String enteredType = name.getText().trim();
403     final String enteredGroup = group.getText().trim();
404     final String enteredDesc = description.getText().replace('\n', ' ');
405
406     if (dialog.accept && useLastDefaults)
407     {
408       lastFeatureAdded = enteredType;
409       lastFeatureGroupAdded = enteredGroup;
410     }
411
412     if (!create)
413     {
414       SequenceFeature sf = features.get(featureIndex);
415       if (dialog.accept)
416       {
417         if (!colourPanel.isGcol)
418         {
419           // update colour - otherwise its already done.
420           setColour(enteredType,
421                   new FeatureColour(colourPanel.getBackground()));
422         }
423         int newBegin = sf.begin;
424         int newEnd = sf.end;
425         try
426         {
427           newBegin = Integer.parseInt(start.getText());
428           newEnd = Integer.parseInt(end.getText());
429         } catch (NumberFormatException ex)
430         {
431           // 
432         }
433
434         /*
435          * replace the feature by deleting it and adding a new one
436          * (to ensure integrity of SequenceFeatures data store)
437          */
438         sequences.get(0).deleteFeature(sf);
439         SequenceFeature newSf = new SequenceFeature(sf, enteredType,
440                 newBegin, newEnd, enteredGroup, sf.getScore());
441         newSf.setDescription(enteredDesc);
442         ffile.parseDescriptionHTML(newSf, false);
443         // amend features dialog only updates one sequence at a time
444         sequences.get(0).addSequenceFeature(newSf);
445         boolean typeOrGroupChanged = (!featureType.equals(newSf.getType()) || !featureGroup
446                 .equals(newSf.getFeatureGroup()));
447
448         ffile.parseDescriptionHTML(sf, false);
449         if (typeOrGroupChanged)
450         {
451           featuresAdded();
452         }
453       }
454       if (deleteFeature)
455       {
456         sequences.get(0).deleteFeature(sf);
457         // ensure Feature Settings reflects removal of feature / group
458         featuresAdded();
459       }
460     }
461     else
462     {
463       /*
464        * adding feature(s)
465        */
466       if (dialog.accept && name.getText().length() > 0)
467       {
468         for (int i = 0; i < sequences.size(); i++)
469         {
470           SequenceFeature sf = features.get(i);
471           SequenceFeature sf2 = new SequenceFeature(enteredType,
472                   enteredDesc, sf.getBegin(), sf.getEnd(), enteredGroup);
473           ffile.parseDescriptionHTML(sf2, false);
474           sequences.get(i).addSequenceFeature(sf2);
475         }
476
477         Color newColour = colourPanel.getBackground();
478         // setColour(lastFeatureAdded, fcol);
479
480         setColour(enteredType, new FeatureColour(newColour)); // was fcol
481         featuresAdded();
482       }
483       else
484       {
485         // no update to the alignment
486         return false;
487       }
488     }
489     // refresh the alignment and the feature settings dialog
490     if (((jalview.appletgui.AlignViewport) av).featureSettings != null)
491     {
492       ((jalview.appletgui.AlignViewport) av).featureSettings.refreshTable();
493     }
494     // findAllFeatures();
495
496     ap.paintAlignment(true, true);
497
498     return true;
499   }
500
501   protected void warnIfGroupHidden(Frame frame, String group)
502   {
503     if (featureGroups.containsKey(group) && !featureGroups.get(group))
504     {
505       String msg = MessageManager.formatMessage("label.warning_hidden",
506               MessageManager.getString("label.group"), group);
507       showWarning(frame, msg);
508     }
509   }
510
511   protected void warnIfTypeHidden(Frame frame, String type)
512   {
513     if (getRenderOrder().contains(type))
514     {
515       if (!showFeatureOfType(type))
516       {
517         String msg = MessageManager.formatMessage("label.warning_hidden",
518                 MessageManager.getString("label.feature_type"), type);
519         showWarning(frame, msg);
520       }
521     }
522   }
523
524   /**
525    * @param frame
526    * @param msg
527    */
528   protected void showWarning(Frame frame, String msg)
529   {
530     JVDialog d = new JVDialog(frame, "", true, 350, 200);
531     Panel mp = new Panel();
532     d.ok.setLabel(MessageManager.getString("action.ok"));
533     d.cancel.setVisible(false);
534     mp.setLayout(new FlowLayout());
535     mp.add(new Label(msg));
536     d.setMainPanel(mp);
537     d.setVisible(true);
538   }
539 }