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