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