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