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