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