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