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