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