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