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