JAL-3829 JAL-3855 moved routing for retrieving structures prior to opening a Jmol...
[jalview.git] / src / jalview / gui / StructureViewerBase.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import java.awt.Color;
24 import java.awt.Component;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.ActionListener;
27 import java.awt.event.ItemEvent;
28 import java.awt.event.ItemListener;
29 import java.io.BufferedReader;
30 import java.io.File;
31 import java.io.FileOutputStream;
32 import java.io.FileReader;
33 import java.io.IOException;
34 import java.io.PrintWriter;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Random;
38 import java.util.Vector;
39
40 import javax.swing.ButtonGroup;
41 import javax.swing.JCheckBoxMenuItem;
42 import javax.swing.JMenu;
43 import javax.swing.JMenuItem;
44 import javax.swing.JRadioButtonMenuItem;
45 import javax.swing.event.MenuEvent;
46 import javax.swing.event.MenuListener;
47
48 import jalview.api.AlignmentViewPanel;
49 import jalview.bin.Cache;
50 import jalview.datamodel.AlignmentI;
51 import jalview.datamodel.PDBEntry;
52 import jalview.datamodel.SequenceI;
53 import jalview.gui.JalviewColourChooser.ColourChooserListener;
54 import jalview.gui.StructureViewer.ViewerType;
55 import jalview.gui.ViewSelectionMenu.ViewSetProvider;
56 import jalview.io.DataSourceType;
57 import jalview.io.JalviewFileChooser;
58 import jalview.io.JalviewFileView;
59 import jalview.jbgui.GStructureViewer;
60 import jalview.schemes.ColourSchemeI;
61 import jalview.schemes.ColourSchemes;
62 import jalview.structure.StructureMapping;
63 import jalview.structures.models.AAStructureBindingModel;
64 import jalview.util.BrowserLauncher;
65 import jalview.util.MessageManager;
66 import jalview.ws.dbsources.EBIAlfaFold;
67 import jalview.ws.dbsources.Pdb;
68
69 /**
70  * Base class with common functionality for JMol, Chimera or other structure
71  * viewers.
72  * 
73  * @author gmcarstairs
74  *
75  */
76 public abstract class StructureViewerBase extends GStructureViewer
77         implements Runnable, ViewSetProvider
78 {
79   /*
80    * names for colour options (additional to Jalview colour schemes)
81    */
82   enum ViewerColour
83   {
84     BySequence, ByChain, ChargeCysteine, ByViewer
85   }
86
87   /**
88    * list of sequenceSet ids associated with the view
89    */
90   protected List<String> _aps = new ArrayList<>();
91
92   /**
93    * list of alignment panels to use for superposition
94    */
95   protected Vector<AlignmentViewPanel> _alignwith = new Vector<>();
96
97   /**
98    * list of alignment panels that are used for colouring structures by aligned
99    * sequences
100    */
101   protected Vector<AlignmentViewPanel> _colourwith = new Vector<>();
102
103   private String viewId = null;
104
105   private AlignmentPanel ap;
106
107   protected boolean alignAddedStructures = false;
108
109   protected volatile boolean _started = false;
110
111   protected volatile boolean addingStructures = false;
112
113   protected Thread worker = null;
114
115   protected boolean allChainsSelected = false;
116
117   protected JMenu viewSelectionMenu;
118
119   /**
120    * set after sequence colouring has been applied for this structure viewer.
121    * used to determine if the final sequence/structure mapping has been
122    * determined
123    */
124   protected volatile boolean seqColoursApplied = false;
125
126   private IProgressIndicator progressBar = null;
127
128   private Random random = new Random();
129
130   /**
131    * Default constructor
132    */
133   public StructureViewerBase()
134   {
135     super();
136   }
137
138   /**
139    * @return true if added structures should be aligned to existing one(s)
140    */
141   @Override
142   public boolean isAlignAddedStructures()
143   {
144     return alignAddedStructures;
145   }
146
147   /**
148    * 
149    * @param true
150    *          if added structures should be aligned to existing one(s)
151    */
152   @Override
153   public void setAlignAddedStructures(boolean alignAdded)
154   {
155     alignAddedStructures = alignAdded;
156   }
157   
158   /**
159    * called by the binding model to indicate when adding structures is happening or has been completed
160    * @param addingStructures
161    */
162   public synchronized void setAddingStructures(boolean addingStructures)
163   {
164     this.addingStructures = addingStructures;
165   }
166
167   /**
168    * 
169    * @param ap2
170    * @return true if this Jmol instance is linked with the given alignPanel
171    */
172   public boolean isLinkedWith(AlignmentPanel ap2)
173   {
174     return _aps.contains(ap2.av.getSequenceSetId());
175   }
176
177   public boolean isUsedforaligment(AlignmentViewPanel ap2)
178   {
179
180     return (_alignwith != null) && _alignwith.contains(ap2);
181   }
182
183   @Override
184   public boolean isUsedForColourBy(AlignmentViewPanel ap2)
185   {
186     return (_colourwith != null) && _colourwith.contains(ap2);
187   }
188
189   /**
190    * 
191    * @return TRUE if the view is NOT being coloured by the alignment colours.
192    */
193   public boolean isColouredByViewer()
194   {
195     return !getBinding().isColourBySequence();
196   }
197
198   public String getViewId()
199   {
200     if (viewId == null)
201     {
202       viewId = System.currentTimeMillis() + "." + this.hashCode();
203     }
204     return viewId;
205   }
206
207   protected void setViewId(String viewId)
208   {
209     this.viewId = viewId;
210   }
211
212   protected void buildActionMenu()
213   {
214     if (_alignwith == null)
215     {
216       _alignwith = new Vector<>();
217     }
218     if (_alignwith.size() == 0 && ap != null)
219     {
220       _alignwith.add(ap);
221     }
222     ;
223     for (Component c : viewerActionMenu.getMenuComponents())
224     {
225       if (c != alignStructs)
226       {
227         viewerActionMenu.remove((JMenuItem) c);
228       }
229     }
230   }
231
232   @Override
233   public AlignmentPanel getAlignmentPanel()
234   {
235     return ap;
236   }
237
238   protected void setAlignmentPanel(AlignmentPanel alp)
239   {
240     this.ap = alp;
241   }
242
243   @Override
244   public AlignmentPanel[] getAllAlignmentPanels()
245   {
246     AlignmentPanel[] t, list = new AlignmentPanel[0];
247     for (String setid : _aps)
248     {
249       AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
250       if (panels != null)
251       {
252         t = new AlignmentPanel[list.length + panels.length];
253         System.arraycopy(list, 0, t, 0, list.length);
254         System.arraycopy(panels, 0, t, list.length, panels.length);
255         list = t;
256       }
257     }
258
259     return list;
260   }
261
262   /**
263    * set the primary alignmentPanel reference and add another alignPanel to the
264    * list of ones to use for colouring and aligning
265    * 
266    * @param nap
267    */
268   public void addAlignmentPanel(AlignmentPanel nap)
269   {
270     if (getAlignmentPanel() == null)
271     {
272       setAlignmentPanel(nap);
273     }
274     if (!_aps.contains(nap.av.getSequenceSetId()))
275     {
276       _aps.add(nap.av.getSequenceSetId());
277     }
278   }
279
280   /**
281    * remove any references held to the given alignment panel
282    * 
283    * @param nap
284    */
285   @Override
286   public void removeAlignmentPanel(AlignmentViewPanel nap)
287   {
288     try
289     {
290       _alignwith.remove(nap);
291       _colourwith.remove(nap);
292       if (getAlignmentPanel() == nap)
293       {
294         setAlignmentPanel(null);
295         for (AlignmentPanel aps : getAllAlignmentPanels())
296         {
297           if (aps != nap)
298           {
299             setAlignmentPanel(aps);
300             break;
301           }
302         }
303       }
304     } catch (Exception ex)
305     {
306     }
307     if (getAlignmentPanel() != null)
308     {
309       buildActionMenu();
310     }
311   }
312
313   public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
314   {
315     addAlignmentPanel(nap);
316     if (!_alignwith.contains(nap))
317     {
318       _alignwith.add(nap);
319     }
320   }
321
322   public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
323   {
324     if (_alignwith.contains(nap))
325     {
326       _alignwith.remove(nap);
327     }
328   }
329
330   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
331           boolean enableColourBySeq)
332   {
333     useAlignmentPanelForColourbyseq(nap);
334     getBinding().setColourBySequence(enableColourBySeq);
335     seqColour.setSelected(enableColourBySeq);
336     viewerColour.setSelected(!enableColourBySeq);
337   }
338
339   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
340   {
341     addAlignmentPanel(nap);
342     if (!_colourwith.contains(nap))
343     {
344       _colourwith.add(nap);
345     }
346   }
347
348   public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
349   {
350     if (_colourwith.contains(nap))
351     {
352       _colourwith.remove(nap);
353     }
354   }
355
356   public abstract ViewerType getViewerType();
357
358   /**
359    * add a new structure (with associated sequences and chains) to this viewer,
360    * retrieving it if necessary first.
361    * 
362    * @param pdbentry
363    * @param seqs
364    * @param chains
365    * @param align
366    *          if true, new structure(s) will be aligned using associated
367    *          alignment
368    * @param alignFrame
369    */
370   protected void addStructure(final PDBEntry pdbentry,
371           final SequenceI[] seqs, final String[] chains,
372           final IProgressIndicator alignFrame)
373   {
374     if (pdbentry.getFile() == null)
375     {
376       if (worker != null && worker.isAlive())
377       {
378         // a retrieval is in progress, wait around and add ourselves to the
379         // queue.
380         new Thread(new Runnable()
381         {
382           @Override
383           public void run()
384           {
385             while (worker != null && worker.isAlive() && _started)
386             {
387               try
388               {
389                 Thread.sleep(100 + ((int) Math.random() * 100));
390
391               } catch (Exception e)
392               {
393               }
394             }
395             // and call ourselves again.
396             addStructure(pdbentry, seqs, chains, alignFrame);
397           }
398         }).start();
399         return;
400       }
401     }
402     // otherwise, start adding the structure.
403     getBinding().addSequenceAndChain(new PDBEntry[] { pdbentry },
404             new SequenceI[][]
405             { seqs }, new String[][] { chains });
406     addingStructures = true;
407     _started = false;
408     worker = new Thread(this);
409     worker.start();
410     return;
411   }
412
413   protected boolean hasPdbId(String pdbId)
414   {
415     return getBinding().hasPdbId(pdbId);
416   }
417
418   /**
419    * Returns a list of any viewer of the instantiated type. The list is
420    * restricted to those linked to the given alignment panel if it is not null.
421    */
422   protected List<StructureViewerBase> getViewersFor(AlignmentPanel alp)
423   {
424     return Desktop.instance.getStructureViewers(alp, this.getClass());
425   }
426
427   @Override
428   public void addToExistingViewer(PDBEntry pdbentry, SequenceI[] seq,
429           String[] chains, final AlignmentViewPanel apanel, String pdbId)
430   {
431     /*
432      * JAL-1742 exclude view with this structure already mapped (don't offer
433      * to align chain B to chain A of the same structure); code may defend
434      * against this possibility before we reach here
435      */
436     if (hasPdbId(pdbId))
437     {
438       return;
439     }
440     AlignmentPanel alignPanel = (AlignmentPanel) apanel; // Implementation error if this
441                                                  // cast fails
442     useAlignmentPanelForSuperposition(alignPanel);
443     addStructure(pdbentry, seq, chains, alignPanel.alignFrame);
444   }
445
446   /**
447    * Adds mappings for the given sequences to an already opened PDB structure,
448    * and updates any viewers that have the PDB file
449    * 
450    * @param seq
451    * @param chains
452    * @param apanel
453    * @param pdbFilename
454    */
455   public void addSequenceMappingsToStructure(SequenceI[] seq,
456           String[] chains, final AlignmentViewPanel alpanel,
457           String pdbFilename)
458   {
459     AlignmentPanel apanel = (AlignmentPanel) alpanel;
460
461     // TODO : Fix multiple seq to one chain issue here.
462     /*
463      * create the mappings
464      */
465     apanel.getStructureSelectionManager().setMapping(seq, chains,
466             pdbFilename, DataSourceType.FILE, getProgressIndicator());
467
468     /*
469      * alert the FeatureRenderer to show new (PDB RESNUM) features
470      */
471     if (apanel.getSeqPanel().seqCanvas.fr != null)
472     {
473       apanel.getSeqPanel().seqCanvas.fr.featuresAdded();
474       // note - we don't do a refresh for structure here because we do it
475       // explicitly for all panels later on
476       apanel.paintAlignment(true, false);
477     }
478
479     /*
480      * add the sequences to any other viewers (of the same type) for this pdb
481      * file
482      */
483     // JBPNOTE: this looks like a binding routine, rather than a gui routine
484     for (StructureViewerBase viewer : getViewersFor(null))
485     {
486       AAStructureBindingModel bindingModel = viewer.getBinding();
487       for (int pe = 0; pe < bindingModel.getPdbCount(); pe++)
488       {
489         if (bindingModel.getPdbEntry(pe).getFile().equals(pdbFilename))
490         {
491           bindingModel.addSequence(pe, seq);
492           viewer.addAlignmentPanel(apanel);
493           /*
494            * add it to the set of alignments used for colouring structure by
495            * sequence
496            */
497           viewer.useAlignmentPanelForColourbyseq(apanel);
498           viewer.buildActionMenu();
499           apanel.getStructureSelectionManager()
500                   .sequenceColoursChanged(apanel);
501           break;
502         }
503       }
504     }
505   }
506
507   @Override
508   public boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains,
509           final AlignmentViewPanel apanel, String pdbId)
510   {
511     String alreadyMapped = apanel.getStructureSelectionManager()
512             .alreadyMappedToFile(pdbId);
513
514     if (alreadyMapped == null)
515     {
516       return false;
517     }
518
519     addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped);
520     return true;
521   }
522
523   void setChainMenuItems(List<String> chainNames)
524   {
525     chainMenu.removeAll();
526     if (chainNames == null || chainNames.isEmpty())
527     {
528       return;
529     }
530     JMenuItem menuItem = new JMenuItem(
531             MessageManager.getString("label.all"));
532     menuItem.addActionListener(new ActionListener()
533     {
534       @Override
535       public void actionPerformed(ActionEvent evt)
536       {
537         allChainsSelected = true;
538         for (int i = 0; i < chainMenu.getItemCount(); i++)
539         {
540           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
541           {
542             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
543           }
544         }
545         showSelectedChains();
546         allChainsSelected = false;
547       }
548     });
549
550     chainMenu.add(menuItem);
551
552     for (String chain : chainNames)
553     {
554       menuItem = new JCheckBoxMenuItem(chain, true);
555       menuItem.addItemListener(new ItemListener()
556       {
557         @Override
558         public void itemStateChanged(ItemEvent evt)
559         {
560           if (!allChainsSelected)
561           {
562             showSelectedChains();
563           }
564         }
565       });
566
567       chainMenu.add(menuItem);
568     }
569   }
570
571   /**
572    * Action on selecting one of Jalview's registered colour schemes
573    */
574   @Override
575   public void changeColour_actionPerformed(String colourSchemeName)
576   {
577     AlignmentI al = getAlignmentPanel().av.getAlignment();
578     ColourSchemeI cs = ColourSchemes.getInstance()
579             .getColourScheme(colourSchemeName, getAlignmentPanel().av, al,
580                     null);
581     getBinding().colourByJalviewColourScheme(cs);
582   }
583
584   /**
585    * Builds the colour menu
586    */
587   protected void buildColourMenu()
588   {
589     colourMenu.removeAll();
590     AlignmentI al = getAlignmentPanel().av.getAlignment();
591
592     /*
593      * add colour by sequence, by chain, by charge and cysteine
594      */
595     colourMenu.add(seqColour);
596     colourMenu.add(chainColour);
597     colourMenu.add(chargeColour);
598     chargeColour.setEnabled(!al.isNucleotide());
599
600     /*
601      * add all 'simple' (per-residue) colour schemes registered to Jalview
602      */
603     ButtonGroup itemGroup = ColourMenuHelper.addMenuItems(colourMenu, this,
604             al, true);
605
606     /*
607      * add 'colour by viewer' (menu item text is set in subclasses)
608      */
609     viewerColour.setSelected(false);
610     viewerColour.addActionListener(new ActionListener()
611     {
612       @Override
613       public void actionPerformed(ActionEvent actionEvent)
614       {
615         viewerColour_actionPerformed();
616       }
617     });
618     colourMenu.add(viewerColour);
619
620     /*
621      * add 'set background colour'
622      */
623     JMenuItem backGround = new JMenuItem();
624     backGround
625             .setText(MessageManager.getString("action.background_colour"));
626     backGround.addActionListener(new ActionListener()
627     {
628       @Override
629       public void actionPerformed(ActionEvent actionEvent)
630       {
631         background_actionPerformed();
632       }
633     });
634     colourMenu.add(backGround);
635
636     /*
637      * add colour buttons to a group so their selection is
638      * mutually exclusive (background colour is a separate option)
639      */
640     itemGroup.add(seqColour);
641     itemGroup.add(chainColour);
642     itemGroup.add(chargeColour);
643     itemGroup.add(viewerColour);
644   }
645
646   /**
647    * Construct menu items
648    */
649   protected void initMenus()
650   {
651     AAStructureBindingModel binding = getBinding();
652
653     seqColour = new JRadioButtonMenuItem();
654     seqColour.setText(MessageManager.getString("action.by_sequence"));
655     seqColour.setName(ViewerColour.BySequence.name());
656     seqColour.setSelected(binding.isColourBySequence());
657     seqColour.addActionListener(new ActionListener()
658     {
659       @Override
660       public void actionPerformed(ActionEvent actionEvent)
661       {
662         seqColour_actionPerformed();
663       }
664     });
665
666     chainColour = new JRadioButtonMenuItem();
667     chainColour.setText(MessageManager.getString("action.by_chain"));
668     chainColour.setName(ViewerColour.ByChain.name());
669     chainColour.addActionListener(new ActionListener()
670     {
671       @Override
672       public void actionPerformed(ActionEvent actionEvent)
673       {
674         chainColour_actionPerformed();
675       }
676     });
677
678     chargeColour = new JRadioButtonMenuItem();
679     chargeColour.setText(MessageManager.getString("label.charge_cysteine"));
680     chargeColour.setName(ViewerColour.ChargeCysteine.name());
681     chargeColour.addActionListener(new ActionListener()
682     {
683       @Override
684       public void actionPerformed(ActionEvent actionEvent)
685       {
686         chargeColour_actionPerformed();
687       }
688     });
689
690     viewerColour = new JRadioButtonMenuItem();
691     viewerColour
692             .setText(MessageManager.getString("label.colour_with_viewer"));
693     viewerColour.setToolTipText(MessageManager
694             .getString("label.let_viewer_manage_structure_colours"));
695     viewerColour.setName(ViewerColour.ByViewer.name());
696     viewerColour.setSelected(!binding.isColourBySequence());
697
698     if (_colourwith == null)
699     {
700       _colourwith = new Vector<>();
701     }
702     if (_alignwith == null)
703     {
704       _alignwith = new Vector<>();
705     }
706
707     ViewSelectionMenu seqColourBy = new ViewSelectionMenu(
708             MessageManager.getString("label.colour_by"), this, _colourwith,
709             new ItemListener()
710             {
711               @Override
712               public void itemStateChanged(ItemEvent e)
713               {
714                 if (!seqColour.isSelected())
715                 {
716                   seqColour.doClick();
717                 }
718                 else
719                 {
720                   // update the viewer display now.
721                   seqColour_actionPerformed();
722                 }
723               }
724             });
725     viewMenu.add(seqColourBy);
726
727     final ItemListener handler = new ItemListener()
728     {
729       @Override
730       public void itemStateChanged(ItemEvent e)
731       {
732         if (_alignwith.isEmpty())
733         {
734           alignStructs.setEnabled(false);
735           alignStructs.setToolTipText(null);
736         }
737         else
738         {
739           alignStructs.setEnabled(true);
740           alignStructs.setToolTipText(MessageManager.formatMessage(
741                   "label.align_structures_using_linked_alignment_views",
742                   _alignwith.size()));
743         }
744       }
745     };
746     viewSelectionMenu = new ViewSelectionMenu(
747             MessageManager.getString("label.superpose_with"), this,
748             _alignwith, handler);
749     handler.itemStateChanged(null);
750     viewerActionMenu.add(viewSelectionMenu, 0);
751     viewerActionMenu.addMenuListener(new MenuListener()
752     {
753       @Override
754       public void menuSelected(MenuEvent e)
755       {
756         handler.itemStateChanged(null);
757       }
758
759       @Override
760       public void menuDeselected(MenuEvent e)
761       {
762       }
763
764       @Override
765       public void menuCanceled(MenuEvent e)
766       {
767       }
768     });
769
770     viewerActionMenu.setText(getViewerName());
771     helpItem.setText(MessageManager.formatMessage("label.viewer_help",
772             getViewerName()));
773
774     buildColourMenu();
775   }
776
777   /**
778    * Sends commands to the structure viewer to superimpose structures based on
779    * currently associated alignments. May optionally return an error message for
780    * the operation.
781    */
782   @Override
783   protected String alignStructsWithAllAlignPanels()
784   {
785     if (getAlignmentPanel() == null)
786     {
787       return null;
788     }
789
790     if (_alignwith.size() == 0)
791     {
792       _alignwith.add(getAlignmentPanel());
793     }
794
795     String reply = null;
796     try
797     {
798       reply = getBinding().superposeStructures(_alignwith);
799       if (reply != null && !reply.isEmpty())
800       {
801         String text = MessageManager
802                 .formatMessage("error.superposition_failed", reply);
803         statusBar.setText(text);
804       }
805     } catch (Exception e)
806     {
807       StringBuffer sp = new StringBuffer();
808       for (AlignmentViewPanel alignPanel : _alignwith)
809       {
810         sp.append("'" + alignPanel.getViewName() + "' ");
811       }
812       Cache.log.info("Couldn't align structures with the " + sp.toString()
813               + "associated alignment panels.", e);
814     }
815     return reply;
816   }
817
818   /**
819    * Opens a colour chooser dialog, and applies the chosen colour to the
820    * background of the structure viewer
821    */
822   @Override
823   public void background_actionPerformed()
824   {
825     String ttl = MessageManager.getString("label.select_background_colour");
826     ColourChooserListener listener = new ColourChooserListener()
827     {
828       @Override
829       public void colourSelected(Color c)
830       {
831         getBinding().setBackgroundColour(c);
832       }
833     };
834     JalviewColourChooser.showColourChooser(this, ttl, null, listener);
835   }
836
837   @Override
838   public void viewerColour_actionPerformed()
839   {
840     if (viewerColour.isSelected())
841     {
842       // disable automatic sequence colouring.
843       getBinding().setColourBySequence(false);
844     }
845   }
846
847   @Override
848   public void chainColour_actionPerformed()
849   {
850     chainColour.setSelected(true);
851     getBinding().colourByChain();
852   }
853
854   @Override
855   public void chargeColour_actionPerformed()
856   {
857     chargeColour.setSelected(true);
858     getBinding().colourByCharge();
859   }
860
861   @Override
862   public void seqColour_actionPerformed()
863   {
864     AAStructureBindingModel binding = getBinding();
865     binding.setColourBySequence(seqColour.isSelected());
866     if (_colourwith == null)
867     {
868       _colourwith = new Vector<>();
869     }
870     if (binding.isColourBySequence())
871     {
872       if (!binding.isLoadingFromArchive())
873       {
874         if (_colourwith.size() == 0 && getAlignmentPanel() != null)
875         {
876           // Make the currently displayed alignment panel the associated view
877           _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
878         }
879       }
880       // Set the colour using the current view for the associated alignframe
881       for (AlignmentViewPanel alignPanel : _colourwith)
882       {
883         binding.colourBySequence(alignPanel);
884       }
885       seqColoursApplied = true;
886     }
887   }
888
889   @Override
890   public void pdbFile_actionPerformed()
891   {
892     // TODO: JAL-3048 not needed for Jalview-JS - save PDB file
893     JalviewFileChooser chooser = new JalviewFileChooser(
894             Cache.getProperty("LAST_DIRECTORY"));
895
896     chooser.setFileView(new JalviewFileView());
897     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
898     chooser.setToolTipText(MessageManager.getString("action.save"));
899
900     int value = chooser.showSaveDialog(this);
901
902     if (value == JalviewFileChooser.APPROVE_OPTION)
903     {
904       BufferedReader in = null;
905       try
906       {
907         // TODO: cope with multiple PDB files in view
908         in = new BufferedReader(
909                 new FileReader(getBinding().getStructureFiles()[0]));
910         File outFile = chooser.getSelectedFile();
911
912         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
913         String data;
914         while ((data = in.readLine()) != null)
915         {
916           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
917           {
918             out.println(data);
919           }
920         }
921         out.close();
922       } catch (Exception ex)
923       {
924         ex.printStackTrace();
925       } finally
926       {
927         if (in != null)
928         {
929           try
930           {
931             in.close();
932           } catch (IOException e)
933           {
934             // ignore
935           }
936         }
937       }
938     }
939   }
940
941   @Override
942   public void viewMapping_actionPerformed()
943   {
944     CutAndPasteTransfer cap = new CutAndPasteTransfer();
945     try
946     {
947       cap.appendText(getBinding().printMappings());
948     } catch (OutOfMemoryError e)
949     {
950       new OOMWarning(
951               "composing sequence-structure alignments for display in text box.",
952               e);
953       cap.dispose();
954       return;
955     }
956     Desktop.addInternalFrame(cap,
957             MessageManager.getString("label.pdb_sequence_mapping"), 550,
958             600);
959   }
960
961   protected abstract String getViewerName();
962
963   /**
964    * Configures the title and menu items of the viewer panel.
965    */
966   @Override
967   public void updateTitleAndMenus()
968   {
969     AAStructureBindingModel binding = getBinding();
970     if (binding.hasFileLoadingError())
971     {
972       repaint();
973       return;
974     }
975     setChainMenuItems(binding.getChainNames());
976
977     this.setTitle(binding.getViewerTitle(getViewerName(), true));
978
979     /*
980      * enable 'Superpose with' if more than one mapped structure
981      */
982     viewSelectionMenu.setEnabled(false);
983     if (getBinding().getMappedStructureCount() > 1
984             && getBinding().getSequence().length > 1)
985     {
986       viewSelectionMenu.setEnabled(true);
987     }
988
989     /*
990      * Show action menu if it has any enabled items
991      */
992     viewerActionMenu.setVisible(false);
993     for (int i = 0; i < viewerActionMenu.getItemCount(); i++)
994     {
995       if (viewerActionMenu.getItem(i).isEnabled())
996       {
997         viewerActionMenu.setVisible(true);
998         break;
999       }
1000     }
1001
1002     if (!binding.isLoadingFromArchive())
1003     {
1004       seqColour_actionPerformed();
1005     }
1006   }
1007
1008   @Override
1009   public String toString()
1010   {
1011     return getTitle();
1012   }
1013
1014   @Override
1015   public boolean hasMapping()
1016   {
1017     if (worker != null && (addingStructures || _started))
1018     {
1019       return false;
1020     }
1021     if (getBinding() == null)
1022     {
1023       if (_aps == null || _aps.size() == 0)
1024       {
1025         // viewer has been closed, but we did at some point run.
1026         return true;
1027       }
1028       return false;
1029     }
1030     String[] pdbids = getBinding().getStructureFiles();
1031     if (pdbids == null)
1032     {
1033       return false;
1034     }
1035     int p=0;
1036     for (String pdbid:pdbids) {
1037       StructureMapping sm[] = getBinding().getSsm().getMapping(pdbid);
1038       if (sm!=null && sm.length>0 && sm[0]!=null) {
1039         p++;
1040       }
1041     }
1042     // only return true if there is a mapping for every structure file we have loaded
1043     if (p == 0 || p != pdbids.length)
1044     {
1045       return false;
1046     }
1047     // and that coloring has been applied
1048     return seqColoursApplied;
1049   }
1050
1051   @Override
1052   public void raiseViewer()
1053   {
1054     toFront();
1055   }
1056
1057   @Override
1058   public long startProgressBar(String msg)
1059   {
1060     // TODO would rather have startProgress/stopProgress as the
1061     // IProgressIndicator interface
1062     long tm = random.nextLong();
1063     if (progressBar != null)
1064     {
1065       progressBar.setProgressBar(msg, tm);
1066     }
1067     return tm;
1068   }
1069
1070   @Override
1071   public void stopProgressBar(String msg, long handle)
1072   {
1073     if (progressBar != null)
1074     {
1075       progressBar.setProgressBar(msg, handle);
1076     }
1077   }
1078
1079   protected IProgressIndicator getProgressIndicator()
1080   {
1081     return progressBar;
1082   }
1083
1084   protected void setProgressIndicator(IProgressIndicator pi)
1085   {
1086     progressBar = pi;
1087   }
1088
1089   public void setProgressMessage(String message, long id)
1090   {
1091     if (progressBar != null)
1092     {
1093       progressBar.setProgressBar(message, id);
1094     }
1095   }
1096
1097   @Override
1098   public void showConsole(boolean show)
1099   {
1100     // default does nothing
1101   }
1102
1103   /**
1104    * Show only the selected chain(s) in the viewer
1105    */
1106   protected void showSelectedChains()
1107   {
1108     List<String> toshow = new ArrayList<>();
1109     for (int i = 0; i < chainMenu.getItemCount(); i++)
1110     {
1111       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
1112       {
1113         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
1114         if (item.isSelected())
1115         {
1116           toshow.add(item.getText());
1117         }
1118       }
1119     }
1120     getBinding().showChains(toshow);
1121   }
1122
1123   /**
1124    * Tries to fetch a PDB file and save to a temporary local file. Returns the
1125    * saved file path if successful, or null if not.
1126    * 
1127    * @param processingEntry
1128    * @return
1129    */
1130   protected String fetchPdbFile(PDBEntry processingEntry)
1131   {
1132     String filePath = null;
1133     Pdb pdbclient = new Pdb();
1134     EBIAlfaFold afclient =  new EBIAlfaFold();
1135     AlignmentI pdbseq = null;
1136     String pdbid = processingEntry.getId();
1137     long handle = System.currentTimeMillis()
1138             + Thread.currentThread().hashCode();
1139   
1140     /*
1141      * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
1142      */
1143     String msg = MessageManager.formatMessage("status.fetching_pdb",
1144             new Object[]
1145             { pdbid });
1146     getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
1147     // long hdl = startProgressBar(MessageManager.formatMessage(
1148     // "status.fetching_pdb", new Object[]
1149     // { pdbid }));
1150     try
1151     {
1152       if (afclient.isValidReference(pdbid))
1153       {
1154         pdbseq = afclient.getSequenceRecords(pdbid);
1155       } else {
1156         pdbseq = pdbclient.getSequenceRecords(pdbid);
1157       }
1158     } catch (Exception e)
1159     {
1160       System.err.println(
1161               "Error retrieving PDB id " + pdbid + ": " + e.getMessage());
1162     } finally
1163     {
1164       msg = pdbid + " " + MessageManager.getString("label.state_completed");
1165       getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
1166       // stopProgressBar(msg, hdl);
1167     }
1168     /*
1169      * If PDB data were saved and are not invalid (empty alignment), return the
1170      * file path.
1171      */
1172     if (pdbseq != null && pdbseq.getHeight() > 0)
1173     {
1174       // just use the file name from the first sequence's first PDBEntry
1175       filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
1176               .elementAt(0).getFile()).getAbsolutePath();
1177       processingEntry.setFile(filePath);
1178     }
1179     return filePath;
1180   }
1181
1182   /**
1183    * If supported, saves the state of the structure viewer to a temporary file
1184    * and returns the file, else returns null
1185    * 
1186    * @return
1187    */
1188   public File saveSession()
1189   {
1190     // TODO: a wait loop to ensure the file is written fully before returning?
1191     return getBinding() == null ? null : getBinding().saveSession();
1192   }
1193
1194   /**
1195    * Close down this instance of Jalview's Chimera viewer, giving the user the
1196    * option to close the associated Chimera window (process). They may wish to
1197    * keep it open until they have had an opportunity to save any work.
1198    * 
1199    * @param forceClose
1200    *          if true, close any linked Chimera process; if false, prompt first
1201    */
1202   @Override
1203   public void closeViewer(boolean forceClose)
1204   {
1205     AAStructureBindingModel binding = getBinding();
1206     if (binding != null && binding.isViewerRunning())
1207     {
1208       if (!forceClose)
1209       {
1210         String viewerName = getViewerName();
1211         String prompt = MessageManager
1212                 .formatMessage("label.confirm_close_viewer", new Object[]
1213                 { binding.getViewerTitle(viewerName, false), viewerName });
1214         prompt = JvSwingUtils.wrapTooltip(true, prompt);
1215         int confirm = JvOptionPane.showConfirmDialog(this, prompt,
1216                 MessageManager.getString("label.close_viewer"),
1217                 JvOptionPane.YES_NO_CANCEL_OPTION);
1218         /*
1219          * abort closure if user hits escape or Cancel
1220          */
1221         if (confirm == JvOptionPane.CANCEL_OPTION
1222                 || confirm == JvOptionPane.CLOSED_OPTION)
1223         {
1224           return;
1225         }
1226         forceClose = confirm == JvOptionPane.YES_OPTION;
1227       }
1228     }
1229     if (binding != null)
1230     {
1231       binding.closeViewer(forceClose);
1232     }
1233     setAlignmentPanel(null);
1234     _aps.clear();
1235     _alignwith.clear();
1236     _colourwith.clear();
1237     // TODO: check for memory leaks where instance isn't finalised because jmb
1238     // holds a reference to the window
1239     // jmb = null;
1240     dispose();
1241   }
1242
1243   @Override
1244   public void showHelp_actionPerformed()
1245   {
1246     try
1247     {
1248       String url = getBinding().getHelpURL();
1249       if (url != null)
1250       {
1251         BrowserLauncher.openURL(url);
1252       }
1253     } catch (IOException ex)
1254     {
1255       System.err
1256               .println("Show " + getViewerName() + " failed with: "
1257                       + ex.getMessage());
1258     }
1259   }
1260
1261 }