JAL-3402 suppress unmapped chains in View Chain menu
[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.AlignViewportI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.bin.Cache;
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.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.structure.StructureMapping;
40 import jalview.structures.models.AAStructureBindingModel;
41 import jalview.util.MessageManager;
42
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ActionListener;
47 import java.awt.event.ItemEvent;
48 import java.awt.event.ItemListener;
49 import java.io.BufferedReader;
50 import java.io.File;
51 import java.io.FileOutputStream;
52 import java.io.FileReader;
53 import java.io.IOException;
54 import java.io.PrintWriter;
55 import java.util.ArrayList;
56 import java.util.Collections;
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.JMenu;
64 import javax.swing.JMenuItem;
65 import javax.swing.JRadioButtonMenuItem;
66 import javax.swing.event.MenuEvent;
67 import javax.swing.event.MenuListener;
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   /**
127    * Default constructor
128    */
129   public StructureViewerBase()
130   {
131     super();
132   }
133
134   /**
135    * @return true if added structures should be aligned to existing one(s)
136    */
137   @Override
138   public boolean isAlignAddedStructures()
139   {
140     return alignAddedStructures;
141   }
142
143   /**
144    * 
145    * @param true
146    *          if added structures should be aligned to existing one(s)
147    */
148   @Override
149   public void setAlignAddedStructures(boolean alignAdded)
150   {
151     alignAddedStructures = alignAdded;
152   }
153
154   /**
155    * 
156    * @param ap2
157    * @return true if this Jmol instance is linked with the given alignPanel
158    */
159   public boolean isLinkedWith(AlignmentPanel ap2)
160   {
161     return _aps.contains(ap2.av.getSequenceSetId());
162   }
163
164   public boolean isUsedforaligment(AlignmentPanel ap2)
165   {
166
167     return (_alignwith != null) && _alignwith.contains(ap2);
168   }
169
170   @Override
171   public boolean isUsedforcolourby(AlignmentViewPanel avp)
172   {
173     return (_colourwith != null) && _colourwith.contains(avp);
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
520     /*
521      * add the 'All' menu item
522      */
523     JMenuItem menuItem = new JMenuItem(
524             MessageManager.getString("label.all"));
525     menuItem.addActionListener(new ActionListener()
526     {
527       @Override
528       public void actionPerformed(ActionEvent evt)
529       {
530         allChainsSelected = true;
531         for (int i = 0; i < chainMenu.getItemCount(); i++)
532         {
533           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
534           {
535             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
536           }
537         }
538         showSelectedChains();
539         allChainsSelected = false;
540       }
541     });
542     chainMenu.add(menuItem);
543
544     /*
545      * add a menu item for each structure and chain
546      */
547     Collections.sort(chainNames);
548     String lastSeqName = "";
549     for (String chain : chainNames)
550     {
551       String seqName = getSequenceNameForChain(chain);
552       if (seqName == null)
553       {
554         continue;
555       }
556       int nameLength = seqName.length();
557       if (nameLength > 16)
558       {
559         seqName = seqName.substring(0, 8) + "..."
560                 + seqName.substring(nameLength - 8, nameLength);
561       }
562       String text = chain;
563       if (!lastSeqName.equals(seqName))
564       {
565         text = text + " " + seqName;
566       }
567       lastSeqName = seqName;
568       menuItem = new JCheckBoxMenuItem(text, true);
569       menuItem.addItemListener(new ItemListener()
570       {
571         @Override
572         public void itemStateChanged(ItemEvent evt)
573         {
574           if (!allChainsSelected)
575           {
576             showSelectedChains();
577           }
578         }
579       });
580
581       chainMenu.add(menuItem);
582     }
583   }
584
585   /**
586    * Answers the name of the sequence mapped to the given chain (formatted as
587    * pdbId:chainId, e.g. 1A70:A). Answers null if no mapped sequence is found. If
588    * more than one sequence is matched, just answers the name of the first one
589    * found.
590    * 
591    * @param chain
592    * @return
593    */
594   private String getSequenceNameForChain(String chain)
595   {
596     String[] tokens = chain.split(":");
597     String pdbId = tokens[0];
598     String chainId = tokens[1];
599     List<StructureMapping> mappings = getBinding().getSsm()
600             .getMappingForChain(pdbId, chainId);
601     return mappings.isEmpty() ? null
602             : mappings.get(0).getSequence().getName();
603   }
604
605   /**
606    * Action on selecting one of Jalview's registered colour schemes
607    */
608   @Override
609   public void changeColour_actionPerformed(String colourSchemeName)
610   {
611     AlignmentI al = getAlignmentPanel().av.getAlignment();
612     ColourSchemeI cs = ColourSchemes.getInstance()
613             .getColourScheme(colourSchemeName, getAlignmentPanel().av, al,
614                     null);
615     getBinding().setJalviewColourScheme(cs);
616   }
617
618   /**
619    * Builds the colour menu
620    */
621   protected void buildColourMenu()
622   {
623     colourMenu.removeAll();
624     AlignmentI al = getAlignmentPanel().av.getAlignment();
625
626     /*
627      * add colour by sequence, by chain, by charge and cysteine
628      */
629     colourMenu.add(seqColour);
630     colourMenu.add(chainColour);
631     colourMenu.add(chargeColour);
632     chargeColour.setEnabled(!al.isNucleotide());
633
634     /*
635      * add all 'simple' (per-residue) colour schemes registered to Jalview
636      */
637     ButtonGroup itemGroup = ColourMenuHelper.addMenuItems(colourMenu, this,
638             al, true);
639
640     /*
641      * add 'colour by viewer' (menu item text is set in subclasses)
642      */
643     viewerColour.setSelected(false);
644     viewerColour.addActionListener(new ActionListener()
645     {
646       @Override
647       public void actionPerformed(ActionEvent actionEvent)
648       {
649         viewerColour_actionPerformed(actionEvent);
650       }
651     });
652     colourMenu.add(viewerColour);
653
654     /*
655      * add 'set background colour'
656      */
657     JMenuItem backGround = new JMenuItem();
658     backGround
659             .setText(MessageManager.getString("action.background_colour"));
660     backGround.addActionListener(new ActionListener()
661     {
662       @Override
663       public void actionPerformed(ActionEvent actionEvent)
664       {
665         background_actionPerformed(actionEvent);
666       }
667     });
668     colourMenu.add(backGround);
669
670     /*
671      * add colour buttons to a group so their selection is
672      * mutually exclusive (background colour is a separate option)
673      */
674     itemGroup.add(seqColour);
675     itemGroup.add(chainColour);
676     itemGroup.add(chargeColour);
677     itemGroup.add(viewerColour);
678   }
679
680   /**
681    * Construct menu items
682    */
683   protected void initMenus()
684   {
685     AAStructureBindingModel binding = getBinding();
686
687     seqColour = new JRadioButtonMenuItem();
688     seqColour.setText(MessageManager.getString("action.by_sequence"));
689     seqColour.setName(ViewerColour.BySequence.name());
690     seqColour.setSelected(binding.isColourBySequence());
691     seqColour.addActionListener(new ActionListener()
692     {
693       @Override
694       public void actionPerformed(ActionEvent actionEvent)
695       {
696         seqColour_actionPerformed(actionEvent);
697       }
698     });
699
700     chainColour = new JRadioButtonMenuItem();
701     chainColour.setText(MessageManager.getString("action.by_chain"));
702     chainColour.setName(ViewerColour.ByChain.name());
703     chainColour.addActionListener(new ActionListener()
704     {
705       @Override
706       public void actionPerformed(ActionEvent actionEvent)
707       {
708         chainColour_actionPerformed(actionEvent);
709       }
710     });
711
712     chargeColour = new JRadioButtonMenuItem();
713     chargeColour.setText(MessageManager.getString("label.charge_cysteine"));
714     chargeColour.setName(ViewerColour.ChargeCysteine.name());
715     chargeColour.addActionListener(new ActionListener()
716     {
717       @Override
718       public void actionPerformed(ActionEvent actionEvent)
719       {
720         chargeColour_actionPerformed(actionEvent);
721       }
722     });
723
724     viewerColour = new JRadioButtonMenuItem();
725     // text is set in overrides of this method
726     viewerColour.setName(ViewerColour.ByViewer.name());
727     viewerColour.setSelected(!binding.isColourBySequence());
728
729     if (_colourwith == null)
730     {
731       _colourwith = new Vector<>();
732     }
733     if (_alignwith == null)
734     {
735       _alignwith = new Vector<>();
736     }
737
738     ViewSelectionMenu seqColourBy = new ViewSelectionMenu(
739             MessageManager.getString("label.colour_by"), this, _colourwith,
740             new ItemListener()
741             {
742               @Override
743               public void itemStateChanged(ItemEvent e)
744               {
745                 if (!seqColour.isSelected())
746                 {
747                   seqColour.doClick();
748                 }
749                 else
750                 {
751                   // update the Chimera display now.
752                   seqColour_actionPerformed(null);
753                 }
754               }
755             });
756     viewMenu.add(seqColourBy);
757
758     showAlignmentOnly = new JCheckBoxMenuItem(
759             MessageManager.getString("label.show_alignment_only"));
760     showAlignmentOnly.addActionListener(new ActionListener()
761     {
762       @Override
763       public void actionPerformed(ActionEvent e)
764       {
765         hideHiddenRegions.setEnabled(showAlignmentOnly.isSelected());
766         getBinding().setShowAlignmentOnly(showAlignmentOnly.isSelected());
767         getBinding().showStructures(getAlignmentPanel().getAlignViewport(),
768                 true);
769       }
770     });
771     viewMenu.add(showAlignmentOnly);
772
773     hideHiddenRegions = new JCheckBoxMenuItem(
774             MessageManager.getString("label.hide_hidden_regions"));
775     hideHiddenRegions.setEnabled(false);
776     hideHiddenRegions.addActionListener(new ActionListener()
777     {
778       @Override
779       public void actionPerformed(ActionEvent e)
780       {
781         getBinding().setHideHiddenRegions(hideHiddenRegions.isSelected());
782         getBinding().showStructures(getAlignmentPanel().getAlignViewport(),
783                 false);
784       }
785     });
786     viewMenu.add(hideHiddenRegions);
787
788     final ItemListener handler = new ItemListener()
789     {
790       @Override
791       public void itemStateChanged(ItemEvent e)
792       {
793         alignStructs.setEnabled(!_alignwith.isEmpty());
794         alignStructs.setToolTipText(MessageManager.formatMessage(
795                 "label.align_structures_using_linked_alignment_views",
796                 _alignwith.size()));
797       }
798     };
799     viewSelectionMenu = new ViewSelectionMenu(
800             MessageManager.getString("label.superpose_with"), this,
801             _alignwith, handler);
802     handler.itemStateChanged(null);
803     viewerActionMenu.add(viewSelectionMenu, 0);
804     viewerActionMenu.addMenuListener(new MenuListener()
805     {
806       @Override
807       public void menuSelected(MenuEvent e)
808       {
809         handler.itemStateChanged(null);
810       }
811
812       @Override
813       public void menuDeselected(MenuEvent e)
814       {
815       }
816
817       @Override
818       public void menuCanceled(MenuEvent e)
819       {
820       }
821     });
822
823     buildColourMenu();
824   }
825
826   /**
827    * Sends commands to the structure viewer to superimpose structures based on
828    * currently associated alignments. May optionally return an error message for
829    * the operation.
830    */
831   @Override
832   protected String alignStructs_actionPerformed(ActionEvent actionEvent)
833   {
834     return alignStructs_withAllAlignPanels();
835   }
836
837   protected String alignStructs_withAllAlignPanels()
838   {
839     if (getAlignmentPanel() == null)
840     {
841       return null;
842     }
843
844     if (_alignwith.size() == 0)
845     {
846       _alignwith.add(getAlignmentPanel());
847     }
848
849     String reply = null;
850     try
851     {
852       AlignmentI[] als = new Alignment[_alignwith.size()];
853       HiddenColumns[] alc = new HiddenColumns[_alignwith.size()];
854       int[] alm = new int[_alignwith.size()];
855       int a = 0;
856
857       for (AlignmentViewPanel alignPanel : _alignwith)
858       {
859         AlignViewportI av = alignPanel.getAlignViewport();
860         als[a] = av.getAlignment();
861         alm[a] = -1;
862         alc[a++] = av.getAlignment().getHiddenColumns();
863       }
864       reply = getBinding().superposeStructures(als, alm, alc);
865       if (reply != null)
866       {
867         String text = MessageManager
868                 .formatMessage("error.superposition_failed", reply);
869         statusBar.setText(text);
870       }
871     } catch (Exception e)
872     {
873       StringBuffer sp = new StringBuffer();
874       for (AlignmentViewPanel avp : _alignwith)
875       {
876         sp.append(
877                 "'" + ((AlignmentPanel) avp).alignFrame.getTitle() + "' ");
878       }
879       Cache.log.info("Couldn't align structures with the " + sp.toString()
880               + "associated alignment panels.", e);
881     }
882     return reply;
883   }
884
885   @Override
886   public void background_actionPerformed(ActionEvent actionEvent)
887   {
888     Color col = JColorChooser.showDialog(this,
889             MessageManager.getString("label.select_background_colour"),
890             null);
891     if (col != null)
892     {
893       getBinding().setBackgroundColour(col);
894     }
895   }
896
897   @Override
898   public void viewerColour_actionPerformed(ActionEvent actionEvent)
899   {
900     if (viewerColour.isSelected())
901     {
902       // disable automatic sequence colouring.
903       getBinding().setColourBySequence(false);
904     }
905   }
906
907   @Override
908   public void chainColour_actionPerformed(ActionEvent actionEvent)
909   {
910     chainColour.setSelected(true);
911     getBinding().colourByChain();
912   }
913
914   @Override
915   public void chargeColour_actionPerformed(ActionEvent actionEvent)
916   {
917     chargeColour.setSelected(true);
918     getBinding().colourByCharge();
919   }
920
921   @Override
922   public void seqColour_actionPerformed(ActionEvent actionEvent)
923   {
924     AAStructureBindingModel binding = getBinding();
925     binding.setColourBySequence(seqColour.isSelected());
926     if (_colourwith == null)
927     {
928       _colourwith = new Vector<>();
929     }
930     if (binding.isColourBySequence())
931     {
932       if (!binding.isLoadingFromArchive())
933       {
934         if (_colourwith.size() == 0 && getAlignmentPanel() != null)
935         {
936           // Make the currently displayed alignment panel the associated view
937           _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
938         }
939       }
940       // Set the colour using the current view for the associated alignframe
941       for (AlignmentViewPanel avp : _colourwith)
942       {
943         binding.updateStructureColours(avp);
944       }
945       seqColoursApplied = true;
946     }
947   }
948
949   @Override
950   public void pdbFile_actionPerformed(ActionEvent actionEvent)
951   {
952     JalviewFileChooser chooser = new JalviewFileChooser(
953             Cache.getProperty("LAST_DIRECTORY"));
954
955     chooser.setFileView(new JalviewFileView());
956     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
957     chooser.setToolTipText(MessageManager.getString("action.save"));
958
959     int value = chooser.showSaveDialog(this);
960
961     if (value == JalviewFileChooser.APPROVE_OPTION)
962     {
963       BufferedReader in = null;
964       try
965       {
966         // TODO: cope with multiple PDB files in view
967         in = new BufferedReader(
968                 new FileReader(getBinding().getStructureFiles()[0]));
969         File outFile = chooser.getSelectedFile();
970
971         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
972         String data;
973         while ((data = in.readLine()) != null)
974         {
975           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
976           {
977             out.println(data);
978           }
979         }
980         out.close();
981       } catch (Exception ex)
982       {
983         ex.printStackTrace();
984       } finally
985       {
986         if (in != null)
987         {
988           try
989           {
990             in.close();
991           } catch (IOException e)
992           {
993             // ignore
994           }
995         }
996       }
997     }
998   }
999
1000   @Override
1001   public void viewMapping_actionPerformed(ActionEvent actionEvent)
1002   {
1003     CutAndPasteTransfer cap = new CutAndPasteTransfer();
1004     try
1005     {
1006       cap.appendText(getBinding().printMappings());
1007     } catch (OutOfMemoryError e)
1008     {
1009       new OOMWarning(
1010               "composing sequence-structure alignments for display in text box.",
1011               e);
1012       cap.dispose();
1013       return;
1014     }
1015     Desktop.addInternalFrame(cap,
1016             MessageManager.getString("label.pdb_sequence_mapping"), 550,
1017             600);
1018   }
1019
1020   protected abstract String getViewerName();
1021
1022   /**
1023    * Configures the title and menu items of the viewer panel.
1024    */
1025   @Override
1026   public void updateTitleAndMenus()
1027   {
1028     AAStructureBindingModel binding = getBinding();
1029     if (binding.hasFileLoadingError())
1030     {
1031       repaint();
1032       return;
1033     }
1034     setChainMenuItems(binding.getChainNames());
1035
1036     this.setTitle(binding.getViewerTitle(getViewerName(), true));
1037
1038     /*
1039      * enable 'Superpose with' if more than one mapped structure
1040      */
1041     viewSelectionMenu.setEnabled(false);
1042     if (getBinding().getStructureFiles().length > 1
1043             && getBinding().getSequence().length > 1)
1044     {
1045       viewSelectionMenu.setEnabled(true);
1046     }
1047
1048     /*
1049      * Show action menu if it has any enabled items
1050      */
1051     viewerActionMenu.setVisible(false);
1052     for (int i = 0; i < viewerActionMenu.getItemCount(); i++)
1053     {
1054       if (viewerActionMenu.getItem(i).isEnabled())
1055       {
1056         viewerActionMenu.setVisible(true);
1057         break;
1058       }
1059     }
1060
1061     if (!binding.isLoadingFromArchive())
1062     {
1063       seqColour_actionPerformed(null);
1064     }
1065   }
1066
1067   @Override
1068   public String toString()
1069   {
1070     return getTitle();
1071   }
1072
1073   @Override
1074   public boolean hasMapping()
1075   {
1076     if (worker != null && (addingStructures || _started))
1077     {
1078       return false;
1079     }
1080     if (getBinding() == null)
1081     {
1082       if (_aps == null || _aps.size() == 0)
1083       {
1084         // viewer has been closed, but we did at some point run.
1085         return true;
1086       }
1087       return false;
1088     }
1089     String[] pdbids = getBinding().getStructureFiles();
1090     if (pdbids == null)
1091     {
1092       return false;
1093     }
1094     int p=0;
1095     for (String pdbid:pdbids) {
1096       StructureMapping sm[] = getBinding().getSsm().getMapping(pdbid);
1097       if (sm!=null && sm.length>0 && sm[0]!=null) {
1098         p++;
1099       }
1100     }
1101     // only return true if there is a mapping for every structure file we have loaded
1102     if (p == 0 || p != pdbids.length)
1103     {
1104       return false;
1105     }
1106     // and that coloring has been applied
1107     return seqColoursApplied;
1108   }
1109
1110   @Override
1111   public void raiseViewer()
1112   {
1113     toFront();
1114   }
1115
1116   @Override
1117   public abstract AAStructureBindingModel getBinding();
1118
1119   /**
1120    * Show only the selected chain(s) in the viewer
1121    */
1122   protected void showSelectedChains()
1123   {
1124     setSelectedChains();
1125   
1126     /*
1127      * refresh display without resizing - easier to see what changed
1128      */
1129     getBinding().showStructures(getAlignmentPanel().getAlignViewport(),
1130             false);
1131   }
1132
1133 }