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