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