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