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