900d3be86a5b56ce8e7d1bfd57f51e55d5924115
[jalview.git] / src / jalview / gui / AppJmol.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.bin.Cache;
24 import jalview.datamodel.Alignment;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.ColumnSelection;
27 import jalview.datamodel.PDBEntry;
28 import jalview.datamodel.SequenceI;
29 import jalview.gui.StructureViewer.ViewerType;
30 import jalview.io.AppletFormatAdapter;
31 import jalview.io.JalviewFileChooser;
32 import jalview.io.JalviewFileView;
33 import jalview.schemes.BuriedColourScheme;
34 import jalview.schemes.ColourSchemeI;
35 import jalview.schemes.HelixColourScheme;
36 import jalview.schemes.HydrophobicColourScheme;
37 import jalview.schemes.PurinePyrimidineColourScheme;
38 import jalview.schemes.StrandColourScheme;
39 import jalview.schemes.TaylorColourScheme;
40 import jalview.schemes.TurnColourScheme;
41 import jalview.schemes.ZappoColourScheme;
42 import jalview.structures.models.AAStructureBindingModel;
43 import jalview.util.BrowserLauncher;
44 import jalview.util.ImageMaker;
45 import jalview.util.MessageManager;
46 import jalview.util.Platform;
47 import jalview.ws.dbsources.Pdb;
48
49 import java.awt.BorderLayout;
50 import java.awt.Color;
51 import java.awt.Dimension;
52 import java.awt.Font;
53 import java.awt.Graphics;
54 import java.awt.Rectangle;
55 import java.awt.event.ActionEvent;
56 import java.awt.event.ActionListener;
57 import java.awt.event.ItemEvent;
58 import java.awt.event.ItemListener;
59 import java.io.BufferedReader;
60 import java.io.File;
61 import java.io.FileOutputStream;
62 import java.io.FileReader;
63 import java.io.IOException;
64 import java.io.PrintWriter;
65 import java.util.Vector;
66
67 import javax.swing.JCheckBoxMenuItem;
68 import javax.swing.JColorChooser;
69 import javax.swing.JInternalFrame;
70 import javax.swing.JMenu;
71 import javax.swing.JMenuItem;
72 import javax.swing.JOptionPane;
73 import javax.swing.JPanel;
74 import javax.swing.JSplitPane;
75 import javax.swing.event.InternalFrameAdapter;
76 import javax.swing.event.InternalFrameEvent;
77 import javax.swing.event.MenuEvent;
78 import javax.swing.event.MenuListener;
79
80 public class AppJmol extends StructureViewerBase
81 {
82   AppJmolBinding jmb;
83
84   JPanel scriptWindow;
85
86   JSplitPane splitPane;
87
88   RenderPanel renderPanel;
89
90   private boolean addingStructures = false;
91
92   ViewSelectionMenu seqColourBy;
93
94   /**
95    * 
96    * @param files
97    * @param ids
98    * @param seqs
99    * @param ap
100    * @param usetoColour
101    *          - add the alignment panel to the list used for colouring these
102    *          structures
103    * @param useToAlign
104    *          - add the alignment panel to the list used for aligning these
105    *          structures
106    * @param leaveColouringToJmol
107    *          - do not update the colours from any other source. Jmol is
108    *          handling them
109    * @param loadStatus
110    * @param bounds
111    * @param viewid
112    */
113   public AppJmol(String[] files, String[] ids, SequenceI[][] seqs,
114           AlignmentPanel ap, boolean usetoColour, boolean useToAlign,
115           boolean leaveColouringToJmol, String loadStatus,
116           Rectangle bounds, String viewid)
117   {
118     PDBEntry[] pdbentrys = new PDBEntry[files.length];
119     for (int i = 0; i < pdbentrys.length; i++)
120     {
121       // PDBEntry pdbentry = new PDBEntry(files[i], ids[i]);
122       PDBEntry pdbentry = new PDBEntry(ids[i], null, PDBEntry.Type.PDB,
123               files[i]);
124       pdbentrys[i] = pdbentry;
125     }
126     // / TODO: check if protocol is needed to be set, and if chains are
127     // autodiscovered.
128     jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
129             pdbentrys, seqs, null, null);
130
131     jmb.setLoadingFromArchive(true);
132     addAlignmentPanel(ap);
133     if (useToAlign)
134     {
135       useAlignmentPanelForSuperposition(ap);
136     }
137     if (leaveColouringToJmol || !usetoColour)
138     {
139       jmb.setColourBySequence(false);
140       seqColour.setSelected(false);
141       viewerColour.setSelected(true);
142     }
143     else if (usetoColour)
144     {
145       useAlignmentPanelForColourbyseq(ap);
146       jmb.setColourBySequence(true);
147       seqColour.setSelected(true);
148       viewerColour.setSelected(false);
149     }
150     this.setBounds(bounds);
151     initMenus();
152     setViewId(viewid);
153     // Desktop.addInternalFrame(this, "Loading File",
154     // bounds.width,bounds.height);
155
156     this.addInternalFrameListener(new InternalFrameAdapter()
157     {
158       @Override
159       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
160       {
161         closeViewer(false);
162       }
163     });
164     initJmol(loadStatus); // pdbentry, seq, JBPCHECK!
165
166   }
167
168   private void initMenus()
169   {
170     seqColour.setSelected(jmb.isColourBySequence());
171     viewerColour.setSelected(!jmb.isColourBySequence());
172     if (_colourwith == null)
173     {
174       _colourwith = new Vector<AlignmentPanel>();
175     }
176     if (_alignwith == null)
177     {
178       _alignwith = new Vector<AlignmentPanel>();
179     }
180
181     seqColourBy = new ViewSelectionMenu(MessageManager.getString("label.colour_by"), this, _colourwith,
182             new ItemListener()
183             {
184
185               @Override
186               public void itemStateChanged(ItemEvent e)
187               {
188                 if (!seqColour.isSelected())
189                 {
190                   seqColour.doClick();
191                 }
192                 else
193                 {
194                   // update the jmol display now.
195                   seqColour_actionPerformed(null);
196                 }
197               }
198             });
199     viewMenu.add(seqColourBy);
200     final ItemListener handler;
201     JMenu alpanels = new ViewSelectionMenu(MessageManager.getString("label.superpose_with"), this,
202             _alignwith, handler = new ItemListener()
203             {
204
205               @Override
206               public void itemStateChanged(ItemEvent e)
207               {
208                 alignStructs.setEnabled(_alignwith.size() > 0);
209                 alignStructs.setToolTipText(MessageManager
210                         .formatMessage(
211                                 "label.align_structures_using_linked_alignment_views",
212                                 new String[]
213                                 { new Integer(_alignwith.size()).toString() }));
214               }
215             });
216     handler.itemStateChanged(null);
217     viewerActionMenu.add(alpanels);
218     viewerActionMenu.addMenuListener(new MenuListener()
219     {
220
221       @Override
222       public void menuSelected(MenuEvent e)
223       {
224         handler.itemStateChanged(null);
225       }
226
227       @Override
228       public void menuDeselected(MenuEvent e)
229       {
230         // TODO Auto-generated method stub
231
232       }
233
234       @Override
235       public void menuCanceled(MenuEvent e)
236       {
237         // TODO Auto-generated method stub
238
239       }
240     });
241   }
242
243   IProgressIndicator progressBar = null;
244
245   /**
246    * add a single PDB structure to a new or existing Jmol view
247    * 
248    * @param pdbentry
249    * @param seq
250    * @param chains
251    * @param ap
252    */
253   public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
254           final AlignmentPanel ap)
255   {
256     progressBar = ap.alignFrame;
257     // ////////////////////////////////
258     // Is the pdb file already loaded?
259     String alreadyMapped = ap.getStructureSelectionManager()
260             .alreadyMappedToFile(pdbentry.getId());
261
262     if (alreadyMapped != null)
263     {
264       int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
265               MessageManager.formatMessage(
266                       "label.pdb_entry_is_already_displayed", new String[]
267                       { pdbentry.getId() }), MessageManager.formatMessage(
268                       "label.map_sequences_to_visible_window", new String[]
269                       { pdbentry.getId() }),
270               JOptionPane.YES_NO_CANCEL_OPTION);
271
272       if (option == JOptionPane.CANCEL_OPTION)
273       {
274         return;
275       }
276       if (option == JOptionPane.YES_OPTION)
277       {
278         // TODO : Fix multiple seq to one chain issue here.
279         ap.getStructureSelectionManager().setMapping(seq, chains,
280                 alreadyMapped, AppletFormatAdapter.FILE);
281         if (ap.getSeqPanel().seqCanvas.fr != null)
282         {
283           ap.getSeqPanel().seqCanvas.fr.featuresAdded();
284           ap.paintAlignment(true);
285         }
286
287         // Now this AppJmol is mapped to new sequences. We must add them to
288         // the exisiting array
289         JInternalFrame[] frames = Desktop.instance.getAllFrames();
290
291         for (int i = 0; i < frames.length; i++)
292         {
293           if (frames[i] instanceof AppJmol)
294           {
295             final AppJmol topJmol = ((AppJmol) frames[i]);
296             // JBPNOTE: this looks like a binding routine, rather than a gui
297             // routine
298             for (int pe = 0; pe < topJmol.jmb.getPdbCount(); pe++)
299             {
300               if (topJmol.jmb.getPdbEntry(pe).getFile()
301                       .equals(alreadyMapped))
302               {
303                 topJmol.jmb.addSequence(pe, seq);
304                 topJmol.addAlignmentPanel(ap);
305                 // add it to the set used for colouring
306                 topJmol.useAlignmentPanelForColourbyseq(ap);
307                 topJmol.buildActionMenu();
308                 ap.getStructureSelectionManager()
309                         .sequenceColoursChanged(ap);
310                 break;
311               }
312             }
313           }
314         }
315
316         return;
317       }
318     }
319
320     /*
321      * Check if there are other Jmol views involving this alignment and prompt
322      * user about adding this molecule to one of them
323      */
324     for (AppJmol topJmol : getJmolsFor(ap))
325     {
326       // TODO: highlight topJmol in view somehow
327       int option = JOptionPane.showInternalConfirmDialog(Desktop.desktop,
328               MessageManager.formatMessage("label.add_pdbentry_to_view",
329                       new String[]
330                       { pdbentry.getId(), topJmol.getTitle() }),
331               MessageManager
332                       .getString("label.align_to_existing_structure_view"),
333               JOptionPane.YES_NO_CANCEL_OPTION);
334       if (option == JOptionPane.CANCEL_OPTION)
335       {
336         return;
337       }
338       if (option == JOptionPane.YES_OPTION)
339       {
340         topJmol.useAlignmentPanelForSuperposition(ap);
341         topJmol.addStructure(pdbentry, seq, chains, true, ap.alignFrame);
342         return;
343       }
344     }
345     // /////////////////////////////////
346     openNewJmol(ap, new PDBEntry[]
347     { pdbentry }, new SequenceI[][]
348     { seq });
349   }
350
351   private void openNewJmol(AlignmentPanel ap, PDBEntry[] pdbentrys,
352           SequenceI[][] seqs)
353   {
354     progressBar = ap.alignFrame;
355     jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
356             pdbentrys, seqs, null, null);
357     addAlignmentPanel(ap);
358     useAlignmentPanelForColourbyseq(ap);
359     if (pdbentrys.length > 1)
360     {
361       alignAddedStructures = true;
362       useAlignmentPanelForSuperposition(ap);
363     }
364     jmb.setColourBySequence(true);
365     setSize(400, 400); // probably should be a configurable/dynamic default here
366     initMenus();
367     worker = null;
368     {
369       addingStructures = false;
370       worker = new Thread(this);
371       worker.start();
372     }
373     this.addInternalFrameListener(new InternalFrameAdapter()
374     {
375       @Override
376       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
377       {
378         closeViewer(false);
379       }
380     });
381
382   }
383
384   /**
385    * create a new Jmol containing several structures superimposed using the
386    * given alignPanel.
387    * 
388    * @param ap
389    * @param pe
390    * @param seqs
391    */
392   public AppJmol(AlignmentPanel ap, PDBEntry[] pe, SequenceI[][] seqs)
393   {
394     openNewJmol(ap, pe, seqs);
395   }
396
397   /**
398    * pdb retrieval thread.
399    */
400   private Thread worker = null;
401
402   /**
403    * add a new structure (with associated sequences and chains) to this viewer,
404    * retrieving it if necessary first.
405    * 
406    * @param pdbentry
407    * @param seq
408    * @param chains
409    * @param alignFrame
410    * @param align
411    *          if true, new structure(s) will be align using associated alignment
412    */
413   private void addStructure(final PDBEntry pdbentry, final SequenceI[] seq,
414           final String[] chains, final boolean b,
415           final IProgressIndicator alignFrame)
416   {
417     if (pdbentry.getFile() == null)
418     {
419       if (worker != null && worker.isAlive())
420       {
421         // a retrieval is in progress, wait around and add ourselves to the
422         // queue.
423         new Thread(new Runnable()
424         {
425           public void run()
426           {
427             while (worker != null && worker.isAlive() && _started)
428             {
429               try
430               {
431                 Thread.sleep(100 + ((int) Math.random() * 100));
432
433               } catch (Exception e)
434               {
435               }
436
437             }
438             // and call ourselves again.
439             addStructure(pdbentry, seq, chains, b, alignFrame);
440           }
441         }).start();
442         return;
443       }
444     }
445     // otherwise, start adding the structure.
446     jmb.addSequenceAndChain(new PDBEntry[]
447     { pdbentry }, new SequenceI[][]
448     { seq }, new String[][]
449     { chains });
450     addingStructures = true;
451     _started = false;
452     alignAddedStructures = b;
453     progressBar = alignFrame; // visual indication happens on caller frame.
454     (worker = new Thread(this)).start();
455     return;
456   }
457
458   private Vector<AppJmol> getJmolsFor(AlignmentPanel apanel)
459   {
460     Vector<AppJmol> result = new Vector<AppJmol>();
461     JInternalFrame[] frames = Desktop.instance.getAllFrames();
462
463     for (JInternalFrame frame : frames)
464     {
465       if (frame instanceof AppJmol)
466       {
467         if (((AppJmol) frame).isLinkedWith(apanel))
468         {
469           result.addElement((AppJmol) frame);
470         }
471       }
472     }
473     return result;
474   }
475
476   void initJmol(String command)
477   {
478     jmb.setFinishedInit(false);
479     renderPanel = new RenderPanel();
480     // TODO: consider waiting until the structure/view is fully loaded before
481     // displaying
482     this.getContentPane().add(renderPanel, java.awt.BorderLayout.CENTER);
483     Desktop.addInternalFrame(this, jmb.getViewerTitle(),
484             getBounds().width, getBounds().height);
485     if (scriptWindow == null)
486     {
487       BorderLayout bl = new BorderLayout();
488       bl.setHgap(0);
489       bl.setVgap(0);
490       scriptWindow = new JPanel(bl);
491       scriptWindow.setVisible(false);
492     }
493
494     jmb.allocateViewer(renderPanel, true, "", null, null, "", scriptWindow,
495             null);
496     // jmb.newJmolPopup("Jmol");
497     if (command == null)
498     {
499       command = "";
500     }
501     jmb.evalStateCommand(command);
502     jmb.setFinishedInit(true);
503   }
504
505   void setChainMenuItems(Vector<String> chains)
506   {
507     chainMenu.removeAll();
508     if (chains == null)
509     {
510       return;
511     }
512     JMenuItem menuItem = new JMenuItem(
513             MessageManager.getString("label.all"));
514     menuItem.addActionListener(new ActionListener()
515     {
516       public void actionPerformed(ActionEvent evt)
517       {
518         allChainsSelected = true;
519         for (int i = 0; i < chainMenu.getItemCount(); i++)
520         {
521           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
522           {
523             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
524           }
525         }
526         centerViewer();
527         allChainsSelected = false;
528       }
529     });
530
531     chainMenu.add(menuItem);
532
533     for (String chain : chains)
534     {
535       menuItem = new JCheckBoxMenuItem(chain, true);
536       menuItem.addItemListener(new ItemListener()
537       {
538         public void itemStateChanged(ItemEvent evt)
539         {
540           if (!allChainsSelected)
541           {
542             centerViewer();
543           }
544         }
545       });
546
547       chainMenu.add(menuItem);
548     }
549   }
550
551   boolean allChainsSelected = false;
552
553   private boolean alignAddedStructures = false;
554
555   void centerViewer()
556   {
557     Vector<String> toshow = new Vector<String>();
558     for (int i = 0; i < chainMenu.getItemCount(); i++)
559     {
560       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
561       {
562         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
563         if (item.isSelected())
564         {
565           toshow.addElement(item.getText());
566         }
567       }
568     }
569     jmb.centerViewer(toshow);
570   }
571
572   public void closeViewer(boolean closeExternalViewer)
573   {
574     // JMol does not use an external viewer
575     jmb.closeViewer();
576     setAlignmentPanel(null);
577     _aps.clear();
578     _alignwith.clear();
579     _colourwith.clear();
580     // TODO: check for memory leaks where instance isn't finalised because jmb
581     // holds a reference to the window
582     jmb = null;
583   }
584
585   /**
586    * state flag for PDB retrieval thread
587    */
588   private boolean _started = false;
589
590   public void run()
591   {
592     _started = true;
593     String pdbid = "";
594     // todo - record which pdbids were successfuly imported.
595     StringBuffer errormsgs = new StringBuffer(), files = new StringBuffer();
596     try
597     {
598       String[] curfiles = jmb.getPdbFile(); // files currently in viewer
599       // TODO: replace with reference fetching/transfer code (validate PDBentry
600       // as a DBRef?)
601       Pdb pdbclient = new Pdb();
602       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
603       {
604         String file = jmb.getPdbEntry(pi).getFile();
605         if (file == null)
606         {
607           // retrieve the pdb and store it locally
608           AlignmentI pdbseq = null;
609           pdbid = jmb.getPdbEntry(pi).getId();
610           long hdl = pdbid.hashCode() - System.currentTimeMillis();
611           if (progressBar != null)
612           {
613             progressBar.setProgressBar(MessageManager.formatMessage("status.fetching_pdb", new String[]{pdbid}), hdl);
614           }
615           try
616           {
617             pdbseq = pdbclient.getSequenceRecords(pdbid);
618           } catch (OutOfMemoryError oomerror)
619           {
620             new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
621           } catch (Exception ex)
622           {
623             ex.printStackTrace();
624             errormsgs.append("'" + pdbid + "'");
625           }
626           if (progressBar != null)
627           {
628             progressBar.setProgressBar(MessageManager.getString("label.state_completed"), hdl);
629           }
630           if (pdbseq != null)
631           {
632             // just transfer the file name from the first sequence's first
633             // PDBEntry
634             file = new File(pdbseq.getSequenceAt(0).getPDBId()
635                     .elementAt(0).getFile()).getAbsolutePath();
636             jmb.getPdbEntry(pi).setFile(file);
637
638             files.append(" \"" + Platform.escapeString(file) + "\"");
639           }
640           else
641           {
642             errormsgs.append("'" + pdbid + "' ");
643           }
644         }
645         else
646         {
647           if (curfiles != null && curfiles.length > 0)
648           {
649             addingStructures = true; // already files loaded.
650             for (int c = 0; c < curfiles.length; c++)
651             {
652               if (curfiles[c].equals(file))
653               {
654                 file = null;
655                 break;
656               }
657             }
658           }
659           if (file != null)
660           {
661             files.append(" \"" + Platform.escapeString(file) + "\"");
662           }
663         }
664       }
665     } catch (OutOfMemoryError oomerror)
666     {
667       new OOMWarning("Retrieving PDB files: " + pdbid, oomerror);
668     } catch (Exception ex)
669     {
670       ex.printStackTrace();
671       errormsgs.append("When retrieving pdbfiles : current was: '" + pdbid
672               + "'");
673     }
674     if (errormsgs.length() > 0)
675     {
676
677       JOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
678               .formatMessage("label.pdb_entries_couldnt_be_retrieved",
679                       new String[]
680                       { errormsgs.toString() }), MessageManager
681               .getString("label.couldnt_load_file"),
682               JOptionPane.ERROR_MESSAGE);
683
684     }
685     long lastnotify = jmb.getLoadNotifiesHandled();
686     if (files.length() > 0)
687     {
688       if (!addingStructures)
689       {
690
691         try
692         {
693           initJmol("load FILES " + files.toString());
694         } catch (OutOfMemoryError oomerror)
695         {
696           new OOMWarning("When trying to open the Jmol viewer!", oomerror);
697           Cache.log.debug("File locations are " + files);
698         } catch (Exception ex)
699         {
700           Cache.log.error("Couldn't open Jmol viewer!", ex);
701         }
702       }
703       else
704       {
705         StringBuffer cmd = new StringBuffer();
706         cmd.append("loadingJalviewdata=true\nload APPEND ");
707         cmd.append(files.toString());
708         cmd.append("\nloadingJalviewdata=null");
709         final String command = cmd.toString();
710         cmd = null;
711         lastnotify = jmb.getLoadNotifiesHandled();
712
713         try
714         {
715           jmb.evalStateCommand(command);
716         } catch (OutOfMemoryError oomerror)
717         {
718           new OOMWarning(
719                   "When trying to add structures to the Jmol viewer!",
720                   oomerror);
721           Cache.log.debug("File locations are " + files);
722         } catch (Exception ex)
723         {
724           Cache.log.error("Couldn't add files to Jmol viewer!", ex);
725         }
726       }
727
728       // need to wait around until script has finished
729       while (addingStructures ? lastnotify >= jmb.getLoadNotifiesHandled()
730               : (!jmb.isFinishedInit() && jmb.getPdbFile() != null && jmb
731                       .getPdbFile().length != jmb.getPdbCount()))
732       {
733         try
734         {
735           Cache.log.debug("Waiting around for jmb notify.");
736           Thread.sleep(35);
737         } catch (Exception e)
738         {
739         }
740       }
741
742       // refresh the sequence colours for the new structure(s)
743       for (AlignmentPanel ap : _colourwith)
744       {
745         jmb.updateColours(ap);
746       }
747       // do superposition if asked to
748       if (alignAddedStructures)
749       {
750         javax.swing.SwingUtilities.invokeLater(new Runnable()
751         {
752           public void run()
753           {
754             alignStructs_withAllAlignPanels();
755             // jmb.superposeStructures(ap.av.getAlignment(), -1, null);
756           }
757         });
758         alignAddedStructures = false;
759       }
760       addingStructures = false;
761
762     }
763     _started = false;
764     worker = null;
765   }
766
767   @Override
768   public void pdbFile_actionPerformed(ActionEvent actionEvent)
769   {
770     JalviewFileChooser chooser = new JalviewFileChooser(
771             Cache.getProperty("LAST_DIRECTORY"));
772
773     chooser.setFileView(new JalviewFileView());
774     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
775     chooser.setToolTipText(MessageManager.getString("action.save"));
776
777     int value = chooser.showSaveDialog(this);
778
779     if (value == JalviewFileChooser.APPROVE_OPTION)
780     {
781       BufferedReader in = null;
782       try
783       {
784         // TODO: cope with multiple PDB files in view
785         in = new BufferedReader(new FileReader(
786                 jmb.getPdbFile()[0]));
787         File outFile = chooser.getSelectedFile();
788
789         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
790         String data;
791         while ((data = in.readLine()) != null)
792         {
793           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
794           {
795             out.println(data);
796           }
797         }
798         out.close();
799       } catch (Exception ex)
800       {
801         ex.printStackTrace();
802       } finally
803       {
804         if (in != null)
805         {
806           try
807           {
808             in.close();
809           } catch (IOException e)
810           {
811             // ignore
812           }
813         }
814       }
815     }
816   }
817
818   @Override
819   public void viewMapping_actionPerformed(ActionEvent actionEvent)
820   {
821     CutAndPasteTransfer cap = new CutAndPasteTransfer();
822     try
823     {
824       cap.appendText(jmb.printMappings());
825     } catch (OutOfMemoryError e)
826     {
827       new OOMWarning(
828               "composing sequence-structure alignments for display in text box.",
829               e);
830       cap.dispose();
831       return;
832     }
833     Desktop.addInternalFrame(cap,
834             MessageManager.getString("label.pdb_sequence_mapping"), 550,
835             600);
836   }
837
838   @Override
839   public void eps_actionPerformed(ActionEvent e)
840   {
841     makePDBImage(ImageMaker.TYPE.EPS);
842   }
843
844   @Override
845   public void png_actionPerformed(ActionEvent e)
846   {
847     makePDBImage(ImageMaker.TYPE.PNG);
848   }
849
850   void makePDBImage(ImageMaker.TYPE type)
851   {
852     int width = getWidth();
853     int height = getHeight();
854
855     ImageMaker im;
856
857     if (type == ImageMaker.TYPE.PNG)
858     {
859       im = new ImageMaker(this, ImageMaker.TYPE.PNG,
860               "Make PNG image from view", width, height, null, null);
861     }
862     else if (type == ImageMaker.TYPE.EPS)
863     {
864       im = new ImageMaker(this, ImageMaker.TYPE.EPS,
865               "Make EPS file from view", width, height, null,
866               this.getTitle());
867     }
868     else
869     {
870
871       im = new ImageMaker(this, ImageMaker.TYPE.SVG,
872               "Make SVG file from PCA",
873               width, height, null, this.getTitle());
874     }
875
876     if (im.getGraphics() != null)
877     {
878       jmb.viewer.renderScreenImage(im.getGraphics(), width, height);
879       im.writeImage();
880     }
881   }
882
883   @Override
884   public void viewerColour_actionPerformed(ActionEvent actionEvent)
885   {
886     if (viewerColour.isSelected())
887     {
888       // disable automatic sequence colouring.
889       jmb.setColourBySequence(false);
890     }
891   }
892
893   @Override
894   public void seqColour_actionPerformed(ActionEvent actionEvent)
895   {
896     jmb.setColourBySequence(seqColour.isSelected());
897     if (_colourwith == null)
898     {
899       _colourwith = new Vector<AlignmentPanel>();
900     }
901     if (jmb.isColourBySequence())
902     {
903       if (!jmb.isLoadingFromArchive())
904       {
905         if (_colourwith.size() == 0 && getAlignmentPanel() != null)
906         {
907           // Make the currently displayed alignment panel the associated view
908           _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
909         }
910       }
911       // Set the colour using the current view for the associated alignframe
912       for (AlignmentPanel ap : _colourwith)
913       {
914         jmb.colourBySequence(ap);
915       }
916     }
917   }
918
919   @Override
920   public void chainColour_actionPerformed(ActionEvent actionEvent)
921   {
922     chainColour.setSelected(true);
923     jmb.colourByChain();
924   }
925
926   @Override
927   public void chargeColour_actionPerformed(ActionEvent actionEvent)
928   {
929     chargeColour.setSelected(true);
930     jmb.colourByCharge();
931   }
932
933   @Override
934   public void zappoColour_actionPerformed(ActionEvent actionEvent)
935   {
936     zappoColour.setSelected(true);
937     jmb.setJalviewColourScheme(new ZappoColourScheme());
938   }
939
940   @Override
941   public void taylorColour_actionPerformed(ActionEvent actionEvent)
942   {
943     taylorColour.setSelected(true);
944     jmb.setJalviewColourScheme(new TaylorColourScheme());
945   }
946
947   @Override
948   public void hydroColour_actionPerformed(ActionEvent actionEvent)
949   {
950     hydroColour.setSelected(true);
951     jmb.setJalviewColourScheme(new HydrophobicColourScheme());
952   }
953
954   @Override
955   public void helixColour_actionPerformed(ActionEvent actionEvent)
956   {
957     helixColour.setSelected(true);
958     jmb.setJalviewColourScheme(new HelixColourScheme());
959   }
960
961   @Override
962   public void strandColour_actionPerformed(ActionEvent actionEvent)
963   {
964     strandColour.setSelected(true);
965     jmb.setJalviewColourScheme(new StrandColourScheme());
966   }
967
968   @Override
969   public void turnColour_actionPerformed(ActionEvent actionEvent)
970   {
971     turnColour.setSelected(true);
972     jmb.setJalviewColourScheme(new TurnColourScheme());
973   }
974
975   @Override
976   public void buriedColour_actionPerformed(ActionEvent actionEvent)
977   {
978     buriedColour.setSelected(true);
979     jmb.setJalviewColourScheme(new BuriedColourScheme());
980   }
981
982   @Override
983   public void purinePyrimidineColour_actionPerformed(ActionEvent actionEvent)
984   {
985     setJalviewColourScheme(new PurinePyrimidineColourScheme());
986   }
987
988   @Override
989   public void userColour_actionPerformed(ActionEvent actionEvent)
990   {
991     userColour.setSelected(true);
992     new UserDefinedColours(this, null);
993   }
994
995   @Override
996   public void backGround_actionPerformed(ActionEvent actionEvent)
997   {
998     java.awt.Color col = JColorChooser.showDialog(this,
999             MessageManager.getString("label.select_backgroud_colour"), null);
1000     if (col != null)
1001     {
1002       jmb.setBackgroundColour(col);
1003     }
1004   }
1005
1006   @Override
1007   public void showHelp_actionPerformed(ActionEvent actionEvent)
1008   {
1009     try
1010     {
1011       BrowserLauncher
1012               .openURL("http://jmol.sourceforge.net/docs/JmolUserGuide/");
1013     } catch (Exception ex)
1014     {
1015     }
1016   }
1017
1018   public void showConsole(boolean showConsole)
1019   {
1020
1021     if (showConsole)
1022     {
1023       if (splitPane == null)
1024       {
1025         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
1026         splitPane.setTopComponent(renderPanel);
1027         splitPane.setBottomComponent(scriptWindow);
1028         this.getContentPane().add(splitPane, BorderLayout.CENTER);
1029         splitPane.setDividerLocation(getHeight() - 200);
1030         scriptWindow.setVisible(true);
1031         scriptWindow.validate();
1032         splitPane.validate();
1033       }
1034
1035     }
1036     else
1037     {
1038       if (splitPane != null)
1039       {
1040         splitPane.setVisible(false);
1041       }
1042
1043       splitPane = null;
1044
1045       this.getContentPane().add(renderPanel, BorderLayout.CENTER);
1046     }
1047
1048     validate();
1049   }
1050
1051   class RenderPanel extends JPanel
1052   {
1053     final Dimension currentSize = new Dimension();
1054
1055     @Override
1056     public void paintComponent(Graphics g)
1057     {
1058       getSize(currentSize);
1059
1060       if (jmb != null && jmb.fileLoadingError != null)
1061       {
1062         g.setColor(Color.black);
1063         g.fillRect(0, 0, currentSize.width, currentSize.height);
1064         g.setColor(Color.white);
1065         g.setFont(new Font("Verdana", Font.BOLD, 14));
1066         g.drawString(MessageManager.getString("label.error_loading_file")
1067                 + "...", 20, currentSize.height / 2);
1068         StringBuffer sb = new StringBuffer();
1069         int lines = 0;
1070         for (int e = 0; e < jmb.getPdbCount(); e++)
1071         {
1072           sb.append(jmb.getPdbEntry(e).getId());
1073           if (e < jmb.getPdbCount() - 1)
1074           {
1075             sb.append(",");
1076           }
1077
1078           if (e == jmb.getPdbCount() - 1 || sb.length() > 20)
1079           {
1080             lines++;
1081             g.drawString(sb.toString(), 20, currentSize.height / 2 - lines
1082                     * g.getFontMetrics().getHeight());
1083           }
1084         }
1085       }
1086       else if (jmb == null || jmb.viewer == null || !jmb.isFinishedInit())
1087       {
1088         g.setColor(Color.black);
1089         g.fillRect(0, 0, currentSize.width, currentSize.height);
1090         g.setColor(Color.white);
1091         g.setFont(new Font("Verdana", Font.BOLD, 14));
1092         g.drawString(MessageManager.getString("label.retrieving_pdb_data"),
1093                 20, currentSize.height / 2);
1094       }
1095       else
1096       {
1097         jmb.viewer.renderScreenImage(g, currentSize.width,
1098                 currentSize.height);
1099       }
1100     }
1101   }
1102
1103   public void updateTitleAndMenus()
1104   {
1105     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
1106     {
1107       repaint();
1108       return;
1109     }
1110     setChainMenuItems(jmb.chainNames);
1111
1112     this.setTitle(jmb.getViewerTitle());
1113     if (jmb.getPdbFile().length > 1 && jmb.getSequence().length > 1)
1114     {
1115       viewerActionMenu.setVisible(true);
1116     }
1117     if (!jmb.isLoadingFromArchive())
1118     {
1119       seqColour_actionPerformed(null);
1120     }
1121   }
1122
1123   /*
1124    * (non-Javadoc)
1125    * 
1126    * @see
1127    * jalview.jbgui.GStructureViewer#alignStructs_actionPerformed(java.awt.event
1128    * .ActionEvent)
1129    */
1130   @Override
1131   protected void alignStructs_actionPerformed(ActionEvent actionEvent)
1132   {
1133     alignStructs_withAllAlignPanels();
1134   }
1135
1136   private void alignStructs_withAllAlignPanels()
1137   {
1138     if (getAlignmentPanel() == null)
1139     {
1140       return;
1141     }
1142     ;
1143     if (_alignwith.size() == 0)
1144     {
1145       _alignwith.add(getAlignmentPanel());
1146     }
1147     ;
1148     try
1149     {
1150       AlignmentI[] als = new Alignment[_alignwith.size()];
1151       ColumnSelection[] alc = new ColumnSelection[_alignwith.size()];
1152       int[] alm = new int[_alignwith.size()];
1153       int a = 0;
1154
1155       for (AlignmentPanel ap : _alignwith)
1156       {
1157         als[a] = ap.av.getAlignment();
1158         alm[a] = -1;
1159         alc[a++] = ap.av.getColumnSelection();
1160       }
1161       jmb.superposeStructures(als, alm, alc);
1162     } catch (Exception e)
1163     {
1164       StringBuffer sp = new StringBuffer();
1165       for (AlignmentPanel ap : _alignwith)
1166       {
1167         sp.append("'" + ap.alignFrame.getTitle() + "' ");
1168       }
1169       Cache.log.info("Couldn't align structures with the " + sp.toString()
1170               + "associated alignment panels.", e);
1171
1172     }
1173
1174   }
1175
1176   public void setJalviewColourScheme(ColourSchemeI ucs)
1177   {
1178     jmb.setJalviewColourScheme(ucs);
1179
1180   }
1181
1182   /**
1183    * 
1184    * @param alignment
1185    * @return first alignment panel displaying given alignment, or the default
1186    *         alignment panel
1187    */
1188   public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment)
1189   {
1190     for (AlignmentPanel ap : getAllAlignmentPanels())
1191     {
1192       if (ap.av.getAlignment() == alignment)
1193       {
1194         return ap;
1195       }
1196     }
1197     return getAlignmentPanel();
1198   }
1199
1200   @Override
1201   public AAStructureBindingModel getBinding()
1202   {
1203     return this.jmb;
1204   }
1205
1206   @Override
1207   public String getStateInfo()
1208   {
1209     return jmb == null ? null : jmb.viewer.getStateInfo();
1210   }
1211
1212   @Override
1213   public ViewerType getViewerType()
1214   {
1215     return ViewerType.JMOL;
1216   }
1217
1218 }