JAL-3161 limit tooltip and status updates to visible columns
[jalview.git] / src / jalview / appletgui / CutAndPasteTransfer.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.analysis.AlignmentUtils;
24 import jalview.api.ComplexAlignFile;
25 import jalview.api.FeaturesSourceI;
26 import jalview.bin.JalviewLite;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.HiddenColumns;
29 import jalview.datamodel.PDBEntry;
30 import jalview.datamodel.SequenceI;
31 import jalview.io.AlignmentFileReaderI;
32 import jalview.io.AnnotationFile;
33 import jalview.io.AppletFormatAdapter;
34 import jalview.io.DataSourceType;
35 import jalview.io.FileFormatI;
36 import jalview.io.IdentifyFile;
37 import jalview.io.NewickFile;
38 import jalview.io.TCoffeeScoreFile;
39 import jalview.json.binding.biojson.v1.ColourSchemeMapper;
40 import jalview.schemes.ColourSchemeI;
41 import jalview.schemes.TCoffeeColourScheme;
42 import jalview.util.MessageManager;
43
44 import java.awt.BorderLayout;
45 import java.awt.Button;
46 import java.awt.Dialog;
47 import java.awt.Font;
48 import java.awt.Frame;
49 import java.awt.Label;
50 import java.awt.Panel;
51 import java.awt.TextArea;
52 import java.awt.event.ActionEvent;
53 import java.awt.event.ActionListener;
54 import java.awt.event.MouseEvent;
55 import java.awt.event.MouseListener;
56 import java.io.IOException;
57
58 public class CutAndPasteTransfer extends Panel
59         implements ActionListener, MouseListener
60 {
61   boolean pdbImport = false;
62
63   boolean treeImport = false;
64
65   boolean annotationImport = false;
66
67   SequenceI seq;
68
69   AlignFrame alignFrame;
70
71   AlignmentFileReaderI source = null;
72
73   public CutAndPasteTransfer(boolean forImport, AlignFrame alignFrame)
74   {
75     try
76     {
77       jbInit();
78     } catch (Exception e)
79     {
80       e.printStackTrace();
81     }
82
83     this.alignFrame = alignFrame;
84
85     if (!forImport)
86     {
87       buttonPanel.setVisible(false);
88     }
89   }
90
91   public String getText()
92   {
93     return textarea.getText();
94   }
95
96   public void setText(String text)
97   {
98     textarea.setText(text);
99   }
100
101   public void setPDBImport(SequenceI seq)
102   {
103     this.seq = seq;
104     accept.setLabel(MessageManager.getString("action.accept"));
105     addSequences.setVisible(false);
106     pdbImport = true;
107   }
108
109   public void setTreeImport()
110   {
111     treeImport = true;
112     accept.setLabel(MessageManager.getString("action.accept"));
113     addSequences.setVisible(false);
114   }
115
116   public void setAnnotationImport()
117   {
118     annotationImport = true;
119     accept.setLabel(MessageManager.getString("action.accept"));
120     addSequences.setVisible(false);
121   }
122
123   @Override
124   public void actionPerformed(ActionEvent evt)
125   {
126     if (evt.getSource() == accept)
127     {
128       ok(true);
129     }
130     else if (evt.getSource() == addSequences)
131     {
132       ok(false);
133     }
134     else if (evt.getSource() == cancel)
135     {
136       cancel();
137     }
138   }
139
140   protected void ok(boolean newWindow)
141   {
142     String text = getText();
143     int length = text.length();
144     textarea.append("\n");
145     if (textarea.getText().length() == length)
146     {
147       String warning = "\n\n#################################################\n"
148               + "WARNING!! THIS IS THE MAXIMUM SIZE OF TEXTAREA!!\n"
149               + "\nCAN'T INPUT FULL ALIGNMENT"
150               + "\n\nYOU MUST DELETE THIS WARNING TO CONTINUE"
151               + "\n\nMAKE SURE LAST SEQUENCE PASTED IS COMPLETE"
152               + "\n#################################################\n";
153       textarea.setText(text.substring(0, text.length() - warning.length())
154               + warning);
155
156       textarea.setCaretPosition(text.length());
157     }
158
159     if (pdbImport)
160     {
161       openPdbViewer(text);
162
163     }
164     else if (treeImport)
165     {
166       if (!loadTree())
167       {
168         return;
169       }
170     }
171     else if (annotationImport)
172     {
173       loadAnnotations();
174     }
175     else if (alignFrame != null)
176     {
177       loadAlignment(text, newWindow, alignFrame.getAlignViewport());
178     }
179
180     // TODO: dialog should indicate if data was parsed correctly or not - see
181     // JAL-1102
182     if (this.getParent() instanceof Frame)
183     {
184       ((Frame) this.getParent()).setVisible(false);
185     }
186     else
187     {
188       ((Dialog) this.getParent()).setVisible(false);
189     }
190   }
191
192   /**
193    * Parses text as Newick Tree format, and loads on to the alignment. Returns
194    * true if successful, else false.
195    */
196   protected boolean loadTree()
197   {
198     try
199     {
200       NewickFile fin = new NewickFile(textarea.getText(),
201               DataSourceType.PASTE);
202
203       fin.parse();
204       if (fin.getTree() != null)
205       {
206         alignFrame.loadTree(fin, "Pasted tree file");
207         return true;
208       }
209     } catch (Exception ex)
210     {
211       // TODO: JAL-1102 - should have a warning message in dialog, not simply
212       // overwrite the broken input data with the exception
213       textarea.setText(MessageManager.formatMessage(
214               "label.could_not_parse_newick_file", new Object[]
215               { ex.getMessage() }));
216       return false;
217     }
218     return false;
219   }
220
221   /**
222    * Parse text as an alignment file and add to the current or a new window.
223    * 
224    * @param text
225    * @param newWindow
226    */
227   protected void loadAlignment(String text, boolean newWindow,
228           AlignViewport viewport)
229   {
230     AlignmentI al = null;
231
232     try
233     {
234       FileFormatI format = new IdentifyFile().identify(text,
235               DataSourceType.PASTE);
236       AppletFormatAdapter afa = new AppletFormatAdapter(
237               alignFrame.alignPanel);
238       al = afa.readFile(text, DataSourceType.PASTE, format);
239       source = afa.getAlignFile();
240
241       if (al != null)
242       {
243         al.setDataset(null); // set dataset on alignment/sequences
244
245         /*
246          * SplitFrame option dependent on applet parameter for now.
247          */
248         boolean allowSplitFrame = alignFrame.viewport.applet
249                 .getDefaultParameter("enableSplitFrame", false);
250         if (allowSplitFrame && openSplitFrame(al, format))
251         {
252           return;
253         }
254         if (newWindow)
255         {
256           AlignFrame af;
257
258           if (source instanceof ComplexAlignFile)
259           {
260             HiddenColumns colSel = ((ComplexAlignFile) source)
261                     .getHiddenColumns();
262             SequenceI[] hiddenSeqs = ((ComplexAlignFile) source)
263                     .getHiddenSequences();
264             boolean showSeqFeatures = ((ComplexAlignFile) source)
265                     .isShowSeqFeatures();
266             String colourSchemeName = ((ComplexAlignFile) source)
267                     .getGlobalColourScheme();
268             af = new AlignFrame(al, hiddenSeqs, colSel,
269                     alignFrame.viewport.applet,
270                     "Cut & Paste input - " + format, false);
271             af.getAlignViewport().setShowSequenceFeatures(showSeqFeatures);
272             ColourSchemeI cs = ColourSchemeMapper
273                     .getJalviewColourScheme(colourSchemeName, al);
274             if (cs != null)
275             {
276               af.changeColour(cs);
277             }
278           }
279           else
280           {
281             af = new AlignFrame(al, alignFrame.viewport.applet,
282                     "Cut & Paste input - " + format, false);
283             if (source instanceof FeaturesSourceI)
284             {
285               af.getAlignViewport().setShowSequenceFeatures(true);
286             }
287           }
288
289           af.statusBar.setText(MessageManager.getString(
290                   "label.successfully_pasted_annotation_to_alignment"));
291         }
292         else
293         {
294           alignFrame.addSequences(al.getSequencesArray());
295           alignFrame.statusBar.setText(MessageManager
296                   .getString("label.successfully_pasted_alignment_file"));
297         }
298       }
299     } catch (IOException ex)
300     {
301       ex.printStackTrace();
302     }
303   }
304
305   /**
306    * Check whether the new alignment could be mapped to the current one as
307    * cDNA/protein, if so offer the option to open as split frame view. Returns
308    * true if a split frame view is opened, false if not.
309    * 
310    * @param al
311    * @return
312    */
313   protected boolean openSplitFrame(AlignmentI al, FileFormatI format)
314   {
315     final AlignmentI thisAlignment = this.alignFrame.getAlignViewport()
316             .getAlignment();
317     if (thisAlignment.isNucleotide() == al.isNucleotide())
318     {
319       // both nucleotide or both protein
320       return false;
321     }
322     AlignmentI protein = thisAlignment.isNucleotide() ? al : thisAlignment;
323     AlignmentI dna = thisAlignment.isNucleotide() ? thisAlignment : al;
324     boolean mapped = AlignmentUtils.mapProteinAlignmentToCdna(protein, dna);
325     if (!mapped)
326     {
327       return false;
328     }
329
330     /*
331      * A mapping is possible; ask user if they want a split frame.
332      */
333     String title = MessageManager.getString("label.open_split_window");
334     final JVDialog dialog = new JVDialog((Frame) this.getParent(), title,
335             true, 100, 400);
336     dialog.ok.setLabel(MessageManager.getString("action.yes"));
337     dialog.cancel.setLabel(MessageManager.getString("action.no"));
338     Panel question = new Panel(new BorderLayout());
339     final String text = MessageManager
340             .getString("label.open_split_window?");
341     question.add(new Label(text, Label.CENTER), BorderLayout.CENTER);
342     dialog.setMainPanel(question);
343     dialog.setVisible(true);
344     dialog.toFront();
345
346     if (!dialog.accept)
347     {
348       return false;
349     }
350
351     /*
352      * 'align' the added alignment to match the current one
353      */
354     al.alignAs(thisAlignment);
355
356     /*
357      * Open SplitFrame with DNA above and protein below, including the alignment
358      * from textbox and a copy of the original.
359      */
360     final JalviewLite applet = this.alignFrame.viewport.applet;
361     AlignFrame copyFrame = new AlignFrame(
362             this.alignFrame.viewport.getAlignment(), applet,
363             alignFrame.getTitle(), false, false);
364     AlignFrame newFrame = new AlignFrame(al, alignFrame.viewport.applet,
365             "Cut & Paste input - " + format, false, false);
366     AlignFrame dnaFrame = al.isNucleotide() ? newFrame : copyFrame;
367     AlignFrame proteinFrame = al.isNucleotide() ? copyFrame : newFrame;
368     SplitFrame sf = new SplitFrame(dnaFrame, proteinFrame);
369     sf.addToDisplay(false, applet);
370     return true;
371   }
372
373   /**
374    * Parse the text as a TCoffee score file, if successful add scores as
375    * alignment annotations.
376    */
377   protected void loadAnnotations()
378   {
379     TCoffeeScoreFile tcf = null;
380     try
381     {
382       tcf = new TCoffeeScoreFile(textarea.getText(),
383               jalview.io.DataSourceType.PASTE);
384       if (tcf.isValid())
385       {
386         if (tcf.annotateAlignment(alignFrame.viewport.getAlignment(), true))
387         {
388           alignFrame.tcoffeeColour.setEnabled(true);
389           alignFrame.alignPanel.fontChanged();
390           alignFrame.changeColour(new TCoffeeColourScheme(
391                   alignFrame.viewport.getAlignment()));
392           alignFrame.statusBar.setText(MessageManager.getString(
393                   "label.successfully_pasted_tcoffee_scores_to_alignment"));
394         }
395         else
396         {
397           // file valid but didn't get added to alignment for some reason
398           alignFrame.statusBar.setText(MessageManager.formatMessage(
399                   "label.failed_add_tcoffee_scores", new Object[]
400                   { (tcf.getWarningMessage() != null
401                           ? tcf.getWarningMessage()
402                           : "") }));
403         }
404       }
405       else
406       {
407         tcf = null;
408       }
409     } catch (Exception x)
410     {
411       tcf = null;
412     }
413     if (tcf == null)
414     {
415       if (new AnnotationFile().annotateAlignmentView(alignFrame.viewport,
416               textarea.getText(), jalview.io.DataSourceType.PASTE))
417       {
418         alignFrame.alignPanel.fontChanged();
419         alignFrame.alignPanel.setScrollValues(0, 0);
420         alignFrame.statusBar.setText(MessageManager.getString(
421                 "label.successfully_pasted_annotation_to_alignment"));
422
423       }
424       else
425       {
426         if (!alignFrame.parseFeaturesFile(textarea.getText(),
427                 jalview.io.DataSourceType.PASTE))
428         {
429           alignFrame.statusBar.setText(MessageManager.getString(
430                   "label.couldnt_parse_pasted_text_as_valid_annotation_feature_GFF_tcoffee_file"));
431         }
432       }
433     }
434   }
435
436   /**
437    * Open a Jmol viewer (if available), failing that the built-in PDB viewer,
438    * passing the input text as the PDB file data.
439    * 
440    * @param text
441    */
442   protected void openPdbViewer(String text)
443   {
444     PDBEntry pdb = new PDBEntry();
445     pdb.setFile(text);
446
447     if (alignFrame.alignPanel.av.applet.jmolAvailable)
448     {
449       new jalview.appletgui.AppletJmol(pdb, new SequenceI[] { seq }, null,
450               alignFrame.alignPanel, DataSourceType.PASTE);
451     }
452     else
453     {
454       new MCview.AppletPDBViewer(pdb, new SequenceI[] { seq }, null,
455               alignFrame.alignPanel, DataSourceType.PASTE);
456     }
457   }
458
459   protected void cancel()
460   {
461     textarea.setText("");
462     if (this.getParent() instanceof Frame)
463     {
464       ((Frame) this.getParent()).setVisible(false);
465     }
466     else
467     {
468       ((Dialog) this.getParent()).setVisible(false);
469     }
470   }
471
472   protected TextArea textarea = new TextArea();
473
474   Button accept = new Button("New Window");
475
476   Button addSequences = new Button("Add to Current Alignment");
477
478   Button cancel = new Button("Close");
479
480   protected Panel buttonPanel = new Panel();
481
482   BorderLayout borderLayout1 = new BorderLayout();
483
484   private void jbInit() throws Exception
485   {
486     textarea.setFont(new java.awt.Font("Monospaced", Font.PLAIN, 10));
487     textarea.setText(
488             MessageManager.getString("label.paste_your_alignment_file"));
489     textarea.addMouseListener(this);
490     this.setLayout(borderLayout1);
491     accept.addActionListener(this);
492     addSequences.addActionListener(this);
493     cancel.addActionListener(this);
494     this.add(buttonPanel, BorderLayout.SOUTH);
495     buttonPanel.add(accept, null);
496     buttonPanel.add(addSequences);
497     buttonPanel.add(cancel, null);
498     this.add(textarea, java.awt.BorderLayout.CENTER);
499   }
500
501   @Override
502   public void mousePressed(MouseEvent evt)
503   {
504     if (textarea.getText()
505             .startsWith(MessageManager.getString("label.paste_your")))
506     {
507       textarea.setText("");
508     }
509   }
510
511   @Override
512   public void mouseReleased(MouseEvent evt)
513   {
514   }
515
516   @Override
517   public void mouseClicked(MouseEvent evt)
518   {
519   }
520
521   @Override
522   public void mouseEntered(MouseEvent evt)
523   {
524   }
525
526   @Override
527   public void mouseExited(MouseEvent evt)
528   {
529   }
530 }