JAL-3253 jalview.bin.Instance handles all singleton instances -
[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 jalview.api.AlignmentViewPanel;
24 import jalview.bin.Cache;
25 import jalview.bin.Instance;
26 import jalview.datamodel.Alignment;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.HiddenColumns;
29 import jalview.datamodel.PDBEntry;
30 import jalview.datamodel.SequenceI;
31 import jalview.gui.JalviewColourChooser.ColourChooserListener;
32 import jalview.gui.StructureViewer.ViewerType;
33 import jalview.gui.ViewSelectionMenu.ViewSetProvider;
34 import jalview.io.DataSourceType;
35 import jalview.io.JalviewFileChooser;
36 import jalview.io.JalviewFileView;
37 import jalview.jbgui.GStructureViewer;
38 import jalview.schemes.ColourSchemeI;
39 import jalview.schemes.ColourSchemes;
40 import jalview.structure.StructureMapping;
41 import jalview.structures.models.AAStructureBindingModel;
42 import jalview.util.MessageManager;
43
44 import java.awt.Color;
45 import java.awt.Component;
46 import java.awt.event.ActionEvent;
47 import java.awt.event.ActionListener;
48 import java.awt.event.ItemEvent;
49 import java.awt.event.ItemListener;
50 import java.io.BufferedReader;
51 import java.io.File;
52 import java.io.FileOutputStream;
53 import java.io.FileReader;
54 import java.io.IOException;
55 import java.io.PrintWriter;
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.Vector;
59
60 import javax.swing.ButtonGroup;
61 import javax.swing.JCheckBoxMenuItem;
62 import javax.swing.JMenu;
63 import javax.swing.JMenuItem;
64 import javax.swing.JRadioButtonMenuItem;
65 import javax.swing.event.MenuEvent;
66 import javax.swing.event.MenuListener;
67
68 /**
69  * Base class with common functionality for JMol, Chimera or other structure
70  * viewers.
71  * 
72  * @author gmcarstairs
73  *
74  */
75 public abstract class StructureViewerBase extends GStructureViewer
76         implements Runnable, ViewSetProvider
77 {
78   /*
79    * names for colour options (additional to Jalview colour schemes)
80    */
81   enum ViewerColour
82   {
83     BySequence, ByChain, ChargeCysteine, ByViewer
84   }
85
86   /**
87    * list of sequenceSet ids associated with the view
88    */
89   protected List<String> _aps = new ArrayList<>();
90
91   /**
92    * list of alignment panels to use for superposition
93    */
94   protected Vector<AlignmentPanel> _alignwith = new Vector<>();
95
96   /**
97    * list of alignment panels that are used for colouring structures by aligned
98    * sequences
99    */
100   protected Vector<AlignmentPanel> _colourwith = new Vector<>();
101
102   private String viewId = null;
103
104   private AlignmentPanel ap;
105
106   protected boolean alignAddedStructures = false;
107
108   protected volatile boolean _started = false;
109
110   protected volatile boolean addingStructures = false;
111
112   protected Thread worker = null;
113
114   protected boolean allChainsSelected = false;
115
116   protected JMenu viewSelectionMenu;
117
118   /**
119    * set after sequence colouring has been applied for this structure viewer.
120    * used to determine if the final sequence/structure mapping has been
121    * determined
122    */
123   protected volatile boolean seqColoursApplied = false;
124
125   /**
126    * Default constructor
127    */
128   public StructureViewerBase()
129   {
130     super();
131   }
132
133   /**
134    * @return true if added structures should be aligned to existing one(s)
135    */
136   @Override
137   public boolean isAlignAddedStructures()
138   {
139     return alignAddedStructures;
140   }
141
142   /**
143    * 
144    * @param true
145    *          if added structures should be aligned to existing one(s)
146    */
147   @Override
148   public void setAlignAddedStructures(boolean alignAdded)
149   {
150     alignAddedStructures = alignAdded;
151   }
152
153   /**
154    * 
155    * @param ap2
156    * @return true if this Jmol instance is linked with the given alignPanel
157    */
158   public boolean isLinkedWith(AlignmentPanel ap2)
159   {
160     return _aps.contains(ap2.av.getSequenceSetId());
161   }
162
163   public boolean isUsedforaligment(AlignmentPanel ap2)
164   {
165
166     return (_alignwith != null) && _alignwith.contains(ap2);
167   }
168
169   public boolean isUsedforcolourby(AlignmentPanel ap2)
170   {
171     return (_colourwith != null) && _colourwith.contains(ap2);
172   }
173
174   /**
175    * 
176    * @return TRUE if the view is NOT being coloured by the alignment colours.
177    */
178   public boolean isColouredByViewer()
179   {
180     return !getBinding().isColourBySequence();
181   }
182
183   public String getViewId()
184   {
185     if (viewId == null)
186     {
187       viewId = System.currentTimeMillis() + "." + this.hashCode();
188     }
189     return viewId;
190   }
191
192   protected void setViewId(String viewId)
193   {
194     this.viewId = viewId;
195   }
196
197   public abstract String getStateInfo();
198
199   protected void buildActionMenu()
200   {
201     if (_alignwith == null)
202     {
203       _alignwith = new Vector<>();
204     }
205     if (_alignwith.size() == 0 && ap != null)
206     {
207       _alignwith.add(ap);
208     }
209     ;
210     for (Component c : viewerActionMenu.getMenuComponents())
211     {
212       if (c != alignStructs)
213       {
214         viewerActionMenu.remove((JMenuItem) c);
215       }
216     }
217   }
218
219   public AlignmentPanel getAlignmentPanel()
220   {
221     return ap;
222   }
223
224   protected void setAlignmentPanel(AlignmentPanel alp)
225   {
226     this.ap = alp;
227   }
228
229   @Override
230   public AlignmentPanel[] getAllAlignmentPanels()
231   {
232     AlignmentPanel[] t, list = new AlignmentPanel[0];
233     for (String setid : _aps)
234     {
235       AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
236       if (panels != null)
237       {
238         t = new AlignmentPanel[list.length + panels.length];
239         System.arraycopy(list, 0, t, 0, list.length);
240         System.arraycopy(panels, 0, t, list.length, panels.length);
241         list = t;
242       }
243     }
244
245     return list;
246   }
247
248   /**
249    * set the primary alignmentPanel reference and add another alignPanel to the
250    * list of ones to use for colouring and aligning
251    * 
252    * @param nap
253    */
254   public void addAlignmentPanel(AlignmentPanel nap)
255   {
256     if (getAlignmentPanel() == null)
257     {
258       setAlignmentPanel(nap);
259     }
260     if (!_aps.contains(nap.av.getSequenceSetId()))
261     {
262       _aps.add(nap.av.getSequenceSetId());
263     }
264   }
265
266   /**
267    * remove any references held to the given alignment panel
268    * 
269    * @param nap
270    */
271   public void removeAlignmentPanel(AlignmentPanel nap)
272   {
273     try
274     {
275       _alignwith.remove(nap);
276       _colourwith.remove(nap);
277       if (getAlignmentPanel() == nap)
278       {
279         setAlignmentPanel(null);
280         for (AlignmentPanel aps : getAllAlignmentPanels())
281         {
282           if (aps != nap)
283           {
284             setAlignmentPanel(aps);
285             break;
286           }
287         }
288       }
289     } catch (Exception ex)
290     {
291     }
292     if (getAlignmentPanel() != null)
293     {
294       buildActionMenu();
295     }
296   }
297
298   public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
299   {
300     addAlignmentPanel(nap);
301     if (!_alignwith.contains(nap))
302     {
303       _alignwith.add(nap);
304     }
305   }
306
307   public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
308   {
309     if (_alignwith.contains(nap))
310     {
311       _alignwith.remove(nap);
312     }
313   }
314
315   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
316           boolean enableColourBySeq)
317   {
318     useAlignmentPanelForColourbyseq(nap);
319     getBinding().setColourBySequence(enableColourBySeq);
320     seqColour.setSelected(enableColourBySeq);
321     viewerColour.setSelected(!enableColourBySeq);
322   }
323
324   public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
325   {
326     addAlignmentPanel(nap);
327     if (!_colourwith.contains(nap))
328     {
329       _colourwith.add(nap);
330     }
331   }
332
333   public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
334   {
335     if (_colourwith.contains(nap))
336     {
337       _colourwith.remove(nap);
338     }
339   }
340
341   public abstract ViewerType getViewerType();
342
343   protected abstract IProgressIndicator getIProgressIndicator();
344
345   /**
346    * add a new structure (with associated sequences and chains) to this viewer,
347    * retrieving it if necessary first.
348    * 
349    * @param pdbentry
350    * @param seqs
351    * @param chains
352    * @param align
353    *          if true, new structure(s) will be aligned using associated
354    *          alignment
355    * @param alignFrame
356    */
357   protected void addStructure(final PDBEntry pdbentry,
358           final SequenceI[] seqs, final String[] chains,
359           final IProgressIndicator alignFrame)
360   {
361     if (pdbentry.getFile() == null)
362     {
363       if (worker != null && worker.isAlive())
364       {
365         // a retrieval is in progress, wait around and add ourselves to the
366         // queue.
367         new Thread(new Runnable()
368         {
369           @Override
370           public void run()
371           {
372             while (worker != null && worker.isAlive() && _started)
373             {
374               try
375               {
376                 Thread.sleep(100 + ((int) Math.random() * 100));
377
378               } catch (Exception e)
379               {
380               }
381             }
382             // and call ourselves again.
383             addStructure(pdbentry, seqs, chains, alignFrame);
384           }
385         }).start();
386         return;
387       }
388     }
389     // otherwise, start adding the structure.
390     getBinding().addSequenceAndChain(new PDBEntry[] { pdbentry },
391             new SequenceI[][]
392             { seqs }, new String[][] { chains });
393     addingStructures = true;
394     _started = false;
395     worker = new Thread(this);
396     worker.start();
397     return;
398   }
399
400   protected boolean hasPdbId(String pdbId)
401   {
402     return getBinding().hasPdbId(pdbId);
403   }
404
405   /**
406    * Returns a list of any viewer of the instantiated type. The list is
407    * restricted to those linked to the given alignment panel if it is not null.
408    */
409   protected List<StructureViewerBase> getViewersFor(AlignmentPanel alp)
410   {
411     return Instance.getDesktop().getStructureViewers(alp, this.getClass());
412   }
413
414   @Override
415   public void addToExistingViewer(PDBEntry pdbentry, SequenceI[] seq,
416           String[] chains, final AlignmentViewPanel apanel, String pdbId)
417   {
418     /*
419      * JAL-1742 exclude view with this structure already mapped (don't offer
420      * to align chain B to chain A of the same structure); code may defend
421      * against this possibility before we reach here
422      */
423     if (hasPdbId(pdbId))
424     {
425       return;
426     }
427     AlignmentPanel alignPanel = (AlignmentPanel) apanel; // Implementation error if this
428                                                  // cast fails
429     useAlignmentPanelForSuperposition(alignPanel);
430     addStructure(pdbentry, seq, chains, alignPanel.alignFrame);
431   }
432
433   /**
434    * Adds mappings for the given sequences to an already opened PDB structure,
435    * and updates any viewers that have the PDB file
436    * 
437    * @param seq
438    * @param chains
439    * @param apanel
440    * @param pdbFilename
441    */
442   public void addSequenceMappingsToStructure(SequenceI[] seq,
443           String[] chains, final AlignmentViewPanel alpanel,
444           String pdbFilename)
445   {
446     AlignmentPanel apanel = (AlignmentPanel) alpanel;
447
448     // TODO : Fix multiple seq to one chain issue here.
449     /*
450      * create the mappings
451      */
452     apanel.getStructureSelectionManager().setMapping(seq, chains,
453             pdbFilename, DataSourceType.FILE, getIProgressIndicator());
454
455     /*
456      * alert the FeatureRenderer to show new (PDB RESNUM) features
457      */
458     if (apanel.getSeqPanel().seqCanvas.fr != null)
459     {
460       apanel.getSeqPanel().seqCanvas.fr.featuresAdded();
461       // note - we don't do a refresh for structure here because we do it
462       // explicitly for all panels later on
463       apanel.paintAlignment(true, false);
464     }
465
466     /*
467      * add the sequences to any other viewers (of the same type) for this pdb
468      * file
469      */
470     // JBPNOTE: this looks like a binding routine, rather than a gui routine
471     for (StructureViewerBase viewer : getViewersFor(null))
472     {
473       AAStructureBindingModel bindingModel = viewer.getBinding();
474       for (int pe = 0; pe < bindingModel.getPdbCount(); pe++)
475       {
476         if (bindingModel.getPdbEntry(pe).getFile().equals(pdbFilename))
477         {
478           bindingModel.addSequence(pe, seq);
479           viewer.addAlignmentPanel(apanel);
480           /*
481            * add it to the set of alignments used for colouring structure by
482            * sequence
483            */
484           viewer.useAlignmentPanelForColourbyseq(apanel);
485           viewer.buildActionMenu();
486           apanel.getStructureSelectionManager()
487                   .sequenceColoursChanged(apanel);
488           break;
489         }
490       }
491     }
492   }
493
494   @Override
495   public boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains,
496           final AlignmentViewPanel apanel, String pdbId)
497   {
498     String alreadyMapped = apanel.getStructureSelectionManager()
499             .alreadyMappedToFile(pdbId);
500
501     if (alreadyMapped == null)
502     {
503       return false;
504     }
505
506     addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped);
507     return true;
508   }
509
510   void setChainMenuItems(List<String> chainNames)
511   {
512     chainMenu.removeAll();
513     if (chainNames == null || chainNames.isEmpty())
514     {
515       return;
516     }
517     JMenuItem menuItem = new JMenuItem(
518             MessageManager.getString("label.all"));
519     menuItem.addActionListener(new ActionListener()
520     {
521       @Override
522       public void actionPerformed(ActionEvent evt)
523       {
524         allChainsSelected = true;
525         for (int i = 0; i < chainMenu.getItemCount(); i++)
526         {
527           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
528           {
529             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
530           }
531         }
532         showSelectedChains();
533         allChainsSelected = false;
534       }
535     });
536
537     chainMenu.add(menuItem);
538
539     for (String chain : chainNames)
540     {
541       menuItem = new JCheckBoxMenuItem(chain, true);
542       menuItem.addItemListener(new ItemListener()
543       {
544         @Override
545         public void itemStateChanged(ItemEvent evt)
546         {
547           if (!allChainsSelected)
548           {
549             showSelectedChains();
550           }
551         }
552       });
553
554       chainMenu.add(menuItem);
555     }
556   }
557
558   abstract void showSelectedChains();
559
560   /**
561    * Action on selecting one of Jalview's registered colour schemes
562    */
563   @Override
564   public void changeColour_actionPerformed(String colourSchemeName)
565   {
566     AlignmentI al = getAlignmentPanel().av.getAlignment();
567     ColourSchemeI cs = ColourSchemes.getInstance()
568             .getColourScheme(colourSchemeName, getAlignmentPanel().av, al,
569                     null);
570     getBinding().setJalviewColourScheme(cs);
571   }
572
573   /**
574    * Builds the colour menu
575    */
576   protected void buildColourMenu()
577   {
578     colourMenu.removeAll();
579     AlignmentI al = getAlignmentPanel().av.getAlignment();
580
581     /*
582      * add colour by sequence, by chain, by charge and cysteine
583      */
584     colourMenu.add(seqColour);
585     colourMenu.add(chainColour);
586     colourMenu.add(chargeColour);
587     chargeColour.setEnabled(!al.isNucleotide());
588
589     /*
590      * add all 'simple' (per-residue) colour schemes registered to Jalview
591      */
592     ButtonGroup itemGroup = ColourMenuHelper.addMenuItems(colourMenu, this,
593             al, true);
594
595     /*
596      * add 'colour by viewer' (menu item text is set in subclasses)
597      */
598     viewerColour.setSelected(false);
599     viewerColour.addActionListener(new ActionListener()
600     {
601       @Override
602       public void actionPerformed(ActionEvent actionEvent)
603       {
604         viewerColour_actionPerformed(actionEvent);
605       }
606     });
607     colourMenu.add(viewerColour);
608
609     /*
610      * add 'set background colour'
611      */
612     JMenuItem backGround = new JMenuItem();
613     backGround
614             .setText(MessageManager.getString("action.background_colour"));
615     backGround.addActionListener(new ActionListener()
616     {
617       @Override
618       public void actionPerformed(ActionEvent actionEvent)
619       {
620         background_actionPerformed(actionEvent);
621       }
622     });
623     colourMenu.add(backGround);
624
625     /*
626      * add colour buttons to a group so their selection is
627      * mutually exclusive (background colour is a separate option)
628      */
629     itemGroup.add(seqColour);
630     itemGroup.add(chainColour);
631     itemGroup.add(chargeColour);
632     itemGroup.add(viewerColour);
633   }
634
635   /**
636    * Construct menu items
637    */
638   protected void initMenus()
639   {
640     AAStructureBindingModel binding = getBinding();
641
642     seqColour = new JRadioButtonMenuItem();
643     seqColour.setText(MessageManager.getString("action.by_sequence"));
644     seqColour.setName(ViewerColour.BySequence.name());
645     seqColour.setSelected(binding.isColourBySequence());
646     seqColour.addActionListener(new ActionListener()
647     {
648       @Override
649       public void actionPerformed(ActionEvent actionEvent)
650       {
651         seqColour_actionPerformed(actionEvent);
652       }
653     });
654
655     chainColour = new JRadioButtonMenuItem();
656     chainColour.setText(MessageManager.getString("action.by_chain"));
657     chainColour.setName(ViewerColour.ByChain.name());
658     chainColour.addActionListener(new ActionListener()
659     {
660       @Override
661       public void actionPerformed(ActionEvent actionEvent)
662       {
663         chainColour_actionPerformed(actionEvent);
664       }
665     });
666
667     chargeColour = new JRadioButtonMenuItem();
668     chargeColour.setText(MessageManager.getString("label.charge_cysteine"));
669     chargeColour.setName(ViewerColour.ChargeCysteine.name());
670     chargeColour.addActionListener(new ActionListener()
671     {
672       @Override
673       public void actionPerformed(ActionEvent actionEvent)
674       {
675         chargeColour_actionPerformed(actionEvent);
676       }
677     });
678
679     viewerColour = new JRadioButtonMenuItem();
680     // text is set in overrides of this method
681     viewerColour.setName(ViewerColour.ByViewer.name());
682     viewerColour.setSelected(!binding.isColourBySequence());
683
684     if (_colourwith == null)
685     {
686       _colourwith = new Vector<>();
687     }
688     if (_alignwith == null)
689     {
690       _alignwith = new Vector<>();
691     }
692
693     ViewSelectionMenu seqColourBy = new ViewSelectionMenu(
694             MessageManager.getString("label.colour_by"), this, _colourwith,
695             new ItemListener()
696             {
697               @Override
698               public void itemStateChanged(ItemEvent e)
699               {
700                 if (!seqColour.isSelected())
701                 {
702                   seqColour.doClick();
703                 }
704                 else
705                 {
706                   // update the Chimera display now.
707                   seqColour_actionPerformed(null);
708                 }
709               }
710             });
711     viewMenu.add(seqColourBy);
712
713     final ItemListener handler = new ItemListener()
714     {
715       @Override
716       public void itemStateChanged(ItemEvent e)
717       {
718         alignStructs.setEnabled(!_alignwith.isEmpty());
719         alignStructs.setToolTipText(MessageManager.formatMessage(
720                 "label.align_structures_using_linked_alignment_views",
721                 _alignwith.size()));
722       }
723     };
724     viewSelectionMenu = new ViewSelectionMenu(
725             MessageManager.getString("label.superpose_with"), this,
726             _alignwith, handler);
727     handler.itemStateChanged(null);
728     viewerActionMenu.add(viewSelectionMenu, 0);
729     viewerActionMenu.addMenuListener(new MenuListener()
730     {
731       @Override
732       public void menuSelected(MenuEvent e)
733       {
734         handler.itemStateChanged(null);
735       }
736
737       @Override
738       public void menuDeselected(MenuEvent e)
739       {
740       }
741
742       @Override
743       public void menuCanceled(MenuEvent e)
744       {
745       }
746     });
747
748     buildColourMenu();
749   }
750
751   @Override
752   public void setJalviewColourScheme(ColourSchemeI cs)
753   {
754     getBinding().setJalviewColourScheme(cs);
755   }
756
757   /**
758    * Sends commands to the structure viewer to superimpose structures based on
759    * currently associated alignments. May optionally return an error message for
760    * the operation.
761    */
762   @Override
763   protected String alignStructs_actionPerformed(ActionEvent actionEvent)
764   {
765     return alignStructs_withAllAlignPanels();
766   }
767
768   protected String alignStructs_withAllAlignPanels()
769   {
770     if (getAlignmentPanel() == null)
771     {
772       return null;
773     }
774
775     if (_alignwith.size() == 0)
776     {
777       _alignwith.add(getAlignmentPanel());
778     }
779
780     String reply = null;
781     try
782     {
783       AlignmentI[] als = new Alignment[_alignwith.size()];
784       HiddenColumns[] alc = new HiddenColumns[_alignwith.size()];
785       int[] alm = new int[_alignwith.size()];
786       int a = 0;
787
788       for (AlignmentPanel alignPanel : _alignwith)
789       {
790         als[a] = alignPanel.av.getAlignment();
791         alm[a] = -1;
792         alc[a++] = alignPanel.av.getAlignment().getHiddenColumns();
793       }
794       reply = getBinding().superposeStructures(als, alm, alc);
795       if (reply != null)
796       {
797         String text = MessageManager
798                 .formatMessage("error.superposition_failed", reply);
799         statusBar.setText(text);
800       }
801     } catch (Exception e)
802     {
803       StringBuffer sp = new StringBuffer();
804       for (AlignmentPanel alignPanel : _alignwith)
805       {
806         sp.append("'" + alignPanel.alignFrame.getTitle() + "' ");
807       }
808       Cache.log.info("Couldn't align structures with the " + sp.toString()
809               + "associated alignment panels.", e);
810     }
811     return reply;
812   }
813
814   /**
815    * Opens a colour chooser dialog, and applies the chosen colour to the
816    * background of the structure viewer
817    */
818   @Override
819   public void background_actionPerformed(ActionEvent actionEvent)
820   {
821     String ttl = MessageManager.getString("label.select_background_colour");
822     ColourChooserListener listener = new ColourChooserListener()
823     {
824       @Override
825       public void colourSelected(Color c)
826       {
827         getBinding().setBackgroundColour(c);
828       }
829     };
830     JalviewColourChooser.showColourChooser(this, ttl, null, listener);
831   }
832
833   @Override
834   public void viewerColour_actionPerformed(ActionEvent actionEvent)
835   {
836     if (viewerColour.isSelected())
837     {
838       // disable automatic sequence colouring.
839       getBinding().setColourBySequence(false);
840     }
841   }
842
843   @Override
844   public void chainColour_actionPerformed(ActionEvent actionEvent)
845   {
846     chainColour.setSelected(true);
847     getBinding().colourByChain();
848   }
849
850   @Override
851   public void chargeColour_actionPerformed(ActionEvent actionEvent)
852   {
853     chargeColour.setSelected(true);
854     getBinding().colourByCharge();
855   }
856
857   @Override
858   public void seqColour_actionPerformed(ActionEvent actionEvent)
859   {
860     AAStructureBindingModel binding = getBinding();
861     binding.setColourBySequence(seqColour.isSelected());
862     if (_colourwith == null)
863     {
864       _colourwith = new Vector<>();
865     }
866     if (binding.isColourBySequence())
867     {
868       if (!binding.isLoadingFromArchive())
869       {
870         if (_colourwith.size() == 0 && getAlignmentPanel() != null)
871         {
872           // Make the currently displayed alignment panel the associated view
873           _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
874         }
875       }
876       // Set the colour using the current view for the associated alignframe
877       for (AlignmentPanel alignPanel : _colourwith)
878       {
879         binding.colourBySequence(alignPanel);
880       }
881       seqColoursApplied = true;
882     }
883   }
884
885   @Override
886   public void pdbFile_actionPerformed(ActionEvent actionEvent)
887   {
888     // TODO: JAL-3048 not needed for Jalview-JS - save PDB file
889     JalviewFileChooser chooser = new JalviewFileChooser(
890             Cache.getProperty("LAST_DIRECTORY"));
891
892     chooser.setFileView(new JalviewFileView());
893     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
894     chooser.setToolTipText(MessageManager.getString("action.save"));
895
896     int value = chooser.showSaveDialog(this);
897
898     if (value == JalviewFileChooser.APPROVE_OPTION)
899     {
900       BufferedReader in = null;
901       try
902       {
903         // TODO: cope with multiple PDB files in view
904         in = new BufferedReader(
905                 new FileReader(getBinding().getStructureFiles()[0]));
906         File outFile = chooser.getSelectedFile();
907
908         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
909         String data;
910         while ((data = in.readLine()) != null)
911         {
912           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
913           {
914             out.println(data);
915           }
916         }
917         out.close();
918       } catch (Exception ex)
919       {
920         ex.printStackTrace();
921       } finally
922       {
923         if (in != null)
924         {
925           try
926           {
927             in.close();
928           } catch (IOException e)
929           {
930             // ignore
931           }
932         }
933       }
934     }
935   }
936
937   @Override
938   public void viewMapping_actionPerformed(ActionEvent actionEvent)
939   {
940     CutAndPasteTransfer cap = new CutAndPasteTransfer();
941     try
942     {
943       cap.appendText(getBinding().printMappings());
944     } catch (OutOfMemoryError e)
945     {
946       new OOMWarning(
947               "composing sequence-structure alignments for display in text box.",
948               e);
949       cap.dispose();
950       return;
951     }
952     Desktop.addInternalFrame(cap,
953             MessageManager.getString("label.pdb_sequence_mapping"), 550,
954             600);
955   }
956
957   protected abstract String getViewerName();
958
959   /**
960    * Configures the title and menu items of the viewer panel.
961    */
962   @Override
963   public void updateTitleAndMenus()
964   {
965     AAStructureBindingModel binding = getBinding();
966     if (binding.hasFileLoadingError())
967     {
968       repaint();
969       return;
970     }
971     setChainMenuItems(binding.getChainNames());
972
973     this.setTitle(binding.getViewerTitle(getViewerName(), true));
974
975     /*
976      * enable 'Superpose with' if more than one mapped structure
977      */
978     viewSelectionMenu.setEnabled(false);
979     if (getBinding().getStructureFiles().length > 1
980             && getBinding().getSequence().length > 1)
981     {
982       viewSelectionMenu.setEnabled(true);
983     }
984
985     /*
986      * Show action menu if it has any enabled items
987      */
988     viewerActionMenu.setVisible(false);
989     for (int i = 0; i < viewerActionMenu.getItemCount(); i++)
990     {
991       if (viewerActionMenu.getItem(i).isEnabled())
992       {
993         viewerActionMenu.setVisible(true);
994         break;
995       }
996     }
997
998     if (!binding.isLoadingFromArchive())
999     {
1000       seqColour_actionPerformed(null);
1001     }
1002   }
1003
1004   @Override
1005   public String toString()
1006   {
1007     return getTitle();
1008   }
1009
1010   @Override
1011   public boolean hasMapping()
1012   {
1013     if (worker != null && (addingStructures || _started))
1014     {
1015       return false;
1016     }
1017     if (getBinding() == null)
1018     {
1019       if (_aps == null || _aps.size() == 0)
1020       {
1021         // viewer has been closed, but we did at some point run.
1022         return true;
1023       }
1024       return false;
1025     }
1026     String[] pdbids = getBinding().getStructureFiles();
1027     if (pdbids == null)
1028     {
1029       return false;
1030     }
1031     int p=0;
1032     for (String pdbid:pdbids) {
1033       StructureMapping sm[] = getBinding().getSsm().getMapping(pdbid);
1034       if (sm!=null && sm.length>0 && sm[0]!=null) {
1035         p++;
1036       }
1037     }
1038     // only return true if there is a mapping for every structure file we have loaded
1039     if (p == 0 || p != pdbids.length)
1040     {
1041       return false;
1042     }
1043     // and that coloring has been applied
1044     return seqColoursApplied;
1045   }
1046
1047   @Override
1048   public void raiseViewer()
1049   {
1050     toFront();
1051   }
1052
1053 }