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