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