JAL-2465 bugfix and rerefactor renamed getPdbFile() method to getStructureFile()
[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   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   protected abstract AAStructureBindingModel getBindingModel();
313
314   protected abstract IProgressIndicator getIProgressIndicator();
315
316   /**
317    * add a new structure (with associated sequences and chains) to this viewer,
318    * retrieving it if necessary first.
319    * 
320    * @param pdbentry
321    * @param seqs
322    * @param chains
323    * @param align
324    *          if true, new structure(s) will be aligned using associated
325    *          alignment
326    * @param alignFrame
327    */
328   protected void addStructure(final PDBEntry pdbentry,
329           final SequenceI[] seqs, final String[] chains,
330           final boolean align, final IProgressIndicator alignFrame)
331   {
332     if (pdbentry.getFile() == null)
333     {
334       if (worker != null && worker.isAlive())
335       {
336         // a retrieval is in progress, wait around and add ourselves to the
337         // queue.
338         new Thread(new Runnable()
339         {
340           @Override
341           public void run()
342           {
343             while (worker != null && worker.isAlive() && _started)
344             {
345               try
346               {
347                 Thread.sleep(100 + ((int) Math.random() * 100));
348
349               } catch (Exception e)
350               {
351               }
352             }
353             // and call ourselves again.
354             addStructure(pdbentry, seqs, chains, align, alignFrame);
355           }
356         }).start();
357         return;
358       }
359     }
360     // otherwise, start adding the structure.
361     getBinding().addSequenceAndChain(new PDBEntry[] { pdbentry },
362             new SequenceI[][] { seqs }, new String[][] { chains });
363     addingStructures = true;
364     _started = false;
365     alignAddedStructures = align;
366     worker = new Thread(this);
367     worker.start();
368     return;
369   }
370
371   /**
372    * Presents a dialog with the option to add an align a structure to an
373    * existing structure view
374    * 
375    * @param pdbId
376    * @param view
377    * @return YES, NO or CANCEL JvOptionPane code
378    */
379   protected int chooseAlignStructureToViewer(String pdbId,
380           StructureViewerBase view)
381   {
382     int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
383             MessageManager.formatMessage("label.add_pdbentry_to_view",
384                     new Object[] { pdbId, view.getTitle() }),
385             MessageManager
386                     .getString("label.align_to_existing_structure_view"),
387             JvOptionPane.YES_NO_CANCEL_OPTION);
388     return option;
389   }
390
391   protected boolean hasPdbId(String pdbId)
392   {
393     return getBinding().hasPdbId(pdbId);
394   }
395
396   protected abstract List<StructureViewerBase> getViewersFor(
397           AlignmentPanel alp);
398
399   /**
400    * Check for any existing views involving this alignment and give user the
401    * option to add and align this molecule to one of them
402    * 
403    * @param pdbentry
404    * @param seq
405    * @param chains
406    * @param apanel
407    * @param pdbId
408    * @return true if user adds to a view, or cancels entirely, else false
409    */
410   protected boolean addToExistingViewer(PDBEntry pdbentry, SequenceI[] seq,
411           String[] chains, final AlignmentPanel apanel, String pdbId)
412   {
413     for (StructureViewerBase view : getViewersFor(apanel))
414     {
415       // TODO: highlight the view somehow
416       /*
417        * JAL-1742 exclude view with this structure already mapped (don't offer
418        * to align chain B to chain A of the same structure)
419        */
420       if (view.hasPdbId(pdbId))
421       {
422         continue;
423       }
424       int option = chooseAlignStructureToViewer(pdbId, view);
425       if (option == JvOptionPane.CANCEL_OPTION)
426       {
427         return true;
428       }
429       else if (option == JvOptionPane.YES_OPTION)
430       {
431         view.useAlignmentPanelForSuperposition(apanel);
432         view.addStructure(pdbentry, seq, chains, true, apanel.alignFrame);
433         return true;
434       }
435       else
436       {
437         // NO_OPTION - offer the next viewer if any
438       }
439     }
440
441     /*
442      * nothing offered and selected
443      */
444     return false;
445   }
446
447   /**
448    * Adds mappings for the given sequences to an already opened PDB structure,
449    * and updates any viewers that have the PDB file
450    * 
451    * @param seq
452    * @param chains
453    * @param apanel
454    * @param pdbFilename
455    */
456   protected void addSequenceMappingsToStructure(SequenceI[] seq,
457           String[] chains, final AlignmentPanel apanel, String pdbFilename)
458   {
459     // TODO : Fix multiple seq to one chain issue here.
460     /*
461      * create the mappings
462      */
463     apanel.getStructureSelectionManager().setMapping(seq, chains,
464             pdbFilename, DataSourceType.FILE, getIProgressIndicator());
465
466     /*
467      * alert the FeatureRenderer to show new (PDB RESNUM) features
468      */
469     if (apanel.getSeqPanel().seqCanvas.fr != null)
470     {
471       apanel.getSeqPanel().seqCanvas.fr.featuresAdded();
472       apanel.paintAlignment(true);
473     }
474
475     /*
476      * add the sequences to any other viewers (of the same type) for this pdb
477      * file
478      */
479     // JBPNOTE: this looks like a binding routine, rather than a gui routine
480     for (StructureViewerBase viewer : getViewersFor(null))
481     {
482       AAStructureBindingModel bindingModel = viewer.getBinding();
483       for (int pe = 0; pe < bindingModel.getPdbCount(); pe++)
484       {
485         if (bindingModel.getPdbEntry(pe).getFile().equals(pdbFilename))
486         {
487           bindingModel.addSequence(pe, seq);
488           viewer.addAlignmentPanel(apanel);
489           /*
490            * add it to the set of alignments used for colouring structure by
491            * sequence
492            */
493           viewer.useAlignmentPanelForColourbyseq(apanel);
494           viewer.buildActionMenu();
495           apanel.getStructureSelectionManager().sequenceColoursChanged(
496                   apanel);
497           break;
498         }
499       }
500     }
501   }
502
503   /**
504    * Check if the PDB file is already loaded, if so offer to add it to the
505    * existing viewer
506    * 
507    * @param seq
508    * @param chains
509    * @param apanel
510    * @param pdbId
511    * @return true if the user chooses to add to a viewer, or to cancel entirely
512    */
513   protected boolean addAlreadyLoadedFile(SequenceI[] seq, String[] chains,
514           final AlignmentPanel apanel, String pdbId)
515   {
516     boolean finished = false;
517     String alreadyMapped = apanel.getStructureSelectionManager()
518             .alreadyMappedToFile(pdbId);
519
520     if (alreadyMapped != null)
521     {
522       /*
523        * the PDB file is already loaded
524        */
525       int option = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
526               MessageManager.formatMessage(
527                       "label.pdb_entry_is_already_displayed",
528                       new Object[] { pdbId }), MessageManager
529                       .formatMessage(
530                               "label.map_sequences_to_visible_window",
531                               new Object[] { pdbId }),
532               JvOptionPane.YES_NO_CANCEL_OPTION);
533       if (option == JvOptionPane.CANCEL_OPTION)
534       {
535         finished = true;
536       }
537       else if (option == JvOptionPane.YES_OPTION)
538       {
539         addSequenceMappingsToStructure(seq, chains, apanel, alreadyMapped);
540         finished = true;
541       }
542     }
543     return finished;
544   }
545
546   void setChainMenuItems(List<String> chainNames)
547   {
548     chainMenu.removeAll();
549     if (chainNames == null || chainNames.isEmpty())
550     {
551       return;
552     }
553     JMenuItem menuItem = new JMenuItem(
554             MessageManager.getString("label.all"));
555     menuItem.addActionListener(new ActionListener()
556     {
557       @Override
558       public void actionPerformed(ActionEvent evt)
559       {
560         allChainsSelected = true;
561         for (int i = 0; i < chainMenu.getItemCount(); i++)
562         {
563           if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
564           {
565             ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
566           }
567         }
568         showSelectedChains();
569         allChainsSelected = false;
570       }
571     });
572
573     chainMenu.add(menuItem);
574
575     for (String chain : chainNames)
576     {
577       menuItem = new JCheckBoxMenuItem(chain, true);
578       menuItem.addItemListener(new ItemListener()
579       {
580         @Override
581         public void itemStateChanged(ItemEvent evt)
582         {
583           if (!allChainsSelected)
584           {
585             showSelectedChains();
586           }
587         }
588       });
589
590       chainMenu.add(menuItem);
591     }
592   }
593
594   abstract void showSelectedChains();
595
596   /**
597    * Action on selecting one of Jalview's registered colour schemes
598    */
599   @Override
600   public void changeColour_actionPerformed(String colourSchemeName)
601   {
602     AlignmentI al = getAlignmentPanel().av.getAlignment();
603     ColourSchemeI cs = ColourSchemes.getInstance().getColourScheme(
604             colourSchemeName, al, null);
605     getBinding().setJalviewColourScheme(cs);
606   }
607
608   /**
609    * Builds the colour menu
610    */
611   protected void buildColourMenu()
612   {
613     colourMenu.removeAll();
614     AlignmentI al = getAlignmentPanel().av.getAlignment();
615
616     /*
617      * add colour by sequence, by chain, by charge and cysteine
618      */
619     colourMenu.add(seqColour);
620     colourMenu.add(chainColour);
621     colourMenu.add(chargeColour);
622     chargeColour.setEnabled(!al.isNucleotide());
623
624     /*
625      * add all 'simple' (per-residue) colour schemes registered to Jalview
626      */
627     ButtonGroup itemGroup = ColourMenuHelper.addMenuItems(colourMenu, this,
628             al, true);
629
630     /*
631      * add 'colour by viewer' (menu item text is set in subclasses)
632      */
633     viewerColour.setSelected(false);
634     viewerColour.addActionListener(new ActionListener()
635     {
636       @Override
637       public void actionPerformed(ActionEvent actionEvent)
638       {
639         viewerColour_actionPerformed(actionEvent);
640       }
641     });
642     colourMenu.add(viewerColour);
643
644     /*
645      * add 'set background colour'
646      */
647     JMenuItem backGround = new JMenuItem();
648     backGround
649             .setText(MessageManager.getString("action.background_colour"));
650     backGround.addActionListener(new ActionListener()
651     {
652       @Override
653       public void actionPerformed(ActionEvent actionEvent)
654       {
655         background_actionPerformed(actionEvent);
656       }
657     });
658     colourMenu.add(backGround);
659
660     /*
661      * add colour buttons to a group so their selection is
662      * mutually exclusive (background colour is a separate option)
663      */
664     itemGroup.add(seqColour);
665     itemGroup.add(chainColour);
666     itemGroup.add(chargeColour);
667     itemGroup.add(viewerColour);
668   }
669
670   /**
671    * Construct menu items
672    */
673   protected void initMenus()
674   {
675     AAStructureBindingModel binding = getBinding();
676
677     seqColour = new JRadioButtonMenuItem();
678     seqColour.setText(MessageManager.getString("action.by_sequence"));
679     seqColour.setName(ViewerColour.BySequence.name());
680     seqColour.setSelected(binding.isColourBySequence());
681     seqColour.addActionListener(new ActionListener()
682     {
683       @Override
684       public void actionPerformed(ActionEvent actionEvent)
685       {
686         seqColour_actionPerformed(actionEvent);
687       }
688     });
689
690     chainColour = new JRadioButtonMenuItem();
691     chainColour.setText(MessageManager.getString("action.by_chain"));
692     chainColour.setName(ViewerColour.ByChain.name());
693     chainColour.addActionListener(new ActionListener()
694     {
695       @Override
696       public void actionPerformed(ActionEvent actionEvent)
697       {
698         chainColour_actionPerformed(actionEvent);
699       }
700     });
701
702     chargeColour = new JRadioButtonMenuItem();
703     chargeColour.setText(MessageManager.getString("label.charge_cysteine"));
704     chargeColour.setName(ViewerColour.ChargeCysteine.name());
705     chargeColour.addActionListener(new ActionListener()
706     {
707       @Override
708       public void actionPerformed(ActionEvent actionEvent)
709       {
710         chargeColour_actionPerformed(actionEvent);
711       }
712     });
713
714     viewerColour = new JRadioButtonMenuItem();
715     // text is set in overrides of this method
716     viewerColour.setName(ViewerColour.ByViewer.name());
717     viewerColour.setSelected(!binding.isColourBySequence());
718
719     if (_colourwith == null)
720     {
721       _colourwith = new Vector<AlignmentPanel>();
722     }
723     if (_alignwith == null)
724     {
725       _alignwith = new Vector<AlignmentPanel>();
726     }
727
728     ViewSelectionMenu seqColourBy = new ViewSelectionMenu(
729             MessageManager.getString("label.colour_by"), this, _colourwith,
730             new ItemListener()
731             {
732               @Override
733               public void itemStateChanged(ItemEvent e)
734               {
735                 if (!seqColour.isSelected())
736                 {
737                   seqColour.doClick();
738                 }
739                 else
740                 {
741                   // update the Chimera display now.
742                   seqColour_actionPerformed(null);
743                 }
744               }
745             });
746     viewMenu.add(seqColourBy);
747
748     final ItemListener handler = new ItemListener()
749     {
750       @Override
751       public void itemStateChanged(ItemEvent e)
752       {
753         alignStructs.setEnabled(!_alignwith.isEmpty());
754         alignStructs.setToolTipText(MessageManager.formatMessage(
755                 "label.align_structures_using_linked_alignment_views",
756                 _alignwith.size()));
757       }
758     };
759     viewSelectionMenu = new ViewSelectionMenu(
760             MessageManager.getString("label.superpose_with"), this,
761             _alignwith, handler);
762     handler.itemStateChanged(null);
763     viewerActionMenu.add(viewSelectionMenu, 0);
764     viewerActionMenu.addMenuListener(new MenuListener()
765     {
766       @Override
767       public void menuSelected(MenuEvent e)
768       {
769         handler.itemStateChanged(null);
770       }
771
772       @Override
773       public void menuDeselected(MenuEvent e)
774       {
775       }
776
777       @Override
778       public void menuCanceled(MenuEvent e)
779       {
780       }
781     });
782
783     buildColourMenu();
784   }
785
786   @Override
787   public void setJalviewColourScheme(ColourSchemeI cs) {
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(
798           ActionEvent actionEvent)
799   {
800     return alignStructs_withAllAlignPanels();
801   }
802
803   protected String alignStructs_withAllAlignPanels()
804   {
805     if (getAlignmentPanel() == null)
806     {
807       return null;
808     }
809   
810     if (_alignwith.size() == 0)
811     {
812       _alignwith.add(getAlignmentPanel());
813     }
814   
815     String reply = null;
816     try
817     {
818       AlignmentI[] als = new Alignment[_alignwith.size()];
819       ColumnSelection[] alc = new ColumnSelection[_alignwith.size()];
820       int[] alm = new int[_alignwith.size()];
821       int a = 0;
822   
823       for (AlignmentPanel ap : _alignwith)
824       {
825         als[a] = ap.av.getAlignment();
826         alm[a] = -1;
827         alc[a++] = ap.av.getColumnSelection();
828       }
829       reply = getBinding().superposeStructures(als, alm, alc);
830       if (reply != null)
831       {
832         String text = MessageManager.formatMessage(
833                 "error.superposition_failed", reply);
834         statusBar.setText(text);
835       }
836     } catch (Exception e)
837     {
838       StringBuffer sp = new StringBuffer();
839       for (AlignmentPanel ap : _alignwith)
840       {
841         sp.append("'" + ap.alignFrame.getTitle() + "' ");
842       }
843       Cache.log.info("Couldn't align structures with the " + sp.toString()
844               + "associated alignment panels.", e);
845     }
846     return reply;
847   }
848
849   @Override
850   public void background_actionPerformed(ActionEvent actionEvent)
851   {
852     Color col = JColorChooser.showDialog(this,
853             MessageManager.getString("label.select_background_colour"),
854             null);
855     if (col != null)
856     {
857       getBinding().setBackgroundColour(col);
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   @Override
870   public void chainColour_actionPerformed(ActionEvent actionEvent)
871   {
872     chainColour.setSelected(true);
873     getBinding().colourByChain();
874   }
875   @Override
876   public void chargeColour_actionPerformed(ActionEvent actionEvent)
877   {
878     chargeColour.setSelected(true);
879     getBinding().colourByCharge();
880   }
881   @Override
882   public void seqColour_actionPerformed(ActionEvent actionEvent)
883   {
884     AAStructureBindingModel binding = getBinding();
885     binding.setColourBySequence(seqColour.isSelected());
886     if (_colourwith == null)
887     {
888       _colourwith = new Vector<AlignmentPanel>();
889     }
890     if (binding.isColourBySequence())
891     {
892       if (!binding.isLoadingFromArchive())
893       {
894         if (_colourwith.size() == 0 && getAlignmentPanel() != null)
895         {
896           // Make the currently displayed alignment panel the associated view
897           _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
898         }
899       }
900       // Set the colour using the current view for the associated alignframe
901       for (AlignmentPanel ap : _colourwith)
902       {
903         binding.colourBySequence(ap);
904       }
905     }
906   }
907   @Override
908   public void pdbFile_actionPerformed(ActionEvent actionEvent)
909   {
910     JalviewFileChooser chooser = new JalviewFileChooser(
911             Cache.getProperty("LAST_DIRECTORY"));
912   
913     chooser.setFileView(new JalviewFileView());
914     chooser.setDialogTitle(MessageManager.getString("label.save_pdb_file"));
915     chooser.setToolTipText(MessageManager.getString("action.save"));
916   
917     int value = chooser.showSaveDialog(this);
918   
919     if (value == JalviewFileChooser.APPROVE_OPTION)
920     {
921       BufferedReader in = null;
922       try
923       {
924         // TODO: cope with multiple PDB files in view
925         in = new BufferedReader(
926                 new FileReader(getBinding().getStructureFiles()[0]));
927         File outFile = chooser.getSelectedFile();
928   
929         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
930         String data;
931         while ((data = in.readLine()) != null)
932         {
933           if (!(data.indexOf("<PRE>") > -1 || data.indexOf("</PRE>") > -1))
934           {
935             out.println(data);
936           }
937         }
938         out.close();
939       } catch (Exception ex)
940       {
941         ex.printStackTrace();
942       } finally
943       {
944         if (in != null)
945         {
946           try
947           {
948             in.close();
949           } catch (IOException e)
950           {
951             // ignore
952           }
953         }
954       }
955     }
956   }
957   @Override
958   public void viewMapping_actionPerformed(ActionEvent actionEvent)
959   {
960     CutAndPasteTransfer cap = new CutAndPasteTransfer();
961     try
962     {
963       cap.appendText(getBinding().printMappings());
964     } catch (OutOfMemoryError e)
965     {
966       new OOMWarning(
967               "composing sequence-structure alignments for display in text box.",
968               e);
969       cap.dispose();
970       return;
971     }
972     Desktop.addInternalFrame(cap,
973             MessageManager.getString("label.pdb_sequence_mapping"), 550,
974             600);
975   }
976
977   protected abstract String getViewerName();
978
979   /**
980    * Configures the title and menu items of the viewer panel.
981    */
982   public void updateTitleAndMenus()
983   {
984     AAStructureBindingModel binding = getBinding();
985     if (binding.hasFileLoadingError())
986     {
987       repaint();
988       return;
989     }
990     setChainMenuItems(binding.getChainNames());
991   
992     this.setTitle(binding.getViewerTitle(getViewerName(), true));
993
994     /*
995      * enable 'Superpose with' if more than one mapped structure
996      */
997     viewSelectionMenu.setEnabled(false);
998     if (getBinding().getStructureFiles().length > 1
999             && getBinding().getSequence().length > 1)
1000     {
1001       viewSelectionMenu.setEnabled(true);
1002     }
1003
1004     /*
1005      * Show action menu if it has any enabled items
1006      */
1007     viewerActionMenu.setVisible(false);
1008     for (int i = 0; i < viewerActionMenu.getItemCount(); i++)
1009     {
1010       if (viewerActionMenu.getItem(i).isEnabled())
1011       {
1012         viewerActionMenu.setVisible(true);
1013         break;
1014       }
1015     }
1016
1017     if (!binding.isLoadingFromArchive())
1018     {
1019       seqColour_actionPerformed(null);
1020     }
1021   }
1022 }