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