b33ada2c6320ea03b34dc1d32940f1ec79b65c32
[jalview.git] / src / jalview / gui / 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.gui;
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
33 import java.awt.BorderLayout;
34 import java.awt.Color;
35 import java.awt.Dimension;
36 import java.awt.Font;
37 import java.awt.GridLayout;
38 import java.awt.event.ActionEvent;
39 import java.awt.event.ActionListener;
40 import java.awt.event.ItemEvent;
41 import java.awt.event.ItemListener;
42 import java.awt.event.MouseAdapter;
43 import java.awt.event.MouseEvent;
44 import java.util.Arrays;
45 import java.util.Comparator;
46 import java.util.HashMap;
47 import java.util.List;
48
49 import javax.swing.JColorChooser;
50 import javax.swing.JComboBox;
51 import javax.swing.JLabel;
52 import javax.swing.JPanel;
53 import javax.swing.JScrollPane;
54 import javax.swing.JSpinner;
55 import javax.swing.JTextArea;
56 import javax.swing.JTextField;
57 import javax.swing.SwingConstants;
58
59 /**
60  * DOCUMENT ME!
61  * 
62  * @author $author$
63  * @version $Revision$
64  */
65 public class FeatureRenderer extends
66         jalview.renderer.seqfeatures.FeatureRenderer
67 {
68   Color resBoxColour;
69
70   AlignmentPanel ap;
71
72   /**
73    * Creates a new FeatureRenderer object
74    * 
75    * @param alignPanel
76    */
77   public FeatureRenderer(AlignmentPanel alignPanel)
78   {
79     super(alignPanel.av);
80     this.ap = alignPanel;
81     if (alignPanel.getSeqPanel() != null
82             && alignPanel.getSeqPanel().seqCanvas != null
83             && alignPanel.getSeqPanel().seqCanvas.fr != null)
84     {
85       transferSettings(alignPanel.getSeqPanel().seqCanvas.fr);
86     }
87   }
88
89   // // /////////////
90   // // Feature Editing Dialog
91   // // Will be refactored in next release.
92
93   static String lastFeatureAdded;
94
95   static String lastFeatureGroupAdded;
96
97   static String lastDescriptionAdded;
98
99   FeatureColourI oldcol, fcol;
100
101   int featureIndex = 0;
102
103   /**
104    * Presents a dialog allowing the user to add new features, or amend or delete
105    * existing features. Currently this can be on
106    * <ul>
107    * <li>double-click on a sequence - Amend/Delete features at position</li>
108    * <li>Create sequence feature from pop-up menu on selected region</li>
109    * <li>Create features for pattern matches from Find</li>
110    * </ul>
111    * 
112    * @param sequences
113    *          the sequences features are to be created on (if creating
114    *          features), or a single sequence (if amending features)
115    * @param features
116    *          the current features at the position (if amending), or template
117    *          new features with start/end position set (if creating)
118    * @param create
119    *          true to create features, false to amend or delete
120    * @param featureType
121    *          the feature type to set on new features; if null, defaults to the
122    *          type of the last new feature created if any, failing that to
123    *          "feature_1"
124    * @param alignPanel
125    * @return
126    */
127   protected boolean amendFeatures(final List<SequenceI> sequences,
128           final List<SequenceFeature> features, boolean create,
129           final AlignmentPanel alignPanel, String featureType)
130   {
131
132     featureIndex = 0;
133
134     final JPanel mainPanel = new JPanel(new BorderLayout());
135     final JTextField name = new JTextField(25);
136     final JTextField source = new JTextField(25);
137     final JTextArea description = new JTextArea(3, 25);
138     final JSpinner start = new JSpinner();
139     final JSpinner end = new JSpinner();
140     start.setPreferredSize(new Dimension(80, 20));
141     end.setPreferredSize(new Dimension(80, 20));
142     final FeatureRenderer me = this;
143     final JLabel colour = new JLabel();
144     colour.setOpaque(true);
145     // colour.setBorder(BorderFactory.createEtchedBorder());
146     colour.setMaximumSize(new Dimension(30, 16));
147     colour.addMouseListener(new MouseAdapter()
148     {
149       FeatureColourChooser fcc = null;
150
151       @Override
152       public void mousePressed(MouseEvent evt)
153       {
154         if (fcol.isSimpleColour())
155         {
156           Color col = JColorChooser.showDialog(Desktop.desktop,
157                   MessageManager.getString("label.select_feature_colour"),
158                   fcol.getColour());
159           if (col != null)
160           {
161             fcol = new FeatureColour(col);
162             updateColourButton(mainPanel, colour, new FeatureColour(col));
163           }
164         }
165         else
166         {
167           if (fcc == null)
168           {
169             final String ft = features.get(featureIndex).getType();
170             final String type = ft == null ? lastFeatureAdded : ft;
171             fcc = new FeatureColourChooser(me, type);
172             fcc.setRequestFocusEnabled(true);
173             fcc.requestFocus();
174
175             fcc.addActionListener(new ActionListener()
176             {
177
178               @Override
179               public void actionPerformed(ActionEvent e)
180               {
181                 fcol = fcc.getLastColour();
182                 fcc = null;
183                 setColour(type, fcol);
184                 updateColourButton(mainPanel, colour, fcol);
185               }
186             });
187
188           }
189         }
190       }
191     });
192     JPanel gridPanel = new JPanel(new GridLayout(3, 1));
193
194     if (!create && features.size() > 1)
195     {
196       /*
197        * more than one feature at selected position - add a drop-down
198        * to choose the feature to amend
199        */
200       gridPanel = new JPanel(new GridLayout(4, 1));
201       JPanel choosePanel = new JPanel();
202       choosePanel.add(new JLabel(MessageManager
203               .getString("label.select_feature")
204               + ":"));
205       final JComboBox<String> overlaps = new JComboBox<String>();
206       for (SequenceFeature sf : features)
207       {
208         String text = sf.getType() + "/" + sf.getBegin() + "-"
209                 + sf.getEnd() + " (" + sf.getFeatureGroup() + ")";
210         overlaps.addItem(text);
211       }
212       choosePanel.add(overlaps);
213
214       overlaps.addItemListener(new ItemListener()
215       {
216         @Override
217         public void itemStateChanged(ItemEvent e)
218         {
219           int index = overlaps.getSelectedIndex();
220           if (index != -1)
221           {
222             featureIndex = index;
223             SequenceFeature sf = features.get(index);
224             name.setText(sf.getType());
225             description.setText(sf.getDescription());
226             source.setText(sf.getFeatureGroup());
227             start.setValue(new Integer(sf.getBegin()));
228             end.setValue(new Integer(sf.getEnd()));
229
230             SearchResultsI highlight = new SearchResults();
231             highlight.addResult(sequences.get(0), sf.getBegin(),
232                     sf.getEnd());
233
234             alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(highlight);
235
236           }
237           FeatureColourI col = getFeatureStyle(name.getText());
238           if (col == null)
239           {
240             col = new FeatureColour(ColorUtils
241                     .createColourFromName(name.getText()));
242           }
243           oldcol = fcol = col;
244           updateColourButton(mainPanel, colour, col);
245         }
246       });
247
248       gridPanel.add(choosePanel);
249     }
250     // ////////
251     // ////////////////////////////////////
252
253     JPanel namePanel = new JPanel();
254     gridPanel.add(namePanel);
255     namePanel.add(new JLabel(MessageManager.getString("label.name:"),
256             JLabel.RIGHT));
257     namePanel.add(name);
258
259     JPanel groupPanel = new JPanel();
260     gridPanel.add(groupPanel);
261     groupPanel.add(new JLabel(MessageManager.getString("label.group:"),
262             JLabel.RIGHT));
263     groupPanel.add(source);
264
265     JPanel colourPanel = new JPanel();
266     gridPanel.add(colourPanel);
267     colourPanel.add(new JLabel(MessageManager.getString("label.colour"),
268             JLabel.RIGHT));
269     colourPanel.add(colour);
270     colour.setPreferredSize(new Dimension(150, 15));
271     colour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 9));
272     colour.setForeground(Color.black);
273     colour.setHorizontalAlignment(SwingConstants.CENTER);
274     colour.setVerticalAlignment(SwingConstants.CENTER);
275     colour.setHorizontalTextPosition(SwingConstants.CENTER);
276     colour.setVerticalTextPosition(SwingConstants.CENTER);
277     mainPanel.add(gridPanel, BorderLayout.NORTH);
278
279     JPanel descriptionPanel = new JPanel();
280     descriptionPanel.add(new JLabel(MessageManager
281             .getString("label.description:"),
282             JLabel.RIGHT));
283     description.setFont(JvSwingUtils.getTextAreaFont());
284     description.setLineWrap(true);
285     descriptionPanel.add(new JScrollPane(description));
286
287     if (!create)
288     {
289       mainPanel.add(descriptionPanel, BorderLayout.SOUTH);
290
291       JPanel startEndPanel = new JPanel();
292       startEndPanel.add(new JLabel(MessageManager.getString("label.start"),
293               JLabel.RIGHT));
294       startEndPanel.add(start);
295       startEndPanel.add(new JLabel(MessageManager.getString("label.end"),
296               JLabel.RIGHT));
297       startEndPanel.add(end);
298       mainPanel.add(startEndPanel, BorderLayout.CENTER);
299     }
300     else
301     {
302       mainPanel.add(descriptionPanel, BorderLayout.CENTER);
303     }
304
305     SequenceFeature firstFeature = features.get(0);
306     if (featureType != null)
307     {
308       lastFeatureAdded = featureType;
309     }
310     else
311     {
312       if (lastFeatureAdded == null)
313       {
314         if (firstFeature.type != null)
315         {
316           lastFeatureAdded = firstFeature.type;
317         }
318         else
319         {
320           lastFeatureAdded = "feature_1";
321         }
322       }
323     }
324
325     if (lastFeatureGroupAdded == null)
326     {
327       if (firstFeature.featureGroup != null)
328       {
329         lastFeatureGroupAdded = firstFeature.featureGroup;
330       }
331       else
332       {
333         lastFeatureGroupAdded = "Jalview";
334       }
335     }
336
337     if (create)
338     {
339       name.setText(lastFeatureAdded);
340       source.setText(lastFeatureGroupAdded);
341     }
342     else
343     {
344       name.setText(firstFeature.getType());
345       source.setText(firstFeature.getFeatureGroup());
346     }
347
348     start.setValue(new Integer(firstFeature.getBegin()));
349     end.setValue(new Integer(firstFeature.getEnd()));
350     description.setText(firstFeature.getDescription());
351     updateColourButton(mainPanel, colour,
352             (oldcol = fcol = getFeatureStyle(name.getText())));
353     Object[] options;
354     if (!create)
355     {
356       options = new Object[] { MessageManager.getString("label.amend"),
357           MessageManager.getString("action.delete"),
358           MessageManager.getString("action.cancel") };
359     }
360     else
361     {
362       options = new Object[] { MessageManager.getString("action.ok"),
363           MessageManager.getString("action.cancel") };
364     }
365
366     String title = create ? MessageManager
367             .getString("label.create_new_sequence_features")
368             : MessageManager.formatMessage("label.amend_delete_features",
369                     new String[] { sequences.get(0).getName() });
370
371     /*
372      * show the dialog
373      */
374     int reply = JvOptionPane.showInternalOptionDialog(Desktop.desktop,
375             mainPanel, title, JvOptionPane.YES_NO_CANCEL_OPTION,
376             JvOptionPane.QUESTION_MESSAGE, null, options,
377             MessageManager.getString("action.ok"));
378
379     FeaturesFile ffile = new FeaturesFile();
380
381     if (reply == JvOptionPane.OK_OPTION && name.getText().length() > 0)
382     {
383       lastFeatureAdded = name.getText().trim();
384       lastFeatureGroupAdded = source.getText().trim();
385       lastDescriptionAdded = description.getText().replaceAll("\n", " ");
386       // TODO: determine if the null feature group is valid
387       if (lastFeatureGroupAdded.length() < 1)
388       {
389         lastFeatureGroupAdded = null;
390       }
391     }
392
393     if (!create)
394     {
395       SequenceFeature sf = features.get(featureIndex);
396
397       if (reply == JvOptionPane.NO_OPTION)
398       {
399         /*
400          * NO_OPTION corresponds to the Delete button
401          */
402         sequences.get(0).getDatasetSequence().deleteFeature(sf);
403       }
404       else if (reply == JvOptionPane.YES_OPTION)
405       {
406         /*
407          * YES_OPTION corresponds to the Amend button
408          */
409         boolean typeChanged = !lastFeatureAdded.equals(sf.type);
410         String newType = lastFeatureAdded;
411         String newFeatureGroup = lastFeatureGroupAdded;
412         String newDescription = lastDescriptionAdded;
413
414         setColour(newType, fcol);
415         getFeaturesDisplayed().setVisible(newType);
416         int newBegin = sf.begin;
417         int newEnd = sf.end;
418         try
419         {
420           newBegin = ((Integer) start.getValue()).intValue();
421           newEnd = ((Integer) end.getValue()).intValue();
422         } catch (NumberFormatException ex)
423         {
424           // JSpinner doesn't accept invalid format data :-)
425         }
426
427         /*
428          * replace the feature by deleting it and adding a new one
429          * (to ensure integrity of SequenceFeatures data store)
430          */
431         sequences.get(0).deleteFeature(sf);
432         SequenceFeature newSf = new SequenceFeature(newType,
433                 newDescription, newBegin, newEnd, sf.getScore(),
434                 newFeatureGroup);
435         // ensure any additional properties are copied
436         if (sf.otherDetails != null)
437         {
438           newSf.otherDetails = new HashMap<String, Object>(sf.otherDetails);
439         }
440         ffile.parseDescriptionHTML(newSf, false);
441         // add any additional links not parsed from description
442         if (sf.links != null)
443         {
444           for (String link : sf.links)
445           {
446             newSf.addLink(link);
447           }
448         }
449         // amend features only gets one sequence to act on
450         sequences.get(0).addSequenceFeature(newSf);
451
452         if (typeChanged)
453         {
454           findAllFeatures();
455         }
456       }
457     }
458     else
459     // NEW FEATURES ADDED
460     {
461       if (reply == JvOptionPane.OK_OPTION && lastFeatureAdded.length() > 0)
462       {
463         for (int i = 0; i < sequences.size(); i++)
464         {
465           SequenceFeature sf = features.get(i);
466           sf.type = lastFeatureAdded;
467           // fix for JAL-1538 - always set feature group here
468           sf.featureGroup = lastFeatureGroupAdded;
469           sf.description = lastDescriptionAdded;
470           sequences.get(i).addSequenceFeature(sf);
471           ffile.parseDescriptionHTML(sf, false);
472         }
473
474         if (lastFeatureGroupAdded != null)
475         {
476           setGroupVisibility(lastFeatureGroupAdded, true);
477         }
478         setColour(lastFeatureAdded, fcol);
479         setVisible(lastFeatureAdded);
480
481         findAllFeatures(false);
482
483         alignPanel.paintAlignment(true);
484
485         return true;
486       }
487       else
488       {
489         return false;
490       }
491     }
492
493     alignPanel.paintAlignment(true);
494
495     return true;
496   }
497
498   /**
499    * update the amend feature button dependent on the given style
500    * 
501    * @param bigPanel
502    * @param col
503    * @param col
504    */
505   protected void updateColourButton(JPanel bigPanel, JLabel colour,
506           FeatureColourI col)
507   {
508     colour.removeAll();
509     colour.setIcon(null);
510     colour.setToolTipText(null);
511     colour.setText("");
512
513     if (col.isSimpleColour())
514     {
515       colour.setBackground(col.getColour());
516     }
517     else
518     {
519       colour.setBackground(bigPanel.getBackground());
520       colour.setForeground(Color.black);
521       FeatureSettings.renderGraduatedColor(colour, col);
522     }
523   }
524
525   /**
526    * Orders features in render precedence (last in order is last to render, so
527    * displayed on top of other features)
528    * 
529    * @param order
530    */
531   public void orderFeatures(Comparator<String> order)
532   {
533     Arrays.sort(renderOrder, order);
534   }
535 }