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