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